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
This commit is contained in:
Kevin Chyn
2020-11-13 11:45:29 -08:00
parent bf80ecae16
commit cb03d79422
5 changed files with 160 additions and 48 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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);
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 @@ 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_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 @@ 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());

View File

@@ -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;
}

View File

@@ -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;
}