15/n: Allow Auth UI to start in credential UI
If the user is locked out of biometrics, and
BiometricPrompt#setDeviceCredentialAllowed(true), the user should be
shown the credential UI.
This change gives BiometricService the ability to request SystemUI
to show AuthCredentialView without first showing AuthBiometricView.
Bug: 140127687
Test: atest BiometricServiceTest
Test: atest com.android.systemui.biometrics
Change-Id: Ic26986ba044b7992641676c3d3b99fc1395a45b7
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e08707d..ecc1f69 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -754,6 +754,6 @@
}
private boolean isDeviceCredentialAllowed() {
- return mBiometricPromptBundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
+ return Utils.isDeviceCredentialAllowed(mBiometricPromptBundle);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 1a3189b..7c6cb08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -24,6 +24,7 @@
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Binder;
@@ -74,6 +75,7 @@
@interface ContainerState {}
final Config mConfig;
+ private final Injector mInjector;
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
private final AuthPanelController mPanelController;
@@ -82,11 +84,11 @@
private final CredentialCallback mCredentialCallback;
@VisibleForTesting final FrameLayout mFrameLayout;
- @VisibleForTesting AuthBiometricView mBiometricView;
- @VisibleForTesting AuthCredentialView mCredentialView;
+ @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
+ @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
private final ImageView mBackgroundView;
- private final ScrollView mBiometricScrollView;
+ @VisibleForTesting final ScrollView mBiometricScrollView;
private final View mPanelView;
private final float mTranslationY;
@@ -107,25 +109,11 @@
String mOpPackageName;
int mModalityMask;
boolean mSkipIntro;
- @Builder.InitialView int mInitialView;
}
public static class Builder {
Config mConfig;
- /**
- * Start the prompt with biometric UI. May flow to credential view if
- * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} is set to true.
- */
- public static final int INITIAL_VIEW_BIOMETRIC = 1;
- /**
- * Start the prompt with credential UI
- */
- public static final int INITIAL_VIEW_CREDENTIAL = 2;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({INITIAL_VIEW_BIOMETRIC, INITIAL_VIEW_CREDENTIAL})
- @interface InitialView {}
-
public Builder(Context context) {
mConfig = new Config();
mConfig.mContext = context;
@@ -161,14 +149,32 @@
return this;
}
- public Builder setInitialView(@InitialView int initialView) {
- mConfig.mInitialView = initialView;
- return this;
- }
-
public AuthContainerView build(int modalityMask) {
mConfig.mModalityMask = modalityMask;
- return new AuthContainerView(mConfig);
+ return new AuthContainerView(mConfig, new Injector());
+ }
+ }
+
+ public static class Injector {
+ ScrollView getBiometricScrollView(FrameLayout parent) {
+ return parent.findViewById(R.id.biometric_scrollview);
+ }
+
+ FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
+ return (FrameLayout) factory.inflate(
+ R.layout.auth_container_view, root, false /* attachToRoot */);
+ }
+
+ AuthPanelController getPanelController(Context context, View panelView) {
+ return new AuthPanelController(context, panelView);
+ }
+
+ ImageView getBackgroundView(FrameLayout parent) {
+ return parent.findViewById(R.id.background);
+ }
+
+ View getPanelView(FrameLayout parent) {
+ return parent.findViewById(R.id.panel);
}
}
@@ -194,7 +200,7 @@
break;
case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
mConfig.mCallback.onDeviceCredentialPressed();
- addCredentialView(false /* animatePanel */);
+ addCredentialView(false /* animatePanel */, true /* animateContents */);
break;
default:
Log.e(TAG, "Unhandled action: " + action);
@@ -210,10 +216,12 @@
}
@VisibleForTesting
- AuthContainerView(Config config) {
+ AuthContainerView(Config config, Injector injector) {
super(config.mContext);
mConfig = config;
+ mInjector = injector;
+
mWindowManager = mContext.getSystemService(WindowManager.class);
mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -224,29 +232,30 @@
mCredentialCallback = new CredentialCallback();
final LayoutInflater factory = LayoutInflater.from(mContext);
- mFrameLayout = (FrameLayout) factory.inflate(
- R.layout.auth_container_view, this, false /* attachToRoot */);
+ mFrameLayout = mInjector.inflateContainerView(factory, this);
- mPanelView = mFrameLayout.findViewById(R.id.panel);
- mPanelController = new AuthPanelController(mContext, mPanelView);
+ mPanelView = mInjector.getPanelView(mFrameLayout);
+ mPanelController = mInjector.getPanelController(mContext, mPanelView);
- // TODO: Update with new controllers if multi-modal authentication can occur simultaneously
- if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
- mBiometricView = (AuthBiometricFingerprintView)
- factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
- } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
- mBiometricView = (AuthBiometricFaceView)
- factory.inflate(R.layout.auth_biometric_face_view, null, false);
- } else {
- Log.e(TAG, "Unsupported modality mask: " + config.mModalityMask);
- mBiometricView = null;
- mBackgroundView = null;
- mBiometricScrollView = null;
- return;
+ // Inflate biometric view only if necessary.
+ if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
+ if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ mBiometricView = (AuthBiometricFingerprintView)
+ factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
+ } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
+ mBiometricView = (AuthBiometricFaceView)
+ factory.inflate(R.layout.auth_biometric_face_view, null, false);
+ } else {
+ Log.e(TAG, "Unsupported biometric modality: " + config.mModalityMask);
+ mBiometricView = null;
+ mBackgroundView = null;
+ mBiometricScrollView = null;
+ return;
+ }
}
- mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
- mBackgroundView = mFrameLayout.findViewById(R.id.background);
+ mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
+ mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
UserManager userManager = mContext.getSystemService(UserManager.class);
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
@@ -277,8 +286,7 @@
@Override
public boolean isAllowDeviceCredentials() {
- return mConfig.mBiometricPromptBundle
- .getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
+ return Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle);
}
private void addBiometricView() {
@@ -297,7 +305,7 @@
* it should own the panel expansion.
* @param animatePanel if the credential view needs to own the panel expansion animation
*/
- private void addCredentialView(boolean animatePanel) {
+ private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
mCredentialView = (AuthCredentialView) factory.inflate(
R.layout.auth_credential_view, null, false);
@@ -305,6 +313,7 @@
mCredentialView.setCallback(mCredentialCallback);
mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
mCredentialView.setPanelController(mPanelController, animatePanel);
+ mCredentialView.setShouldAnimateContents(animateContents);
mFrameLayout.addView(mCredentialView);
}
@@ -317,23 +326,22 @@
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ onAttachedToWindowInternal();
+ }
+
+ @VisibleForTesting
+ void onAttachedToWindowInternal() {
mWakefulnessLifecycle.addObserver(this);
- Log.v(TAG, "Initial view: " + mConfig.mInitialView);
-
- switch (mConfig.mInitialView) {
- case Builder.INITIAL_VIEW_BIOMETRIC:
- addBiometricView();
- break;
- case Builder.INITIAL_VIEW_CREDENTIAL:
- addCredentialView(true /* animatePanel */);
- break;
- default:
- Log.e(TAG, "Initial view not supported: " + mConfig.mInitialView);
- break;
+ if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
+ addBiometricView();
+ } else if (Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle)) {
+ addCredentialView(true /* animatePanel */, false /* animateContents */);
+ } else {
+ throw new IllegalStateException("Unknown configuration: "
+ + Utils.getAuthenticators(mConfig.mBiometricPromptBundle));
}
-
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
} else {
@@ -358,6 +366,15 @@
.setInterpolator(mLinearOutSlowIn)
.withLayer()
.start();
+ if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
+ mCredentialView.setY(mTranslationY);
+ mCredentialView.animate()
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ }
animate()
.alpha(1f)
.setDuration(ANIMATION_DURATION_SHOW_MS)
@@ -381,7 +398,9 @@
@Override
public void show(WindowManager wm, @Nullable Bundle savedState) {
- mBiometricView.restoreState(savedState);
+ if (mBiometricView != null) {
+ mBiometricView.restoreState(savedState);
+ }
wm.addView(this, getLayoutParams(mWindowToken));
}
@@ -427,7 +446,10 @@
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
mBiometricView != null && mCredentialView == null);
outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
- mBiometricView.onSaveState(outState);
+
+ if (mBiometricView != null) {
+ mBiometricView.onSaveState(outState);
+ }
}
@Override
@@ -525,7 +547,9 @@
return;
}
mContainerState = STATE_SHOWING;
- mBiometricView.onDialogAnimatedIn();
+ if (mBiometricView != null) {
+ mBiometricView.onDialogAnimatedIn();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 74e170d..4c2afb0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -200,16 +201,19 @@
}
@Override
- public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId, String opPackageName) {
+ public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+ int biometricModality, boolean requireConfirmation, int userId, String opPackageName) {
+ final int authenticators = Utils.getAuthenticators(bundle);
+
if (DEBUG) {
- Log.d(TAG, "showBiometricDialog, type: " + type
+ Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
+ + ", biometricModality: " + biometricModality
+ ", requireConfirmation: " + requireConfirmation);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
args.arg2 = receiver;
- args.argi1 = type;
+ args.argi1 = biometricModality;
args.arg3 = requireConfirmation;
args.argi2 = userId;
args.arg4 = opPackageName;
@@ -219,8 +223,8 @@
Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
skipAnimation = true;
}
- showDialog(args, skipAnimation, null /* savedState */,
- AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC);
+
+ showDialog(args, skipAnimation, null /* savedState */);
}
@Override
@@ -256,14 +260,13 @@
}
@Override
- public void hideBiometricDialog() {
- if (DEBUG) Log.d(TAG, "hideBiometricDialog");
+ public void hideAuthenticationDialog() {
+ if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
mCurrentDialog.dismissFromSystemServer();
}
- private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
- @AuthContainerView.Builder.InitialView int initialView) {
+ private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
final int type = args.argi1;
final Bundle biometricPromptBundle = (Bundle) args.arg1;
@@ -278,8 +281,7 @@
userId,
type,
opPackageName,
- skipAnimation,
- initialView);
+ skipAnimation);
if (newDialog == null) {
Log.e(TAG, "Unsupported type: " + type);
@@ -287,12 +289,11 @@
}
if (DEBUG) {
- Log.d(TAG, "showDialog, "
+ Log.d(TAG, "showDialog: " + args
+ " savedState: " + savedState
+ " mCurrentDialog: " + mCurrentDialog
+ " newDialog: " + newDialog
- + " type: " + type
- + " initialView: " + initialView);
+ + " type: " + type);
}
if (mCurrentDialog != null) {
@@ -334,21 +335,20 @@
!= AuthContainerView.STATE_ANIMATING_OUT) {
final boolean credentialShowing =
savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
+ if (credentialShowing) {
+ // TODO: Clean this up
+ Bundle bundle = (Bundle) mCurrentDialogArgs.arg1;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
+ Authenticator.TYPE_CREDENTIAL);
+ }
- // We can assume if credential is showing, then biometric doesn't need to be shown,
- // since credential is always after biometric.
- final int initialView = credentialShowing
- ? AuthContainerView.Builder.INITIAL_VIEW_CREDENTIAL
- : AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC;
-
- showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, initialView);
+ showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
}
}
}
protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
- int userId, int type, String opPackageName, boolean skipIntro,
- @AuthContainerView.Builder.InitialView int initialView) {
+ int userId, int type, String opPackageName, boolean skipIntro) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setBiometricPromptBundle(biometricPromptBundle)
@@ -356,7 +356,6 @@
.setUserId(userId)
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
- .setInitialView(initialView)
.build(type);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 8eac8f5..1ba88c6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -59,6 +59,7 @@
private Bundle mBiometricPromptBundle;
private AuthPanelController mPanelController;
private boolean mShouldAnimatePanel;
+ private boolean mShouldAnimateContents;
private TextView mTitleView;
private TextView mSubtitleView;
@@ -220,6 +221,10 @@
mShouldAnimatePanel = animatePanel;
}
+ void setShouldAnimateContents(boolean animateContents) {
+ mShouldAnimateContents = animateContents;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -230,18 +235,21 @@
setTextOrHide(mDescriptionView,
mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
- setTranslationY(getResources()
- .getDimension(R.dimen.biometric_dialog_credential_translation_offset));
- setAlpha(0);
+ // Only animate this if we're transitioning from a biometric view.
+ if (mShouldAnimateContents) {
+ setTranslationY(getResources()
+ .getDimension(R.dimen.biometric_dialog_credential_translation_offset));
+ setAlpha(0);
- postOnAnimation(() -> {
- animate().translationY(0)
- .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS)
- .alpha(1.f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .withLayer()
- .start();
- });
+ postOnAnimation(() -> {
+ animate().translationY(0)
+ .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS)
+ .alpha(1.f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withLayer()
+ .start();
+ });
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index e00cf6a..485e667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -19,6 +19,9 @@
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
import android.content.Context;
+import android.hardware.biometrics.Authenticator;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
@@ -46,4 +49,18 @@
view.sendAccessibilityEventUnchecked(event);
view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
}
+
+ static boolean isDeviceCredentialAllowed(Bundle biometricPromptBundle) {
+ final int authenticators = getAuthenticators(biometricPromptBundle);
+ return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+ }
+
+ static boolean isBiometricAllowed(Bundle biometricPromptBundle) {
+ final int authenticators = getAuthenticators(biometricPromptBundle);
+ return (authenticators & Authenticator.TYPE_BIOMETRIC) != 0;
+ }
+
+ static int getAuthenticators(Bundle biometricPromptBundle) {
+ return biometricPromptBundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 39b65c4..36e04fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -270,12 +270,13 @@
default void onRotationProposal(int rotation, boolean isValid) { }
- default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId, String opPackageName) { }
+ default void showAuthenticationDialog(Bundle bundle,
+ IBiometricServiceReceiverInternal receiver, int biometricModality,
+ boolean requireConfirmation, int userId, String opPackageName) { }
default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(int errorCode, String error) { }
- default void hideBiometricDialog() { }
+ default void hideAuthenticationDialog() { }
/**
* @see IStatusBar#onDisplayReady(int)
@@ -740,13 +741,13 @@
}
@Override
- public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
- int type, boolean requireConfirmation, int userId, String opPackageName) {
+ public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+ int biometricModality, boolean requireConfirmation, int userId, String opPackageName) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
args.arg2 = receiver;
- args.argi1 = type;
+ args.argi1 = biometricModality;
args.arg3 = requireConfirmation;
args.argi2 = userId;
args.arg4 = opPackageName;
@@ -780,7 +781,7 @@
}
@Override
- public void hideBiometricDialog() {
+ public void hideAuthenticationDialog() {
synchronized (mLock) {
mHandler.obtainMessage(MSG_BIOMETRIC_HIDE).sendToTarget();
}
@@ -1032,10 +1033,10 @@
mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showBiometricDialog(
+ mCallbacks.get(i).showAuthenticationDialog(
(Bundle) someArgs.arg1,
(IBiometricServiceReceiverInternal) someArgs.arg2,
- someArgs.argi1 /* type */,
+ someArgs.argi1 /* biometricModality */,
(boolean) someArgs.arg3 /* requireConfirmation */,
someArgs.argi2 /* userId */,
(String) someArgs.arg4 /* opPackageName */);
@@ -1065,7 +1066,7 @@
break;
case MSG_BIOMETRIC_HIDE:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).hideBiometricDialog();
+ mCallbacks.get(i).hideAuthenticationDialog();
}
break;
case MSG_SHOW_CHARGING_ANIMATION:
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 d110940..2c85424 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -291,11 +292,13 @@
private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) {
Bundle bundle = new Bundle();
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
+ int authenticators = Authenticator.TYPE_BIOMETRIC;
if (allowDeviceCredential) {
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
+ authenticators |= Authenticator.TYPE_CREDENTIAL;
} else {
bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
}
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
return bundle;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index aeceba7..8550390 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -17,14 +17,29 @@
package com.android.systemui.biometrics;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ScrollView;
import com.android.systemui.SysuiTestCase;
@@ -46,16 +61,12 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
-
- AuthContainerView.Config config = new AuthContainerView.Config();
- config.mContext = mContext;
- config.mCallback = mCallback;
- config.mModalityMask |= BiometricAuthenticator.TYPE_FINGERPRINT;
- mAuthContainer = new TestableAuthContainer(config);
}
@Test
public void testActionAuthenticated_sendsDismissedAuthenticated() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_AUTHENTICATED);
verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED));
@@ -63,6 +74,8 @@
@Test
public void testActionUserCanceled_sendsDismissedUserCanceled() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USER_CANCELED);
verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED));
@@ -70,6 +83,8 @@
@Test
public void testActionButtonNegative_sendsDismissedButtonNegative() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE));
@@ -77,6 +92,8 @@
@Test
public void testActionTryAgain_sendsTryAgain() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
verify(mCallback).onTryAgainPressed();
@@ -84,6 +101,8 @@
@Test
public void testActionError_sendsDismissedError() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_ERROR);
verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR);
@@ -91,25 +110,68 @@
@Test
public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
+ initializeContainer(
+ Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
verify(mCallback).onDeviceCredentialPressed();
// Credential view is attached to the frame layout
waitForIdleSync();
- assertEquals(mAuthContainer.mFrameLayout, mAuthContainer.mCredentialView.getParent());
+ assertNotNull(mAuthContainer.mCredentialView);
+ verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView));
}
@Test
public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+ initializeContainer(
+ Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+
mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
mAuthContainer.animateToCredentialUI();
verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI();
}
+ @Test
+ public void testShowBiometricUI() {
+ initializeContainer(Authenticator.TYPE_BIOMETRIC);
+
+ assertNotEquals(null, mAuthContainer.mBiometricView);
+
+ mAuthContainer.onAttachedToWindowInternal();
+ verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView);
+ // Credential view is not added
+ verify(mAuthContainer.mFrameLayout, never()).addView(any());
+ }
+
+ @Test
+ public void testShowCredentialUI_doesNotInflateBiometricUI() {
+ initializeContainer(Authenticator.TYPE_CREDENTIAL);
+
+ mAuthContainer.onAttachedToWindowInternal();
+
+ assertNull(null, mAuthContainer.mBiometricView);
+ assertNotNull(mAuthContainer.mCredentialView);
+ verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView);
+ }
+
+ private void initializeContainer(int authenticators) {
+ AuthContainerView.Config config = new AuthContainerView.Config();
+ config.mContext = mContext;
+ config.mCallback = mCallback;
+ config.mModalityMask |= BiometricAuthenticator.TYPE_FINGERPRINT;
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ config.mBiometricPromptBundle = bundle;
+
+ mAuthContainer = new TestableAuthContainer(config);
+ }
+
private class TestableAuthContainer extends AuthContainerView {
TestableAuthContainer(AuthContainerView.Config config) {
- super(config);
+ super(config, new MockInjector());
}
@Override
@@ -117,4 +179,31 @@
mConfig.mCallback.onDismissed(reason);
}
}
+
+ private final class MockInjector extends AuthContainerView.Injector {
+ @Override
+ public ScrollView getBiometricScrollView(FrameLayout parent) {
+ return mock(ScrollView.class);
+ }
+
+ @Override
+ public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
+ return mock(FrameLayout.class);
+ }
+
+ @Override
+ public AuthPanelController getPanelController(Context context, View view) {
+ return mock(AuthPanelController.class);
+ }
+
+ @Override
+ public ImageView getBackgroundView(FrameLayout parent) {
+ return mock(ImageView.class);
+ }
+
+ @Override
+ public View getPanelView(FrameLayout parent) {
+ return mock(View.class);
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b8f735e..dcdb5c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -35,6 +36,7 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -72,7 +74,7 @@
@Mock
private AuthDialog mDialog2;
- private TestableBiometricDialogImpl mBiometricDialogImpl;
+ private TestableAuthController mAuthController;
@Before
@@ -93,63 +95,66 @@
when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
- mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector());
- mBiometricDialogImpl.mContext = context;
- mBiometricDialogImpl.mComponents = mContext.getComponents();
+ when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
+ when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
- mBiometricDialogImpl.start();
+ mAuthController = new TestableAuthController(new MockInjector());
+ mAuthController.mContext = context;
+ mAuthController.mComponents = mContext.getComponents();
+
+ mAuthController.start();
}
// Callback tests
@Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Test
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
}
@Test
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
}
@Test
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
}
@Test
public void testSendsReasonError_whenDismissedByError() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
}
@Test
public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
}
@Test
public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
}
@@ -158,22 +163,22 @@
@Test
public void testShowInvoked_whenSystemRequested()
throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
}
@Test
- public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */);
+ public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.onBiometricAuthenticated(true, null /* failureReason */);
verify(mDialog1).onAuthenticationSucceeded();
}
@Test
- public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testOnAuthenticationFailedInvoked_whenSystemRequested() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final String failureReason = "failure reason";
- mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason);
+ mAuthController.onBiometricAuthenticated(false, failureReason);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
@@ -182,10 +187,10 @@
}
@Test
- public void testOnHelpInvoked_whenSystemRequested() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testOnHelpInvoked_whenSystemRequested() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final String helpMessage = "help";
- mBiometricDialogImpl.onBiometricHelp(helpMessage);
+ mAuthController.onBiometricHelp(helpMessage);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onHelp(captor.capture());
@@ -194,11 +199,11 @@
}
@Test
- public void testOnErrorInvoked_whenSystemRequested() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testOnErrorInvoked_whenSystemRequested() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = 1;
final String errMessage = "error message";
- mBiometricDialogImpl.onBiometricError(error, errMessage);
+ mAuthController.onBiometricError(error, errMessage);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(captor.capture());
@@ -207,83 +212,82 @@
}
@Test
- public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final String errorString = "lockout";
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
- mBiometricDialogImpl.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(error, errorString);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@Test
- public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI()
- throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final String errorString = "lockout_permanent";
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
- mBiometricDialogImpl.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(error, errorString);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1).animateToCredentialUI();
}
@Test
- public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final String errorString = "lockout";
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
- mBiometricDialogImpl.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(error, errorString);
verify(mDialog1).onError(eq(errorString));
verify(mDialog1, never()).animateToCredentialUI();
}
@Test
- public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final String errorString = "lockout_permanent";
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
- mBiometricDialogImpl.onBiometricError(error, errorString);
+ mAuthController.onBiometricError(error, errorString);
verify(mDialog1).onError(eq(errorString));
verify(mDialog1, never()).animateToCredentialUI();
}
@Test
- public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.hideBiometricDialog();
+ public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.hideAuthenticationDialog();
verify(mDialog1).dismissFromSystemServer();
}
@Test
- public void testClientNotified_whenDismissedBySystemServer() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.hideBiometricDialog();
+ public void testClientNotified_whenDismissedBySystemServer() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ mAuthController.hideAuthenticationDialog();
verify(mDialog1).dismissFromSystemServer();
- assertNotNull(mBiometricDialogImpl.mCurrentDialog);
- assertNotNull(mBiometricDialogImpl.mReceiver);
+ assertNotNull(mAuthController.mCurrentDialog);
+ assertNotNull(mAuthController.mReceiver);
}
// Corner case tests
@Test
- public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
- showDialog(BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
// First dialog should be dismissed without animation
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
@@ -293,11 +297,20 @@
}
@Test
- public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ public void testConfigurationPersists_whenOnConfigurationChanged() {
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
- mBiometricDialogImpl.onConfigurationChanged(new Configuration());
+ // Return that the UI is in "showing" state
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ Bundle savedState = (Bundle) args[0];
+ savedState.putInt(
+ AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ return null; // onSaveState returns void
+ }).when(mDialog1).onSaveState(any());
+
+ mAuthController.onConfigurationChanged(new Configuration());
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(mDialog1).onSaveState(captor.capture());
@@ -314,37 +327,63 @@
}
@Test
+ public void testConfigurationPersists_whenBiometricFallbackToCredential() {
+ showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC,
+ BiometricPrompt.TYPE_FACE);
+ verify(mDialog1).show(any(), any());
+
+ // Pretend that the UI is now showing device credential UI.
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ Bundle savedState = (Bundle) args[0];
+ savedState.putInt(
+ AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING);
+ savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
+ return null; // onSaveState returns void
+ }).when(mDialog1).onSaveState(any());
+
+ mAuthController.onConfigurationChanged(new Configuration());
+
+ // Check that the new dialog was initialized to the credential UI.
+ ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mDialog2).show(any(), captor.capture());
+ assertEquals(Authenticator.TYPE_CREDENTIAL,
+ mAuthController.mLastBiometricPromptBundle
+ .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ }
+
+ @Test
public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
- showDialog(BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
taskInfo.topActivity = mock(ComponentName.class);
when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
tasks.add(taskInfo);
- when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+ when(mAuthController.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
- mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged();
+ mAuthController.mTaskStackListener.onTaskStackChanged();
waitForIdleSync();
- assertNull(mBiometricDialogImpl.mCurrentDialog);
- assertNull(mBiometricDialogImpl.mReceiver);
+ assertNull(mAuthController.mCurrentDialog);
+ assertNull(mAuthController.mReceiver);
verify(mDialog1).dismissWithoutCallback(true /* animate */);
verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
}
// Helpers
- private void showDialog(int type) {
- mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(),
+ private void showDialog(int authenticators, int biometricModality) {
+ mAuthController.showAuthenticationDialog(createTestDialogBundle(authenticators),
mReceiver /* receiver */,
- type,
+ biometricModality,
true /* requireConfirmation */,
0 /* userId */,
"testPackage");
}
- private Bundle createTestDialogBundle() {
+ private Bundle createTestDialogBundle(int authenticators) {
Bundle bundle = new Bundle();
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
@@ -356,20 +395,26 @@
// by user settings, and should be tested in BiometricService.
bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+
return bundle;
}
- private final class TestableBiometricDialogImpl extends AuthController {
+ private final class TestableAuthController extends AuthController {
private int mBuildCount = 0;
+ private Bundle mLastBiometricPromptBundle;
- public TestableBiometricDialogImpl(Injector injector) {
+ public TestableAuthController(Injector injector) {
super(injector);
}
@Override
protected AuthDialog buildDialog(Bundle biometricPromptBundle,
boolean requireConfirmation, int userId, int type, String opPackageName,
- boolean skipIntro, @AuthContainerView.Builder.InitialView int initialView) {
+ boolean skipIntro) {
+
+ mLastBiometricPromptBundle = biometricPromptBundle;
+
AuthDialog dialog;
if (mBuildCount == 0) {
dialog = mDialog1;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index fcd0e23..1bd01e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -367,12 +367,13 @@
}
@Test
- public void testShowBiometricDialog() {
+ public void testShowAuthenticationDialog() {
Bundle bundle = new Bundle();
String packageName = "test";
- mCommandQueue.showBiometricDialog(bundle, null /* receiver */, 1, true, 3, packageName);
+ mCommandQueue.showAuthenticationDialog(bundle, null /* receiver */, 1, true, 3,
+ packageName);
waitForIdleSync();
- verify(mCallbacks).showBiometricDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3),
+ verify(mCallbacks).showAuthenticationDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3),
eq(packageName));
}
@@ -402,9 +403,9 @@
}
@Test
- public void testHideBiometricDialog() {
- mCommandQueue.hideBiometricDialog();
+ public void testHideAuthenticationDialog() {
+ mCommandQueue.hideAuthenticationDialog();
waitForIdleSync();
- verify(mCallbacks).hideBiometricDialog();
+ verify(mCallbacks).hideAuthenticationDialog();
}
}