5/n: Restore biometric dialog state across configuration changes
Bug: 123378871
Test: manual test, rotating device during various stages of
authentication
Test: atest com.android.systemui.biometrics
Change-Id: I4130f79975f58e5141c9d69e2689680ceaa419ed
This commit is contained in:
@@ -56,7 +56,7 @@
|
||||
android:scaleType="fitXY" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="24dp"
|
||||
|
||||
@@ -154,18 +154,18 @@ public class AuthBiometricFaceView extends AuthBiometricView {
|
||||
|
||||
@Override
|
||||
protected void handleResetAfterError() {
|
||||
resetErrorView(mContext, mErrorView);
|
||||
resetErrorView(mContext, mIndicatorView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResetAfterHelp() {
|
||||
resetErrorView(mContext, mErrorView);
|
||||
resetErrorView(mContext, mIndicatorView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mIconController = new IconController(mContext, mIconView, mErrorView);
|
||||
mIconController = new IconController(mContext, mIconView, mIndicatorView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,7 +174,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
|
||||
|
||||
if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
|
||||
(newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
|
||||
resetErrorView(mContext, mErrorView);
|
||||
resetErrorView(mContext, mIndicatorView);
|
||||
}
|
||||
|
||||
// Do this last since the state variable gets updated.
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.os.Bundle;
|
||||
@@ -127,8 +129,8 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
return mBiometricView.findViewById(R.id.description);
|
||||
}
|
||||
|
||||
public TextView getErrorView() {
|
||||
return mBiometricView.findViewById(R.id.error);
|
||||
public TextView getIndicatorView() {
|
||||
return mBiometricView.findViewById(R.id.indicator);
|
||||
}
|
||||
|
||||
public ImageView getIconView() {
|
||||
@@ -150,7 +152,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
private TextView mSubtitleView;
|
||||
private TextView mDescriptionView;
|
||||
protected ImageView mIconView;
|
||||
@VisibleForTesting protected TextView mErrorView;
|
||||
@VisibleForTesting protected TextView mIndicatorView;
|
||||
@VisibleForTesting Button mNegativeButton;
|
||||
@VisibleForTesting Button mPositiveButton;
|
||||
@VisibleForTesting Button mTryAgainButton;
|
||||
@@ -165,6 +167,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
private float mIconOriginalY;
|
||||
|
||||
protected boolean mDialogSizeAnimating;
|
||||
protected Bundle mSavedState;
|
||||
|
||||
/**
|
||||
* Delay after authentication is confirmed, before the dialog should be animated away.
|
||||
@@ -252,7 +255,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
mTitleView.setVisibility(View.GONE);
|
||||
mSubtitleView.setVisibility(View.GONE);
|
||||
mDescriptionView.setVisibility(View.GONE);
|
||||
mErrorView.setVisibility(View.GONE);
|
||||
mIndicatorView.setVisibility(View.GONE);
|
||||
mNegativeButton.setVisibility(View.GONE);
|
||||
|
||||
final float iconPadding = getResources()
|
||||
@@ -284,7 +287,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
final float opacity = (float) animation.getAnimatedValue();
|
||||
|
||||
mTitleView.setAlpha(opacity);
|
||||
mErrorView.setAlpha(opacity);
|
||||
mIndicatorView.setAlpha(opacity);
|
||||
mNegativeButton.setAlpha(opacity);
|
||||
mTryAgainButton.setAlpha(opacity);
|
||||
|
||||
@@ -304,7 +307,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
mTitleView.setVisibility(View.VISIBLE);
|
||||
mErrorView.setVisibility(View.VISIBLE);
|
||||
mIndicatorView.setVisibility(View.VISIBLE);
|
||||
mNegativeButton.setVisibility(View.VISIBLE);
|
||||
mTryAgainButton.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -339,6 +342,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
|
||||
public void updateState(@BiometricState int newState) {
|
||||
Log.v(TAG, "newState: " + newState);
|
||||
|
||||
switch (newState) {
|
||||
case STATE_AUTHENTICATING_ANIMATING_IN:
|
||||
case STATE_AUTHENTICATING:
|
||||
@@ -353,7 +357,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
if (mSize != AuthDialog.SIZE_SMALL) {
|
||||
mPositiveButton.setVisibility(View.GONE);
|
||||
mNegativeButton.setVisibility(View.GONE);
|
||||
mErrorView.setVisibility(View.INVISIBLE);
|
||||
mIndicatorView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
|
||||
getDelayAfterAuthenticatedDurationMs());
|
||||
@@ -364,9 +368,10 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
mNegativeButton.setText(R.string.cancel);
|
||||
mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
|
||||
mPositiveButton.setEnabled(true);
|
||||
mErrorView.setTextColor(mTextColorHint);
|
||||
mErrorView.setText(R.string.biometric_dialog_tap_confirm);
|
||||
mErrorView.setVisibility(View.VISIBLE);
|
||||
mPositiveButton.setVisibility(View.VISIBLE);
|
||||
mIndicatorView.setTextColor(mTextColorHint);
|
||||
mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
|
||||
mIndicatorView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
|
||||
case STATE_ERROR:
|
||||
@@ -409,6 +414,27 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
updateState(STATE_HELP);
|
||||
}
|
||||
|
||||
public void onSaveState(@NonNull Bundle outState) {
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
|
||||
mTryAgainButton.getVisibility());
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
|
||||
outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
|
||||
mIndicatorView.getText().toString());
|
||||
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
|
||||
mHandler.hasCallbacks(mResetErrorRunnable));
|
||||
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
|
||||
mHandler.hasCallbacks(mResetHelpRunnable));
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE, mSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after inflation but before being attached to window.
|
||||
* @param savedState
|
||||
*/
|
||||
public void restoreState(@Nullable Bundle savedState) {
|
||||
mSavedState = savedState;
|
||||
}
|
||||
|
||||
private void setTextOrHide(TextView view, String string) {
|
||||
if (TextUtils.isEmpty(string)) {
|
||||
view.setVisibility(View.GONE);
|
||||
@@ -429,25 +455,28 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
|
||||
private void showTemporaryMessage(String message, Runnable resetMessageRunnable) {
|
||||
removePendingAnimations();
|
||||
mErrorView.setText(message);
|
||||
mErrorView.setTextColor(mTextColorError);
|
||||
mErrorView.setVisibility(View.VISIBLE);
|
||||
mIndicatorView.setText(message);
|
||||
mIndicatorView.setTextColor(mTextColorError);
|
||||
mIndicatorView.setVisibility(View.VISIBLE);
|
||||
mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
initializeViews();
|
||||
onFinishInflateInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* After inflation, but before things like restoreState, onAttachedToWindow, etc.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void initializeViews() {
|
||||
void onFinishInflateInternal() {
|
||||
mTitleView = mInjector.getTitleView();
|
||||
mSubtitleView = mInjector.getSubtitleView();
|
||||
mDescriptionView = mInjector.getDescriptionView();
|
||||
mIconView = mInjector.getIconView();
|
||||
mErrorView = mInjector.getErrorView();
|
||||
mIndicatorView = mInjector.getIndicatorView();
|
||||
mNegativeButton = mInjector.getNegativeButton();
|
||||
mPositiveButton = mInjector.getPositiveButton();
|
||||
mTryAgainButton = mInjector.getTryAgainButton();
|
||||
@@ -474,13 +503,49 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
onAttachedToWindowInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
|
||||
* invoked.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void onAttachedToWindowInternal() {
|
||||
setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE));
|
||||
setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
|
||||
|
||||
setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE));
|
||||
setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
|
||||
|
||||
updateState(STATE_AUTHENTICATING_ANIMATING_IN);
|
||||
if (mSavedState == null) {
|
||||
updateState(STATE_AUTHENTICATING_ANIMATING_IN);
|
||||
} else {
|
||||
// Restore as much state as possible first
|
||||
updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
|
||||
|
||||
// Restore positive button state
|
||||
mTryAgainButton.setVisibility(
|
||||
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
|
||||
|
||||
// Restore indicator text state
|
||||
final String indicatorText =
|
||||
mSavedState.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING);
|
||||
if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING)) {
|
||||
onHelp(indicatorText);
|
||||
} else if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)) {
|
||||
onAuthenticationFailed(indicatorText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
// Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
|
||||
// the new dialog is restored.
|
||||
mHandler.removeCallbacksAndMessages(null /* all */);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -521,13 +586,24 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
@Override
|
||||
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
onLayoutInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all the testable logic that should be invoked when
|
||||
* {@link #onLayout(boolean, int, int, int, int)}, is invoked.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void onLayoutInternal() {
|
||||
// Start with initial size only once. Subsequent layout changes don't matter since we
|
||||
// only care about the initial icon position.
|
||||
if (mIconOriginalY == 0) {
|
||||
mIconOriginalY = mIconView.getY();
|
||||
updateSize(mRequireConfirmation ? AuthDialog.SIZE_MEDIUM
|
||||
: AuthDialog.SIZE_SMALL);
|
||||
if (mSavedState == null) {
|
||||
updateSize(mRequireConfirmation ? AuthDialog.SIZE_MEDIUM : AuthDialog.SIZE_SMALL);
|
||||
} else {
|
||||
updateSize(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Binder;
|
||||
@@ -269,7 +271,8 @@ public class AuthContainerView extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(WindowManager wm) {
|
||||
public void show(WindowManager wm, @Nullable Bundle savedState) {
|
||||
mBiometricView.restoreState(savedState);
|
||||
wm.addView(this, getLayoutParams(mWindowToken));
|
||||
}
|
||||
|
||||
@@ -308,13 +311,8 @@ public class AuthContainerView extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveState(Bundle outState) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(Bundle savedState) {
|
||||
|
||||
public void onSaveState(@NonNull Bundle outState) {
|
||||
mBiometricView.onSaveState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -272,11 +272,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
|
||||
+ " type: " + type);
|
||||
}
|
||||
|
||||
if (savedState != null) {
|
||||
// SavedState is only non-null if it's from onConfigurationChanged. Restore the state
|
||||
// even though it may be removed / re-created again
|
||||
newDialog.restoreState(savedState);
|
||||
} else if (mCurrentDialog != null) {
|
||||
if (mCurrentDialog != null) {
|
||||
// If somehow we're asked to show a dialog, the old one doesn't need to be animated
|
||||
// away. This can happen if the app cancels and re-starts auth during configuration
|
||||
// change. This is ugly because we also have to do things on onConfigurationChanged
|
||||
@@ -286,7 +282,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
|
||||
|
||||
mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
|
||||
mCurrentDialog = newDialog;
|
||||
mCurrentDialog.show(mWindowManager);
|
||||
mCurrentDialog.show(mWindowManager, savedState);
|
||||
}
|
||||
|
||||
private void onDialogDismissed(@DismissedReason int reason) {
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@@ -28,28 +29,12 @@ import java.lang.annotation.RetentionPolicy;
|
||||
* Interface for the biometric dialog UI.
|
||||
*/
|
||||
public interface AuthDialog {
|
||||
|
||||
// TODO: Clean up save/restore state
|
||||
String[] KEYS_TO_BACKUP = {
|
||||
BiometricPrompt.KEY_TITLE,
|
||||
BiometricPrompt.KEY_USE_DEFAULT_TITLE,
|
||||
BiometricPrompt.KEY_SUBTITLE,
|
||||
BiometricPrompt.KEY_DESCRIPTION,
|
||||
BiometricPrompt.KEY_POSITIVE_TEXT,
|
||||
BiometricPrompt.KEY_NEGATIVE_TEXT,
|
||||
BiometricPrompt.KEY_REQUIRE_CONFIRMATION,
|
||||
BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL,
|
||||
BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL,
|
||||
|
||||
BiometricDialogView.KEY_TRY_AGAIN_VISIBILITY,
|
||||
BiometricDialogView.KEY_CONFIRM_VISIBILITY,
|
||||
BiometricDialogView.KEY_CONFIRM_ENABLED,
|
||||
BiometricDialogView.KEY_STATE,
|
||||
BiometricDialogView.KEY_ERROR_TEXT_VISIBILITY,
|
||||
BiometricDialogView.KEY_ERROR_TEXT_STRING,
|
||||
BiometricDialogView.KEY_ERROR_TEXT_IS_TEMPORARY,
|
||||
BiometricDialogView.KEY_ERROR_TEXT_COLOR,
|
||||
};
|
||||
String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
|
||||
String KEY_BIOMETRIC_STATE = "state";
|
||||
String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
|
||||
String KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING = "error_is_temporary";
|
||||
String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
|
||||
String KEY_BIOMETRIC_DIALOG_SIZE = "size";
|
||||
|
||||
int SIZE_UNKNOWN = 0;
|
||||
int SIZE_SMALL = 1;
|
||||
@@ -68,7 +53,7 @@ public interface AuthDialog {
|
||||
* Show the dialog.
|
||||
* @param wm
|
||||
*/
|
||||
void show(WindowManager wm);
|
||||
void show(WindowManager wm, @Nullable Bundle savedState);
|
||||
|
||||
/**
|
||||
* Dismiss the dialog without sending a callback.
|
||||
@@ -107,13 +92,7 @@ public interface AuthDialog {
|
||||
* Save the current state.
|
||||
* @param outState
|
||||
*/
|
||||
void onSaveState(Bundle outState);
|
||||
|
||||
/**
|
||||
* Restore a previous state.
|
||||
* @param savedState
|
||||
*/
|
||||
void restoreState(Bundle savedState);
|
||||
void onSaveState(@NonNull Bundle outState);
|
||||
|
||||
/**
|
||||
* Get the client's package name
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Outline;
|
||||
@@ -738,7 +739,10 @@ public abstract class BiometricDialogView extends LinearLayout implements AuthDi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(WindowManager wm) {
|
||||
public void show(WindowManager wm, @Nullable Bundle savedState) {
|
||||
if (savedState != null) {
|
||||
restoreState(savedState);
|
||||
}
|
||||
wm.addView(this, getLayoutParams(mWindowToken));
|
||||
}
|
||||
|
||||
@@ -832,7 +836,6 @@ public abstract class BiometricDialogView extends LinearLayout implements AuthDi
|
||||
bundle.putInt(KEY_DIALOG_SIZE, mSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(Bundle bundle) {
|
||||
mRestoredState = bundle;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class AuthBiometricFaceViewTest extends SysuiTestCase {
|
||||
mFaceView.mNegativeButton = mNegativeButton;
|
||||
mFaceView.mPositiveButton = mPositiveButton;
|
||||
mFaceView.mTryAgainButton = mTryAgainButton;
|
||||
mFaceView.mErrorView = mErrorView;
|
||||
mFaceView.mIndicatorView = mErrorView;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.os.Bundle;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
@@ -55,10 +58,10 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
@Mock private TextView mTitleView;
|
||||
@Mock private TextView mSubtitleView;
|
||||
@Mock private TextView mDescriptionView;
|
||||
@Mock private TextView mErrorView;
|
||||
@Mock private TextView mIndicatorView;
|
||||
@Mock private ImageView mIconView;
|
||||
|
||||
TestableBiometricView mBiometricView;
|
||||
private TestableBiometricView mBiometricView;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -87,8 +90,8 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
verify(mCallback, never()).onAction(anyInt());
|
||||
verify(mBiometricView.mNegativeButton).setText(eq(R.string.cancel));
|
||||
verify(mBiometricView.mPositiveButton).setEnabled(eq(true));
|
||||
verify(mErrorView).setText(eq(R.string.biometric_dialog_tap_confirm));
|
||||
verify(mErrorView).setVisibility(eq(View.VISIBLE));
|
||||
verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
|
||||
verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -193,11 +196,88 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestoresState() {
|
||||
final boolean requireConfirmation = true; // set/init from AuthController
|
||||
|
||||
Button tryAgainButton = new Button(mContext);
|
||||
TextView indicatorView = new TextView(mContext);
|
||||
initDialog(mContext, mCallback, new MockInjector() {
|
||||
@Override
|
||||
public Button getTryAgainButton() {
|
||||
return tryAgainButton;
|
||||
}
|
||||
@Override
|
||||
public TextView getIndicatorView() {
|
||||
return indicatorView;
|
||||
}
|
||||
});
|
||||
|
||||
final String failureMessage = "testFailureMessage";
|
||||
mBiometricView.setRequireConfirmation(requireConfirmation);
|
||||
mBiometricView.onAuthenticationFailed(failureMessage);
|
||||
waitForIdleSync();
|
||||
|
||||
Bundle state = new Bundle();
|
||||
mBiometricView.onSaveState(state);
|
||||
|
||||
assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
|
||||
assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
|
||||
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
|
||||
|
||||
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
|
||||
assertTrue(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING));
|
||||
|
||||
assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
|
||||
assertEquals(failureMessage, state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING));
|
||||
|
||||
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
|
||||
|
||||
// Create new dialog and restore the previous state into it
|
||||
Button tryAgainButton2 = new Button(mContext);
|
||||
TextView indicatorView2 = new TextView(mContext);
|
||||
initDialog(mContext, mCallback, state, new MockInjector() {
|
||||
@Override
|
||||
public Button getTryAgainButton() {
|
||||
return tryAgainButton2;
|
||||
}
|
||||
@Override
|
||||
public TextView getIndicatorView() {
|
||||
return indicatorView2;
|
||||
}
|
||||
});
|
||||
mBiometricView.setRequireConfirmation(requireConfirmation);
|
||||
waitForIdleSync();
|
||||
|
||||
// Test restored state
|
||||
assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
|
||||
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
|
||||
assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
|
||||
}
|
||||
|
||||
private Bundle buildBiometricPromptBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
|
||||
bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private void initDialog(Context context, AuthBiometricView.Callback callback,
|
||||
Bundle savedState, MockInjector injector) {
|
||||
mBiometricView = new TestableBiometricView(context, null, injector);
|
||||
mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle());
|
||||
mBiometricView.setCallback(callback);
|
||||
mBiometricView.restoreState(savedState);
|
||||
mBiometricView.onFinishInflateInternal();
|
||||
mBiometricView.onAttachedToWindowInternal();
|
||||
}
|
||||
|
||||
private void initDialog(Context context, AuthBiometricView.Callback callback,
|
||||
MockInjector injector) {
|
||||
mBiometricView = new TestableBiometricView(context, null, injector);
|
||||
mBiometricView.setCallback(callback);
|
||||
mBiometricView.initializeViews();
|
||||
initDialog(context, callback, null /* savedState */, injector);
|
||||
}
|
||||
|
||||
private class MockInjector extends AuthBiometricView.Injector {
|
||||
@@ -232,8 +312,8 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextView getErrorView() {
|
||||
return mErrorView;
|
||||
public TextView getIndicatorView() {
|
||||
return mIndicatorView;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -147,7 +147,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
public void testShowInvoked_whenSystemRequested()
|
||||
throws Exception {
|
||||
showDialog(BiometricPrompt.TYPE_FACE);
|
||||
verify(mDialog1).show(any());
|
||||
verify(mDialog1).show(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -215,7 +215,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
|
||||
showDialog(BiometricPrompt.TYPE_FACE);
|
||||
verify(mDialog1).show(any());
|
||||
verify(mDialog1).show(any(), any());
|
||||
|
||||
showDialog(BiometricPrompt.TYPE_FACE);
|
||||
|
||||
@@ -223,13 +223,13 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
|
||||
|
||||
// Second dialog should be shown without animation
|
||||
verify(mDialog2).show(any());
|
||||
verify(mDialog2).show(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
|
||||
showDialog(BiometricPrompt.TYPE_FACE);
|
||||
verify(mDialog1).show(any());
|
||||
verify(mDialog1).show(any(), any());
|
||||
|
||||
mBiometricDialogImpl.onConfigurationChanged(new Configuration());
|
||||
|
||||
@@ -241,10 +241,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
||||
|
||||
// Saved state is restored into new dialog
|
||||
ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
|
||||
verify(mDialog2).restoreState(captor2.capture());
|
||||
|
||||
// Dialog for new configuration skips intro
|
||||
verify(mDialog2).show(any());
|
||||
verify(mDialog2).show(any(), captor2.capture());
|
||||
|
||||
// TODO: This should check all values we want to save/restore
|
||||
assertEquals(captor.getValue(), captor2.getValue());
|
||||
|
||||
Reference in New Issue
Block a user