Un-overload Biometric UI negative button

Use different buttons for different actions. Makes code easier to
read, and also easier to test.

Bug: 171357779
Test: Demo app
Test: atest com.android.systemui.biometrics

Change-Id: I77ed66b3e387cc8e1b0c13d908425bff5b85bec4
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 2439fed..aed067c 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -88,7 +88,8 @@
             android:layout_width="8dp"
             android:layout_height="match_parent"
             android:visibility="visible" />
-        <!-- Negative Button -->
+
+        <!-- Negative Button, reserved for app -->
         <Button android:id="@+id/button_negative"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -97,13 +98,32 @@
             android:ellipsize="end"
             android:maxLines="2"
             android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"/>
+        <!-- Cancel Button, replaces negative button when biometric is accepted -->
+        <Button android:id="@+id/button_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:layout_gravity="center_vertical"
+            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
+            android:text="@string/cancel"
+            android:visibility="gone"/>
+        <!-- "Use Credential" Button, replaces if device credential is allowed -->
+        <Button android:id="@+id/button_use_credential"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:layout_gravity="center_vertical"
+            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
+            android:visibility="gone"/>
+
         <Space android:id="@+id/middleSpacer"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:visibility="visible"/>
+
         <!-- Positive Button -->
-        <Button android:id="@+id/button_positive"
+        <Button android:id="@+id/button_confirm"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             style="@*android:style/Widget.DeviceDefault.Button.Colored"
@@ -124,6 +144,7 @@
             android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
             android:text="@string/biometric_dialog_try_again"
             android:visibility="gone"/>
+
         <Space android:id="@+id/rightSpacer"
             android:layout_width="8dp"
             android:layout_height="match_parent"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
index 9a40541..e4f6d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -196,7 +196,7 @@
     public void onAuthenticationFailed(String failureReason) {
         if (getSize() == AuthDialog.SIZE_MEDIUM) {
             mTryAgainButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setVisibility(View.GONE);
+            mConfirmButton.setVisibility(View.GONE);
         }
 
         // Do this last since wa want to know if the button is being animated (in the case of
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 0608ca2..c748ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -116,8 +116,16 @@
             return mBiometricView.findViewById(R.id.button_negative);
         }
 
-        public Button getPositiveButton() {
-            return mBiometricView.findViewById(R.id.button_positive);
+        public Button getCancelButton() {
+            return mBiometricView.findViewById(R.id.button_cancel);
+        }
+
+        public Button getUseCredentialButton() {
+            return mBiometricView.findViewById(R.id.button_use_credential);
+        }
+
+        public Button getConfirmButton() {
+            return mBiometricView.findViewById(R.id.button_confirm);
         }
 
         public Button getTryAgainButton() {
@@ -176,8 +184,16 @@
     private View mIconHolderView;
     protected ImageView mIconView;
     @VisibleForTesting protected TextView mIndicatorView;
+
+    // Negative button position, exclusively for the app-specified behavior
     @VisibleForTesting Button mNegativeButton;
-    @VisibleForTesting Button mPositiveButton;
+    // Negative button position, exclusively for cancelling auth after passive auth success
+    @VisibleForTesting Button mCancelButton;
+    // Negative button position, shown if device credentials are allowed
+    @VisibleForTesting Button mUseCredentialButton;
+
+    // Positive button position,
+    @VisibleForTesting Button mConfirmButton;
     @VisibleForTesting Button mTryAgainButton;
 
     // Measurements when biometric view is showing text, buttons, etc.
@@ -303,6 +319,7 @@
             mDescriptionView.setVisibility(View.GONE);
             mIndicatorView.setVisibility(View.GONE);
             mNegativeButton.setVisibility(View.GONE);
+            mUseCredentialButton.setVisibility(View.GONE);
 
             final float iconPadding = getResources()
                     .getDimension(R.dimen.biometric_dialog_icon_padding);
@@ -336,6 +353,7 @@
                 mTitleView.setAlpha(opacity);
                 mIndicatorView.setAlpha(opacity);
                 mNegativeButton.setAlpha(opacity);
+                mCancelButton.setAlpha(opacity);
                 mTryAgainButton.setAlpha(opacity);
 
                 if (!TextUtils.isEmpty(mSubtitleView.getText())) {
@@ -355,7 +373,12 @@
                     super.onAnimationStart(animation);
                     mTitleView.setVisibility(View.VISIBLE);
                     mIndicatorView.setVisibility(View.VISIBLE);
-                    mNegativeButton.setVisibility(View.VISIBLE);
+
+                    if (isDeviceCredentialAllowed()) {
+                        mUseCredentialButton.setVisibility(View.VISIBLE);
+                    } else {
+                        mNegativeButton.setVisibility(View.VISIBLE);
+                    }
                     mTryAgainButton.setVisibility(View.VISIBLE);
 
                     if (!TextUtils.isEmpty(mSubtitleView.getText())) {
@@ -447,15 +470,17 @@
             case STATE_AUTHENTICATING:
                 removePendingAnimations();
                 if (mRequireConfirmation) {
-                    mPositiveButton.setEnabled(false);
-                    mPositiveButton.setVisibility(View.VISIBLE);
+                    mConfirmButton.setEnabled(false);
+                    mConfirmButton.setVisibility(View.VISIBLE);
                 }
                 break;
 
             case STATE_AUTHENTICATED:
                 if (mSize != AuthDialog.SIZE_SMALL) {
-                    mPositiveButton.setVisibility(View.GONE);
+                    mConfirmButton.setVisibility(View.GONE);
                     mNegativeButton.setVisibility(View.GONE);
+                    mUseCredentialButton.setVisibility(View.GONE);
+                    mCancelButton.setVisibility(View.GONE);
                     mIndicatorView.setVisibility(View.INVISIBLE);
                 }
                 announceForAccessibility(getResources()
@@ -466,10 +491,11 @@
 
             case STATE_PENDING_CONFIRMATION:
                 removePendingAnimations();
-                mNegativeButton.setText(R.string.cancel);
-                mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
-                mPositiveButton.setEnabled(true);
-                mPositiveButton.setVisibility(View.VISIBLE);
+                mNegativeButton.setVisibility(View.GONE);
+                mCancelButton.setVisibility(View.VISIBLE);
+                mUseCredentialButton.setVisibility(View.GONE);
+                mConfirmButton.setEnabled(true);
+                mConfirmButton.setVisibility(View.VISIBLE);
                 mIndicatorView.setTextColor(mTextColorHint);
                 mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
                 mIndicatorView.setVisibility(View.VISIBLE);
@@ -595,23 +621,29 @@
         mIconView = mInjector.getIconView();
         mIconHolderView = mInjector.getIconHolderView();
         mIndicatorView = mInjector.getIndicatorView();
+
+        // Negative-side (left) buttons
         mNegativeButton = mInjector.getNegativeButton();
-        mPositiveButton = mInjector.getPositiveButton();
+        mCancelButton = mInjector.getCancelButton();
+        mUseCredentialButton = mInjector.getUseCredentialButton();
+
+        // Positive-side (right) buttons
+        mConfirmButton = mInjector.getConfirmButton();
         mTryAgainButton = mInjector.getTryAgainButton();
 
         mNegativeButton.setOnClickListener((view) -> {
-            if (mState == STATE_PENDING_CONFIRMATION) {
-                mCallback.onAction(Callback.ACTION_USER_CANCELED);
-            } else {
-                if (isDeviceCredentialAllowed()) {
-                    startTransitionToCredentialUI();
-                } else {
-                    mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
-                }
-            }
+            mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
         });
 
-        mPositiveButton.setOnClickListener((view) -> {
+        mCancelButton.setOnClickListener((view) -> {
+            mCallback.onAction(Callback.ACTION_USER_CANCELED);
+        });
+
+        mUseCredentialButton.setOnClickListener((view) -> {
+            startTransitionToCredentialUI();
+        });
+
+        mConfirmButton.setOnClickListener((view) -> {
             updateState(STATE_AUTHENTICATED);
         });
 
@@ -645,31 +677,36 @@
     void onAttachedToWindowInternal() {
         setText(mTitleView, mPromptInfo.getTitle());
 
-        final CharSequence negativeText;
         if (isDeviceCredentialAllowed()) {
-
+            final CharSequence credentialButtonText;
             final @Utils.CredentialType int credentialType =
                     Utils.getCredentialType(mContext, mEffectiveUserId);
-
             switch (credentialType) {
                 case Utils.CREDENTIAL_PIN:
-                    negativeText = getResources().getString(R.string.biometric_dialog_use_pin);
+                    credentialButtonText =
+                            getResources().getString(R.string.biometric_dialog_use_pin);
                     break;
                 case Utils.CREDENTIAL_PATTERN:
-                    negativeText = getResources().getString(R.string.biometric_dialog_use_pattern);
+                    credentialButtonText =
+                            getResources().getString(R.string.biometric_dialog_use_pattern);
                     break;
                 case Utils.CREDENTIAL_PASSWORD:
-                    negativeText = getResources().getString(R.string.biometric_dialog_use_password);
+                    credentialButtonText =
+                            getResources().getString(R.string.biometric_dialog_use_password);
                     break;
                 default:
-                    negativeText = getResources().getString(R.string.biometric_dialog_use_password);
+                    credentialButtonText =
+                            getResources().getString(R.string.biometric_dialog_use_password);
                     break;
             }
 
+            mNegativeButton.setVisibility(View.GONE);
+
+            mUseCredentialButton.setText(credentialButtonText);
+            mUseCredentialButton.setVisibility(View.VISIBLE);
         } else {
-            negativeText = mPromptInfo.getNegativeButtonText();
+            setText(mNegativeButton, mPromptInfo.getNegativeButtonText());
         }
-        setText(mNegativeButton, negativeText);
 
         setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
index b907cdb..043bd5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
@@ -50,8 +50,12 @@
     private TestableFaceView mFaceView;
 
     @Mock private Button mNegativeButton;
-    @Mock private Button mPositiveButton;
+    @Mock private Button mCancelButton;
+    @Mock private Button mUseCredentialButton;
+
+    @Mock private Button mConfirmButton;
     @Mock private Button mTryAgainButton;
+
     @Mock private TextView mErrorView;
 
     @Before
@@ -60,9 +64,14 @@
         mFaceView = new TestableFaceView(mContext);
         mFaceView.mIconController = mock(TestableFaceView.TestableIconController.class);
         mFaceView.setCallback(mCallback);
+
         mFaceView.mNegativeButton = mNegativeButton;
-        mFaceView.mPositiveButton = mPositiveButton;
+        mFaceView.mCancelButton = mCancelButton;
+        mFaceView.mUseCredentialButton = mUseCredentialButton;
+
+        mFaceView.mConfirmButton = mConfirmButton;
         mFaceView.mTryAgainButton = mTryAgainButton;
+
         mFaceView.mIndicatorView = mErrorView;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index e2517f2..49282ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -60,8 +60,12 @@
     @Mock private AuthPanelController mPanelController;
 
     @Mock private Button mNegativeButton;
+    @Mock private Button mCancelButton;
+    @Mock private Button mUseCredentialButton;
+
     @Mock private Button mPositiveButton;
     @Mock private Button mTryAgainButton;
+
     @Mock private TextView mTitleView;
     @Mock private TextView mSubtitleView;
     @Mock private TextView mDescriptionView;
@@ -89,15 +93,31 @@
 
     @Test
     public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
-        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
+        final Button negativeButton = new Button(mContext);
+        final Button cancelButton = new Button(mContext);
+        initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
+            @Override
+            public Button getNegativeButton() {
+                return negativeButton;
+            }
+
+            @Override
+            public Button getCancelButton() {
+                return cancelButton;
+            }
+        });
 
         mBiometricView.setRequireConfirmation(true);
         mBiometricView.onAuthenticationSucceeded();
         waitForIdleSync();
         assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
         verify(mCallback, never()).onAction(anyInt());
-        verify(mBiometricView.mNegativeButton).setText(eq(R.string.cancel));
-        verify(mBiometricView.mPositiveButton).setEnabled(eq(true));
+
+        assertEquals(View.GONE, negativeButton.getVisibility());
+        assertEquals(View.VISIBLE, cancelButton.getVisibility());
+        assertTrue(cancelButton.isEnabled());
+
+        verify(mBiometricView.mConfirmButton).setEnabled(eq(true));
         verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
         verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
     }
@@ -107,7 +127,7 @@
         Button button = new Button(mContext);
         initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
            @Override
-            public Button getPositiveButton() {
+            public Button getConfirmButton() {
                return button;
            }
         });
@@ -137,18 +157,26 @@
     }
 
     @Test
-    public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() {
-        Button button = new Button(mContext);
+    public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
+        Button cancelButton = new Button(mContext);
+        Button negativeButton = new Button(mContext);
         initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() {
             @Override
             public Button getNegativeButton() {
-                return button;
+                return negativeButton;
+            }
+            @Override
+            public Button getCancelButton() {
+                return cancelButton;
             }
         });
 
         mBiometricView.setRequireConfirmation(true);
         mBiometricView.onAuthenticationSucceeded();
-        button.performClick();
+
+        assertEquals(View.GONE, negativeButton.getVisibility());
+
+        cancelButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -282,16 +310,23 @@
     }
 
     @Test
-    public void testNegativeButton_whenDeviceCredentialAllowed() {
-        Button negativeButton = new Button(mContext);
+    public void testCredentialButton_whenDeviceCredentialAllowed() {
+        final Button negativeButton = new Button(mContext);
+        final Button useCredentialButton = new Button(mContext);
         initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() {
             @Override
             public Button getNegativeButton() {
                 return negativeButton;
             }
+
+            @Override
+            public Button getUseCredentialButton() {
+                return useCredentialButton;
+            }
         });
 
-        negativeButton.performClick();
+        assertEquals(View.GONE, negativeButton.getVisibility());
+        useCredentialButton.performClick();
         waitForIdleSync();
 
         verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -361,7 +396,17 @@
         }
 
         @Override
-        public Button getPositiveButton() {
+        public Button getCancelButton() {
+            return mCancelButton;
+        }
+
+        @Override
+        public Button getUseCredentialButton() {
+            return mUseCredentialButton;
+        }
+
+        @Override
+        public Button getConfirmButton() {
             return mPositiveButton;
         }