Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb03d794 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

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
parent bf80ecae
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -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"
+1 −1
Original line number Diff line number Diff line
@@ -196,7 +196,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
    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
+68 −31
Original line number Diff line number Diff line
@@ -116,8 +116,16 @@ public abstract class AuthBiometricView extends LinearLayout {
            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 @@ public abstract class AuthBiometricView extends LinearLayout {
    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 @@ public abstract class AuthBiometricView extends LinearLayout {
            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 @@ public abstract class AuthBiometricView extends LinearLayout {
                mTitleView.setAlpha(opacity);
                mIndicatorView.setAlpha(opacity);
                mNegativeButton.setAlpha(opacity);
                mCancelButton.setAlpha(opacity);
                mTryAgainButton.setAlpha(opacity);

                if (!TextUtils.isEmpty(mSubtitleView.getText())) {
@@ -355,7 +373,12 @@ public abstract class AuthBiometricView extends LinearLayout {
                    super.onAnimationStart(animation);
                    mTitleView.setVisibility(View.VISIBLE);
                    mIndicatorView.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 @@ public abstract class AuthBiometricView extends LinearLayout {
            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 @@ public abstract class AuthBiometricView extends LinearLayout {

            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 @@ public abstract class AuthBiometricView extends LinearLayout {
        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_BUTTON_NEGATIVE);
        });

        mCancelButton.setOnClickListener((view) -> {
            mCallback.onAction(Callback.ACTION_USER_CANCELED);
            } else {
                if (isDeviceCredentialAllowed()) {
        });

        mUseCredentialButton.setOnClickListener((view) -> {
            startTransitionToCredentialUI();
                } else {
                    mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
                }
            }
        });

        mPositiveButton.setOnClickListener((view) -> {
        mConfirmButton.setOnClickListener((view) -> {
            updateState(STATE_AUTHENTICATED);
        });

@@ -645,31 +677,36 @@ public abstract class AuthBiometricView extends LinearLayout {
    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());

+11 −2
Original line number Diff line number Diff line
@@ -50,8 +50,12 @@ public class AuthBiometricFaceViewTest extends SysuiTestCase {
    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 @@ public class AuthBiometricFaceViewTest extends SysuiTestCase {
        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;
    }

+57 −12
Original line number Diff line number Diff line
@@ -60,8 +60,12 @@ public class AuthBiometricViewTest extends SysuiTestCase {
    @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 @@ public class AuthBiometricViewTest extends SysuiTestCase {

    @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 @@ public class AuthBiometricViewTest extends SysuiTestCase {
        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 @@ public class AuthBiometricViewTest extends SysuiTestCase {
    }

    @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 @@ public class AuthBiometricViewTest extends SysuiTestCase {
    }

    @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 @@ public class AuthBiometricViewTest extends SysuiTestCase {
        }

        @Override
        public Button getPositiveButton() {
        public Button getCancelButton() {
            return mCancelButton;
        }

        @Override
        public Button getUseCredentialButton() {
            return mUseCredentialButton;
        }

        @Override
        public Button getConfirmButton() {
            return mPositiveButton;
        }