Fix feedback and animations for face to fingerprint fallback authentication.

Bug: 189164072
Test: atest com.android.systemui.biometrics com.android.server.biometrics
Change-Id: I7fe8085dc4a4994c591bb4be20309ffb46bb06e9
This commit is contained in:
Joe Bolinger
2021-06-03 09:24:00 -07:00
parent 10de71a380
commit 9f685a9fa7
17 changed files with 219 additions and 92 deletions

View File

@@ -67,7 +67,8 @@ public interface BiometricAuthenticator {
TYPE_NONE,
TYPE_CREDENTIAL,
TYPE_FINGERPRINT,
TYPE_IRIS
TYPE_IRIS,
TYPE_FACE
})
@Retention(RetentionPolicy.SOURCE)
@interface Modality {}

View File

@@ -143,17 +143,25 @@ oneway interface IStatusBar
void showShutdownUi(boolean isReboot, String reason);
// Used to show the authentication dialog (Biometrics, Device Credential)
/**
* Used to show the authentication dialog (Biometrics, Device Credential).
*/
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
String opPackageName, long operationId, int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to show an error - the dialog will dismiss after a certain amount of time
/**
* Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc.
*/
void onBiometricHelp(int modality, String message);
/** Used to show an error - the dialog will dismiss after a certain amount of time. */
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication
/**
* Used to hide the authentication dialog, e.g. when the application cancels authentication.
*/
void hideAuthenticationDialog();
/**

View File

@@ -115,7 +115,7 @@ interface IStatusBarService
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
void onBiometricHelp(int modality, String message);
// Used to show an error - the dialog will dismiss after a certain amount of time
void onBiometricError(int modality, int error, int vendorCode);
// Used to hide the authentication dialog, e.g. when the application cancels authentication

View File

@@ -22,7 +22,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
import android.util.Log;
@@ -87,9 +87,11 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
}
}
@BiometricAuthenticator.Modality private int mActiveSensorType = TYPE_FACE;
@Modality
private int mActiveSensorType = TYPE_FACE;
@Nullable UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
@Nullable
private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
public AuthBiometricFaceToFingerprintView(Context context) {
super(context);
@@ -125,6 +127,16 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
return false;
}
@Override
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
if (modality == TYPE_FACE && mActiveSensorType == TYPE_FACE) {
// switching from face -> fingerprint mode, suppress soft error messages
failureReason = mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead);
}
super.onAuthenticationFailed(modality, failureReason);
}
@Override
@BiometricState
protected int getStateForAfterError() {
@@ -155,7 +167,7 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
}
@Override
public void updateState(int newState) {
public void updateState(@BiometricState int newState) {
if (mState == STATE_HELP || mState == STATE_ERROR) {
mActiveSensorType = TYPE_FINGERPRINT;

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -29,6 +30,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -206,7 +209,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
}
@Override
public void onAuthenticationFailed(String failureReason) {
public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) {
if (getSize() == AuthDialog.SIZE_MEDIUM) {
if (supportsManualRetry()) {
mTryAgainButton.setVisibility(View.VISIBLE);
@@ -216,7 +219,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
// Do this last since we want to know if the button is being animated (in the case of
// small -> medium dialog)
super.onAuthenticationFailed(failureReason);
super.onAuthenticationFailed(modality, failureReason);
}
private void resetErrorView() {

View File

@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -24,6 +26,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.PromptInfo;
import android.os.Bundle;
@@ -542,12 +545,25 @@ public abstract class AuthBiometricView extends LinearLayout {
}
}
public void onAuthenticationFailed(String failureReason) {
/**
* Notify the view that auth has failed.
*
* @param modality sensor modality that failed
* @param failureReason message
*/
public void onAuthenticationFailed(
@Modality int modality, @Nullable String failureReason) {
showTemporaryMessage(failureReason, mResetErrorRunnable);
updateState(STATE_ERROR);
}
public void onError(String error) {
/**
* Notify the view that an error occurred.
*
* @param modality sensor modality that failed
* @param error message
*/
public void onError(@Modality int modality, String error) {
showTemporaryMessage(error, mResetErrorRunnable);
updateState(STATE_ERROR);
@@ -556,11 +572,22 @@ public abstract class AuthBiometricView extends LinearLayout {
}, mInjector.getDelayAfterError());
}
public void onHelp(String help) {
/**
* Show a help message to the user.
*
* @param modality sensor modality
* @param help message
*/
public void onHelp(@Modality int modality, String help) {
if (mSize != AuthDialog.SIZE_MEDIUM) {
Log.w(TAG, "Help received in size: " + mSize);
return;
}
if (TextUtils.isEmpty(help)) {
Log.w(TAG, "Ignoring blank help message");
return;
}
showTemporaryMessage(help, mResetHelpRunnable);
updateState(STATE_HELP);
}
@@ -828,10 +855,10 @@ public abstract class AuthBiometricView extends LinearLayout {
final String indicatorText =
mSavedState.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING);
if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING)) {
onHelp(indicatorText);
onHelp(TYPE_NONE, indicatorText);
} else if (mSavedState.getBoolean(
AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)) {
onAuthenticationFailed(indicatorText);
onAuthenticationFailed(TYPE_NONE, indicatorText);
}
}
}

View File

@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -598,18 +599,18 @@ public class AuthContainerView extends LinearLayout
}
@Override
public void onAuthenticationFailed(String failureReason) {
mBiometricView.onAuthenticationFailed(failureReason);
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
mBiometricView.onAuthenticationFailed(modality, failureReason);
}
@Override
public void onHelp(String help) {
mBiometricView.onHelp(help);
public void onHelp(@Modality int modality, String help) {
mBiometricView.onHelp(modality, help);
}
@Override
public void onError(String error) {
mBiometricView.onError(error);
public void onError(@Modality int modality, String error) {
mBiometricView.onError(modality, error);
}
@Override

View File

@@ -18,8 +18,6 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,7 +31,10 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
@@ -525,11 +526,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
@Override
public void onBiometricHelp(String message) {
public void onBiometricHelp(@Modality int modality, String message) {
if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
if (mCurrentDialog != null) {
mCurrentDialog.onHelp(message);
mCurrentDialog.onHelp(modality, message);
} else {
Log.w(TAG, "onBiometricHelp callback but dialog gone");
}
@@ -540,7 +541,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
return mUdfpsProps;
}
private String getErrorString(int modality, int error, int vendorCode) {
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
return FaceManager.getErrorString(mContext, error, vendorCode);
@@ -559,7 +560,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
* example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
*/
@Override
public void onBiometricError(int modality, int error, int vendorCode) {
public void onBiometricError(@Modality int modality, int error, int vendorCode) {
if (DEBUG) {
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
@@ -580,11 +581,11 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
? mContext.getString(R.string.biometric_not_recognized)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
mCurrentDialog.onAuthenticationFailed(errorMessage);
mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
} else {
final String errorMessage = getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(errorMessage);
mCurrentDialog.onError(modality, errorMessage);
}
} else {
Log.w(TAG, "onBiometricError callback but dialog is gone");

View File

@@ -19,6 +19,7 @@ package com.android.systemui.biometrics;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.os.Bundle;
import android.view.WindowManager;
@@ -112,21 +113,24 @@ public interface AuthDialog {
/**
* Authentication failed (reject, timeout). Dialog stays showing.
* @param failureReason
* @param modality sensor modality that triggered the error
* @param failureReason message
*/
void onAuthenticationFailed(String failureReason);
void onAuthenticationFailed(@Modality int modality, String failureReason);
/**
* Authentication rejected, or help message received.
* @param help
* @param modality sensor modality that triggered the help message
* @param help message
*/
void onHelp(String help);
void onHelp(@Modality int modality, String help);
/**
* Authentication failed. Dialog going away.
* @param error
* @param modality sensor modality that triggered the error
* @param error message
*/
void onError(String error);
void onError(@Modality int modality, String error);
/**
* Save the current state.

View File

@@ -18,7 +18,6 @@ package com.android.systemui.statusbar;
import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -35,6 +34,8 @@ import android.app.StatusBarManager.WindowType;
import android.app.StatusBarManager.WindowVisibleState;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
@@ -293,13 +294,16 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
}
/** @see IStatusBar#onBiometricAuthenticated() */
default void onBiometricAuthenticated() {
}
default void onBiometricHelp(String message) {
/** @see IStatusBar#onBiometricHelp(String) */
default void onBiometricHelp(@Modality int modality, String message) {
}
default void onBiometricError(int modality, int error, int vendorCode) {
/** @see IStatusBar#onBiometricError(int, int, int) */
default void onBiometricError(@Modality int modality, int error, int vendorCode) {
}
default void hideAuthenticationDialog() {
@@ -891,9 +895,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
public void onBiometricHelp(String message) {
public void onBiometricHelp(@Modality int modality, String message) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
SomeArgs args = SomeArgs.obtain();
args.argi1 = modality;
args.arg1 = message;
mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
}
}
@@ -1318,11 +1325,16 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
break;
}
case MSG_BIOMETRIC_HELP:
case MSG_BIOMETRIC_HELP: {
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricHelp((String) msg.obj);
mCallbacks.get(i).onBiometricHelp(
someArgs.argi1 /* modality */,
(String) someArgs.arg1 /* message */);
}
someArgs.recycle();
break;
}
case MSG_BIOMETRIC_ERROR: {
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {

View File

@@ -16,10 +16,12 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -31,6 +33,7 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -61,15 +64,14 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
@Mock private TextView mIndicatorView;
@Mock private ImageView mIconView;
@Mock private View mIconHolderView;
@Mock private TextView mErrorView;
@Mock private AuthBiometricFaceView.IconController mIconController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mFaceToFpView = new TestableView(mContext);
mFaceToFpView.mIconController = mock(AuthBiometricFaceView.IconController.class);
mFaceToFpView.mIconController = mIconController;
mFaceToFpView.setCallback(mCallback);
mFaceToFpView.mNegativeButton = mNegativeButton;
@@ -77,8 +79,7 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
mFaceToFpView.mConfirmButton = mConfirmButton;
mFaceToFpView.mTryAgainButton = mTryAgainButton;
mFaceToFpView.mIndicatorView = mErrorView;
mFaceToFpView.mIndicatorView = mIndicatorView;
}
@Test
@@ -90,7 +91,7 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
@Test
public void testIconUpdatesState_whenDialogStateUpdated() {
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
mFaceToFpView.onDialogAnimatedIn();
verify(mFaceToFpView.mIconController)
.updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
@@ -98,11 +99,13 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
verify(mFaceToFpView.mIconController).updateState(
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState);
}
@Test
public void testStateUpdated_whenSwitchToFingerprint() {
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
mFaceToFpView.onDialogAnimatedIn();
verify(mFaceToFpView.mIconController)
.updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
@@ -120,6 +123,17 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
verify(mConfirmButton).setVisibility(eq(View.GONE));
}
@Test
public void testModeUpdated_whenSwitchToFingerprint() {
mFaceToFpView.onDialogAnimatedIn();
mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
waitForIdleSync();
verify(mIndicatorView).setText(
eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)));
assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING, mFaceToFpView.mState);
}
public class TestableView extends AuthBiometricFaceToFingerprintView {
public TestableView(Context context) {
super(context, null, new MockInjector());
@@ -132,7 +146,7 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
@Override
protected IconController createUdfpsIconController() {
return mIconController;
return AuthBiometricFaceToFingerprintViewTest.this.mIconController;
}
}

View File

@@ -16,10 +16,13 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -199,10 +202,10 @@ public class AuthBiometricViewTest extends SysuiTestCase {
public void testError_sendsActionError() {
initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
final String testError = "testError";
mBiometricView.onError(testError);
mBiometricView.onError(TYPE_FACE, testError);
waitForIdleSync();
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_ERROR);
verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
assertEquals(AuthBiometricView.STATE_IDLE, mBiometricView.mState);
}
@@ -239,6 +242,23 @@ public class AuthBiometricViewTest extends SysuiTestCase {
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
}
@Test
public void testIgnoresUselessHelp() {
initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
mBiometricView.onDialogAnimatedIn();
waitForIdleSync();
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
mBiometricView.onHelp(TYPE_FINGERPRINT, "");
waitForIdleSync();
verify(mIndicatorView, never()).setText(any());
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
}
@Test
public void testRestoresState() {
final boolean requireConfirmation = true; // set/init from AuthController
@@ -264,7 +284,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
final String failureMessage = "testFailureMessage";
mBiometricView.setRequireConfirmation(requireConfirmation);
mBiometricView.onAuthenticationFailed(failureMessage);
mBiometricView.onAuthenticationFailed(TYPE_FACE, failureMessage);
waitForIdleSync();
Bundle state = new Bundle();

View File

@@ -252,52 +252,67 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
final int modality = BiometricAuthenticator.TYPE_NONE;
mAuthController.onBiometricError(modality,
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
assertEquals(modalityCaptor.getValue().intValue(), modality);
assertEquals(messageCaptor.getValue(),
mContext.getString(R.string.biometric_not_recognized));
}
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
mAuthController.onBiometricError(modality, error, vendorCode);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(captor.capture());
ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onAuthenticationFailed(modalityCaptor.capture(), messageCaptor.capture());
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
assertEquals(modalityCaptor.getValue().intValue(), modality);
assertEquals(messageCaptor.getValue(),
FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
public void testOnHelpInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_IRIS;
final String helpMessage = "help";
mAuthController.onBiometricHelp(helpMessage);
mAuthController.onBiometricHelp(modality, helpMessage);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onHelp(captor.capture());
ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onHelp(modalityCaptor.capture(), messageCaptor.capture());
assertEquals(captor.getValue(), helpMessage);
assertEquals(modalityCaptor.getValue().intValue(), modality);
assertEquals(messageCaptor.getValue(), helpMessage);
}
@Test
public void testOnErrorInvoked_whenSystemRequested() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
final int error = 1;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
mAuthController.onBiometricError(modality, error, vendorCode);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(captor.capture());
ArgumentCaptor<Integer> modalityCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
verify(mDialog1).onError(modalityCaptor.capture(), messageCaptor.capture());
assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
assertEquals(modalityCaptor.getValue().intValue(), modality);
assertEquals(messageCaptor.getValue(),
FaceManager.getErrorString(mContext, error, vendorCode));
}
@Test
@@ -309,7 +324,7 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1, never()).onError(anyInt(), anyString());
verify(mDialog1).animateToCredentialUI();
}
@@ -322,33 +337,37 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1, never()).onError(anyString());
verify(mDialog1, never()).onError(anyInt(), anyString());
verify(mDialog1).animateToCredentialUI();
}
@Test
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}
@Test
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
final int modality = BiometricAuthenticator.TYPE_FACE;
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final int vendorCode = 0;
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
mAuthController.onBiometricError(modality, error, vendorCode);
verify(mDialog1).onError(
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
verify(mDialog1, never()).animateToCredentialUI();
}

View File

@@ -14,6 +14,7 @@
package com.android.systemui.statusbar;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -444,10 +445,11 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testOnBiometricHelp() {
String helpMessage = "test_help_message";
mCommandQueue.onBiometricHelp(helpMessage);
final int modality = TYPE_FACE;
final String helpMessage = "test_help_message";
mCommandQueue.onBiometricHelp(modality, helpMessage);
waitForIdleSync();
verify(mCallbacks).onBiometricHelp(eq(helpMessage));
verify(mCallbacks).onBiometricHelp(eq(modality), eq(helpMessage));
}
@Test

View File

@@ -21,7 +21,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING;
@@ -44,8 +43,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -498,7 +499,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
try {
mStatusBarService.onBiometricHelp(message);
mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -873,7 +874,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
return modality;
}
private @BiometricAuthenticator.Modality int sensorIdToModality(int sensorId) {
private @Modality int sensorIdToModality(int sensorId) {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
if (sensorId == sensor.id) {
return sensor.modality;
@@ -884,7 +885,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
final @BiometricAuthenticator.Modality int modality = sensorIdToModality(sensorId);
final @Modality int modality = sensorIdToModality(sensorId);
switch (modality) {
case BiometricAuthenticator.TYPE_FINGERPRINT:
return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode);

View File

@@ -18,7 +18,6 @@ package com.android.server.statusbar;
import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import static android.view.Display.DEFAULT_DISPLAY;
import android.Manifest;
@@ -33,6 +32,8 @@ import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
@@ -814,11 +815,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
public void onBiometricHelp(String message) {
public void onBiometricHelp(@Modality int modality, String message) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.onBiometricHelp(message);
mBar.onBiometricHelp(modality, message);
} catch (RemoteException ex) {
}
}

View File

@@ -1108,7 +1108,8 @@ public class BiometricServiceTest {
public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
when(mContext.getResources().getString(anyInt())).thenReturn("test string");
setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
setupAuthForOnly(modality, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1121,7 +1122,7 @@ public class BiometricServiceTest {
// Sends to SysUI and stays in authenticating state. We don't test that the correct
// string is retrieved for now, but it's also very unlikely to break anyway.
verify(mBiometricService.mStatusBarService)
.onBiometricHelp(anyString());
.onBiometricHelp(eq(modality), anyString());
assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
}