Customize BiometricPrompt for automative bulids

Bug: 276918288
Test: manual

Change-Id: I7f464b6f1f2256f88809dc3e0bcafdae0431b009
diff --git a/res/drawable/ic_backspace.xml b/res/drawable/ic_backspace.xml
new file mode 100644
index 0000000..9ad6c54
--- /dev/null
+++ b/res/drawable/ic_backspace.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2023 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/pin_pad_icon_size"
+    android:height="@dimen/pin_pad_icon_size"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="@color/pin_pad_icon_color"
+        android:pathData="M22,3H7C6.31,3 5.77,3.35 5.41,3.88l-5.04,7.57c-0.22,0.34 -0.22,0.77 0,1.11l5.04,7.56C5.77,20.64 6.31,21 7,21h15c1.1,0 2,-0.9 2,-2V5C24,3.9 23.1,3 22,3zM18.3,16.3L18.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L14,13.41l-2.89,2.89c-0.39,0.39 -1.02,0.39 -1.41,0h0c-0.39,-0.39 -0.39,-1.02 0,-1.41L12.59,12L9.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41l0,0c0.39,-0.39 1.02,-0.39 1.41,0L14,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0v0c0.39,0.39 0.39,1.02 0,1.41L15.41,12l2.89,2.89C18.68,15.27 18.68,15.91 18.3,16.3z"/>
+</vector>
diff --git a/res/drawable/ic_check.xml b/res/drawable/ic_check.xml
new file mode 100644
index 0000000..3f32759
--- /dev/null
+++ b/res/drawable/ic_check.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2023 The Android Open Source Project
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/pin_pad_icon_size"
+    android:height="@dimen/pin_pad_icon_size"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:pathData="M0 0h24v24H0z"/>
+    <path
+        android:fillColor="@color/pin_pad_icon_color"
+        android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</vector>
diff --git a/res/layout/pin_pad_view.xml b/res/layout/pin_pad_view.xml
new file mode 100644
index 0000000..52cb760
--- /dev/null
+++ b/res/layout/pin_pad_view.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!-- This file is copied from packages/apps/Car/Settings/res/layout/pin_pad_view.xml -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Row 1 -->
+    <Button
+        android:id="@+id/key1"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="1"
+        android:text="@string/one"/>
+    <Button
+        android:id="@+id/key2"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="2"
+        android:text="@string/two"/>
+    <Button
+        android:id="@+id/key3"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="3"
+        android:text="@string/three"/>
+    <!-- Row 2 -->
+    <Button
+        android:id="@+id/key4"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="4"
+        android:text="@string/four"/>
+    <Button
+        android:id="@+id/key5"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="5"
+        android:text="@string/five"/>
+    <Button
+        android:id="@+id/key6"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="6"
+        android:text="@string/six"/>
+    <!-- Row 3 -->
+    <Button
+        android:id="@+id/key7"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="7"
+        android:text="@string/seven"/>
+    <Button
+        android:id="@+id/key8"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="8"
+        android:text="@string/eight"/>
+    <Button
+        android:id="@+id/key9"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="9"
+        android:text="@string/nine"/>
+    <!-- Row 4 -->
+    <ImageButton
+        android:id="@+id/key_backspace"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:contentDescription="@string/backspace_key"
+        android:src="@drawable/ic_backspace"/>
+    <Button
+        android:id="@+id/key0"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:tag="0"
+        android:text="@string/zero"/>
+    <ImageButton
+        android:id="@+id/key_enter"
+        style="@style/PinPadKey"
+        android:layout_width="@dimen/pin_pad_key_width"
+        android:layout_height="@dimen/pin_pad_key_height"
+        android:layout_margin="@dimen/pin_pad_key_margin"
+        android:contentDescription="@string/enter_key"
+        android:src="@drawable/ic_check"/>
+</merge>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e2a3819..df3185d 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -169,4 +169,8 @@
         <attr name="showAsDropDown" format="boolean"/>
         <attr name="panelGravity" format="integer"/>
     </declare-styleable>
+
+    <declare-styleable name="PinPadView">
+        <attr name="layout" format="reference"/>
+    </declare-styleable>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 167f962..fe31e89 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -154,4 +154,7 @@
 
     <!-- Color for button background in the quick controls panel. -->
     <color name="car_quick_controls_button_background_pill_color">#3C4043</color>
+
+    <!-- Color for PIN Pad icon.-->
+    <color name="pin_pad_icon_color">@color/car_ui_text_color_primary</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 11649d4..10dc13c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -432,4 +432,12 @@
     <dimen name="car_quick_controls_panel_footer_button_view_padding">10dp</dimen>
 
     <dimen name="confirm_logout_dialog_dim" format="float">0.8</dimen>
+
+    <!-- Dimensions for Pin pad -->
+    <dimen name="confirm_lock_message_vertical_spacing">@*android:dimen/car_padding_2</dimen>
+    <dimen name="pin_password_entry_padding_horizontal">@*android:dimen/car_padding_2</dimen>
+    <dimen name="pin_pad_key_width">120dp</dimen>
+    <dimen name="pin_pad_key_height">80dp</dimen>
+    <dimen name="pin_pad_key_margin">12dp</dimen>
+    <dimen name="pin_pad_icon_size">@*android:dimen/car_primary_icon_size</dimen>
 </resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 640e131..92f0559 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -20,7 +20,7 @@
     <item type="id" name="lock_screen_nav_buttons"/>
     <item type="id" name="hvac_panel_close_button"/>
 
-    <!-- Values assigneed to quick control views    -->
+    <!-- Values assigned to quick control views    -->
     <item type="id" name="qc_display_status_icon"/>
     <item type="id" name="qc_bluetooth_status_icon"/>
     <item type="id" name="qc_signal_status_icon"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ed8e11c..7c79613 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -243,6 +243,23 @@
     <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
     <string name="car_keyguard_enter_your_password">Enter your password</string>
 
+    <!-- Accessibility description for the backspace key in the PIN pad [CHAR LIMIT=NONE] -->
+    <string name="backspace_key">Backspace key</string>
+    <!-- Accessibility description for the enter key in the PIN pad [CHAR LIMIT=NONE] -->
+    <string name="enter_key">Enter key</string>
+
+    <!-- These are the digits on the PIN pad -->
+    <string name="one" translatable="false">1</string>
+    <string name="two" translatable="false">2</string>
+    <string name="three" translatable="false">3</string>
+    <string name="four" translatable="false">4</string>
+    <string name="five" translatable="false">5</string>
+    <string name="six" translatable="false">6</string>
+    <string name="seven" translatable="false">7</string>
+    <string name="eight" translatable="false">8</string>
+    <string name="nine" translatable="false">9</string>
+    <string name="zero" translatable="false">0</string>
+
     <!-- Intent action meant to invoke user TOS flow.
         The intent URI has to be formatted according to Intent.URI_INTENT_SCHEME
         URI, for example should contain the intent action and any extras if necessary:
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 7059f60..2c0138a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -207,4 +207,16 @@
         <item name="android:layout_margin">@dimen/car_padding_2</item>
         <item name="android:maxLines">2</item>
     </style>
+
+
+    <!-- Biometrics -->
+    <style name="PinPadKey">
+        <item name="android:gravity">center</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@*android:dimen/car_body1_size</item>
+        <item name="android:textColor">@*android:color/car_body3</item>
+        <item name="android:tint">@*android:color/car_body3</item>
+        <item name="android:clickable">true</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+    </style>
 </resources>
diff --git a/src/com/android/systemui/car/biometrics/PinPadView.java b/src/com/android/systemui/car/biometrics/PinPadView.java
new file mode 100644
index 0000000..e1f0d35
--- /dev/null
+++ b/src/com/android/systemui/car/biometrics/PinPadView.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.biometrics;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.GridLayout;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.R;
+import com.android.systemui.biometrics.ui.IPinPad;
+import com.android.systemui.biometrics.ui.PinPadClickListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom view for the PIN pad.
+ */
+public class PinPadView extends GridLayout implements IPinPad {
+    // Number of keys in the pin pad, 0-9 plus backspace and enter keys.
+    @VisibleForTesting
+    static final int NUM_KEYS = 12;
+
+    @VisibleForTesting
+    static final int[] PIN_PAD_DIGIT_KEYS = {R.id.key0, R.id.key1, R.id.key2, R.id.key3,
+            R.id.key4, R.id.key5, R.id.key6, R.id.key7, R.id.key8, R.id.key9};
+
+    /**
+     * The delay in milliseconds between character deletion when the user continuously holds the
+     * backspace key.
+     */
+    private static final int LONG_CLICK_DELAY_MILLS = 100;
+
+    private final List<View> mPinKeys = new ArrayList<>(NUM_KEYS);
+    private final Runnable mOnBackspaceLongClick = new Runnable() {
+        public void run() {
+            if (mOnClickListener != null) {
+                mOnClickListener.onBackspaceClick();
+                getHandler().postDelayed(this, LONG_CLICK_DELAY_MILLS);
+            }
+        }
+    };
+
+    private PinPadClickListener mOnClickListener;
+    private ImageButton mEnterKey;
+
+    public PinPadView(Context context) {
+        super(context);
+        init(null, 0, 0);
+    }
+
+    public PinPadView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs, 0, 0);
+    }
+
+    public PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(attrs, defStyleAttr, 0);
+    }
+
+    public PinPadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        for (View key : mPinKeys) {
+            key.setEnabled(enabled);
+        }
+    }
+
+    /**
+     * Set the resource Id of the enter key icon.
+     *
+     * @param drawableId The resource Id of the drawable.
+     */
+    public void setEnterKeyIcon(@DrawableRes int drawableId) {
+        mEnterKey.setImageResource(drawableId);
+    }
+
+    /**
+     * Override the default tint of the enter key icon.
+     *
+     * @param tint A ColorStateList.
+     */
+    public void setEnterKeyImageTint(ColorStateList tint) {
+        mEnterKey.setImageTintList(tint);
+    }
+
+    /**
+     * Sets if the enter key for submitting a PIN is enabled or disabled.
+     */
+    public void setEnterKeyEnabled(boolean enabled) {
+        mEnterKey.setEnabled(enabled);
+    }
+
+    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        TypedArray typedArray = getContext().obtainStyledAttributes(
+                attrs, R.styleable.PinPadView, defStyleAttr, defStyleRes);
+        inflater.inflate(
+                typedArray.getResourceId(R.styleable.PinPadView_layout, R.layout.pin_pad_view),
+                this, true);
+        typedArray.recycle();
+
+        for (int keyId : PIN_PAD_DIGIT_KEYS) {
+            TextView key = findViewById(keyId);
+            String digit = key.getTag().toString();
+            key.setOnClickListener(v -> mOnClickListener.onDigitKeyClick(digit));
+            mPinKeys.add(key);
+        }
+
+        ImageButton backspace = findViewById(R.id.key_backspace);
+        backspace.setOnTouchListener((v, event) -> {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    getHandler().post(mOnBackspaceLongClick);
+                    // Must return false so that ripple can show
+                    return false;
+                case MotionEvent.ACTION_UP:
+                    getHandler().removeCallbacks(mOnBackspaceLongClick);
+                    // Must return false so that ripple can show
+                    return false;
+                default:
+                    return false;
+            }
+        });
+        backspace.setOnKeyListener((v, code, event) -> {
+            if (code != KeyEvent.KEYCODE_DPAD_CENTER) {
+                return false;
+            }
+            switch (event.getAction()) {
+                case KeyEvent.ACTION_DOWN:
+                    getHandler().post(mOnBackspaceLongClick);
+                    // Must return false so that ripple can show
+                    return false;
+                case KeyEvent.ACTION_UP:
+                    getHandler().removeCallbacks(mOnBackspaceLongClick);
+                    // Must return false so that ripple can show
+                    return false;
+                default:
+                    return false;
+            }
+        });
+        mPinKeys.add(backspace);
+
+        mEnterKey = findViewById(R.id.key_enter);
+        mEnterKey.setOnClickListener(v -> mOnClickListener.onEnterKeyClick());
+
+        mPinKeys.add(mEnterKey);
+    }
+
+    @Override
+    public void setPinPadClickListener(
+            @NonNull PinPadClickListener pinPadClickListener) {
+        mOnClickListener = pinPadClickListener;
+    }
+}