14/n: Animate to device credential UI when lockout occurs
Test: manual test with BiometricPromptDemo
Test: atest com.android.systemui.biometrics
Test: atest BiometricServiceTest
Change-Id: I83547c28f1b164dc772b01861430f0d479e47a75
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 18d2747..e08707d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -584,11 +584,7 @@
mCallback.onAction(Callback.ACTION_USER_CANCELED);
} else {
if (isDeviceCredentialAllowed()) {
- updateSize(AuthDialog.SIZE_LARGE);
- mHandler.postDelayed(() -> {
- mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
- }, mInjector.getAnimateCredentialStartDelayMs());
-
+ startTransitionToCredentialUI();
} else {
mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
}
@@ -607,6 +603,16 @@
});
}
+ /**
+ * Kicks off the animation process and invokes the callback.
+ */
+ void startTransitionToCredentialUI() {
+ updateSize(AuthDialog.SIZE_LARGE);
+ mHandler.postDelayed(() -> {
+ mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
+ }, mInjector.getAnimateCredentialStartDelayMs());
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index a0ce67d..1a3189b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,7 +82,7 @@
private final CredentialCallback mCredentialCallback;
@VisibleForTesting final FrameLayout mFrameLayout;
- private final AuthBiometricView mBiometricView;
+ @VisibleForTesting AuthBiometricView mBiometricView;
@VisibleForTesting AuthCredentialView mCredentialView;
private final ImageView mBackgroundView;
@@ -275,6 +275,12 @@
requestFocus();
}
+ @Override
+ public boolean isAllowDeviceCredentials() {
+ return mConfig.mBiometricPromptBundle
+ .getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
+ }
+
private void addBiometricView() {
mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
mBiometricView.setPanelController(mPanelController);
@@ -429,6 +435,11 @@
return mConfig.mOpPackageName;
}
+ @Override
+ public void animateToCredentialUI() {
+ mBiometricView.startTransitionToCredentialUI();
+ }
+
@VisibleForTesting
void animateAway(int reason) {
animateAway(true /* sendReason */, reason);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 1e8c26d..74e170d 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.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
@@ -242,9 +243,16 @@
}
@Override
- public void onBiometricError(String error) {
- if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
- mCurrentDialog.onError(error);
+ public void onBiometricError(int errorCode, String error) {
+ if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
+
+ final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
+ mCurrentDialog.animateToCredentialUI();
+ } else {
+ mCurrentDialog.onError(error);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 461f237..ca95f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -124,4 +124,14 @@
* Get the client's package name
*/
String getOpPackageName();
+
+ /**
+ * Animate to credential UI. Typically called after biometric is locked out.
+ */
+ void animateToCredentialUI();
+
+ /**
+ * @return true if device credential is allowed.
+ */
+ boolean isAllowDeviceCredentials();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 134d4b8..39b65c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -274,7 +274,7 @@
int type, boolean requireConfirmation, int userId, String opPackageName) { }
default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
default void onBiometricHelp(String message) { }
- default void onBiometricError(String error) { }
+ default void onBiometricError(int errorCode, String error) { }
default void hideBiometricDialog() { }
/**
@@ -773,9 +773,9 @@
}
@Override
- public void onBiometricError(String error) {
+ public void onBiometricError(int errorCode, String error) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, error).sendToTarget();
+ mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget();
}
}
@@ -1060,7 +1060,7 @@
break;
case MSG_BIOMETRIC_ERROR:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricError((String) msg.obj);
+ mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj);
}
break;
case MSG_BIOMETRIC_HIDE:
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 838a931..aeceba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -18,8 +18,10 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -48,6 +50,7 @@
AuthContainerView.Config config = new AuthContainerView.Config();
config.mContext = mContext;
config.mCallback = mCallback;
+ config.mModalityMask |= BiometricAuthenticator.TYPE_FINGERPRINT;
mAuthContainer = new TestableAuthContainer(config);
}
@@ -97,6 +100,13 @@
assertEquals(mAuthContainer.mFrameLayout, mAuthContainer.mCredentialView.getParent());
}
+ @Test
+ public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+ mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
+ mAuthContainer.animateToCredentialUI();
+ verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI();
+ }
+
private class TestableAuthContainer extends AuthContainerView {
TestableAuthContainer(AuthContainerView.Config config) {
super(config);
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 360183d..b8f735e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -22,8 +22,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,6 +35,7 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
@@ -193,8 +196,9 @@
@Test
public void testOnErrorInvoked_whenSystemRequested() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
+ final int error = 1;
final String errMessage = "error message";
- mBiometricDialogImpl.onBiometricError(errMessage);
+ mBiometricDialogImpl.onBiometricError(error, errMessage);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(captor.capture());
@@ -203,6 +207,59 @@
}
@Test
+ public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
+ final String errorString = "lockout";
+
+ when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
+
+ mBiometricDialogImpl.onBiometricError(error, errorString);
+ verify(mDialog1, never()).onError(anyString());
+ verify(mDialog1).animateToCredentialUI();
+ }
+
+ @Test
+ public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI()
+ throws Exception {
+ showDialog(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);
+ verify(mDialog1, never()).onError(anyString());
+ verify(mDialog1).animateToCredentialUI();
+ }
+
+ @Test
+ public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() throws Exception {
+ showDialog(BiometricPrompt.TYPE_FACE);
+ final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
+ final String errorString = "lockout";
+
+ when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
+
+ mBiometricDialogImpl.onBiometricError(error, errorString);
+ verify(mDialog1).onError(eq(errorString));
+ verify(mDialog1, never()).animateToCredentialUI();
+ }
+
+ @Test
+ public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() throws Exception {
+ showDialog(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);
+ verify(mDialog1).onError(eq(errorString));
+ verify(mDialog1, never()).animateToCredentialUI();
+ }
+
+ @Test
public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
mBiometricDialogImpl.hideBiometricDialog();
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 b252a0d..fcd0e23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -394,10 +394,11 @@
@Test
public void testOnBiometricError() {
+ final int errorCode = 1;
String errorMessage = "test_error_message";
- mCommandQueue.onBiometricError(errorMessage);
+ mCommandQueue.onBiometricError(errorCode, errorMessage);
waitForIdleSync();
- verify(mCallbacks).onBiometricError(eq(errorMessage));
+ verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage));
}
@Test