Merge changes I57f018e4,Ib1e92278 into sc-dev

* changes:
  Remove confirm button when falling back to fingerprint auth.
  Delay starting fingerprint sensor when using multi-sensor devices.
This commit is contained in:
Joe Bolinger
2021-05-21 21:35:57 +00:00
committed by Android (Google) Code Review
20 changed files with 640 additions and 107 deletions

View File

@@ -34,7 +34,6 @@ import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Slog;
@@ -97,6 +96,27 @@ public class BiometricManager {
@Retention(RetentionPolicy.SOURCE)
public @interface BiometricError {}
/**
* Single sensor or unspecified multi-sensor behavior (prefer an explicit choice if the
* device is multi-sensor).
* @hide
*/
public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0;
/**
* Prefer the face sensor and fall back to fingerprint when needed.
* @hide
*/
public static final int BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT = 1;
/**
* @hide
*/
@IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT,
BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT})
@Retention(RetentionPolicy.SOURCE)
public @interface BiometricMultiSensorMode {}
/**
* Types of authenticators, defined at a level of granularity supported by
* {@link BiometricManager} and {@link BiometricPrompt}.

View File

@@ -28,6 +28,8 @@ oneway interface IBiometricSysuiReceiver {
void onDeviceCredentialPressed();
// Notifies the client that an internal event, e.g. back button has occurred.
void onSystemEvent(int event);
// Notifies that the dialog has finished animating in.
// Notifies that the dialog has finished animating.
void onDialogAnimatedIn();
// For multi-sensor devices, notifies that the fingerprint should start now.
void onStartFingerprintNow();
}

View File

@@ -146,7 +146,7 @@ oneway interface IStatusBar
// 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);
String opPackageName, long operationId, int multiSensorConfig);
// 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

View File

@@ -110,7 +110,7 @@ interface IStatusBarService
// 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 userId, String opPackageName, long operationId, int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();

View File

@@ -91,9 +91,23 @@ message BiometricServiceStateProto {
STATE_CLIENT_DIED_CANCELLING = 10;
}
enum MultiSensorState {
// Initializing or not yet started.
MULTI_SENSOR_STATE_UNKNOWN = 0;
// Sensors are in the process of being transitioned and there is no active sensor.
MULTI_SENSOR_STATE_SWITCHING = 1;
// Face sensor is being used as the primary input.
MULTI_SENSOR_STATE_FACE_SCANNING = 2;
// Fingerprint sensor is being used as the primary input.
MULTI_SENSOR_STATE_FP_SCANNING = 3;
}
repeated SensorServiceStateProto sensor_service_states = 1;
optional AuthSessionState auth_session_state = 2;
// Additional session state information, when the device has multiple sensors.
optional MultiSensorState auth_session_multi_sensor_state = 3;
}
// Overall state for an instance of a <Biometric>Service, for example FingerprintService or

View File

@@ -26,9 +26,11 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
/**
@@ -97,6 +99,11 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
super(context, attrs);
}
@VisibleForTesting
AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) {
super(context, attrs, injector);
}
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
if (!sensorProps.isAnyUdfpsType()) {
return;
@@ -118,24 +125,45 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
return false;
}
@Override
@BiometricState
protected int getStateForAfterError() {
if (mActiveSensorType == TYPE_FACE) {
mHandler.post(() -> mCallback.onAction(
Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
return STATE_AUTHENTICATING;
}
return super.getStateForAfterError();
}
@Override
@NonNull
protected IconController getIconController() {
if (mActiveSensorType == TYPE_FINGERPRINT) {
if (!(mIconController instanceof UdfpsIconController)) {
mIconController = new UdfpsIconController(getContext(), mIconView, mIndicatorView);
mIconController = createUdfpsIconController();
}
return mIconController;
}
return super.getIconController();
}
@NonNull
protected IconController createUdfpsIconController() {
return new UdfpsIconController(getContext(), mIconView, mIndicatorView);
}
@Override
public void updateState(int newState) {
if (mState == STATE_HELP || mState == STATE_ERROR) {
mActiveSensorType = TYPE_FINGERPRINT;
setRequireConfirmation(false);
mConfirmButton.setEnabled(false);
mConfirmButton.setVisibility(View.GONE);
}
super.updateState(newState);
}

View File

@@ -29,6 +29,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
public class AuthBiometricFaceView extends AuthBiometricView {
@@ -148,6 +149,11 @@ public class AuthBiometricFaceView extends AuthBiometricView {
super(context, attrs);
}
@VisibleForTesting
AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) {
super(context, attrs, injector);
}
@Override
protected int getDelayAfterAuthenticatedDurationMs() {
return HIDE_DELAY_MS;

View File

@@ -99,6 +99,13 @@ public abstract class AuthBiometricView extends LinearLayout {
int ACTION_BUTTON_TRY_AGAIN = 4;
int ACTION_ERROR = 5;
int ACTION_USE_DEVICE_CREDENTIAL = 6;
/**
* Notify the receiver to start the fingerprint sensor.
*
* This is only applicable to multi-sensor devices that need to delay fingerprint auth
* (i.e face -> fingerprint).
*/
int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
/**
* When an action has occurred. The caller will only invoke this when the callback should
@@ -166,7 +173,7 @@ public abstract class AuthBiometricView extends LinearLayout {
}
private final Injector mInjector;
private final Handler mHandler;
protected final Handler mHandler;
private final AccessibilityManager mAccessibilityManager;
protected final int mTextColorError;
protected final int mTextColorHint;
@@ -199,7 +206,7 @@ public abstract class AuthBiometricView extends LinearLayout {
// Measurements when biometric view is showing text, buttons, etc.
@Nullable @VisibleForTesting AuthDialog.LayoutParams mLayoutParams;
private Callback mCallback;
protected Callback mCallback;
protected @BiometricState int mState;
private float mIconOriginalY;
@@ -605,7 +612,7 @@ public abstract class AuthBiometricView extends LinearLayout {
mIndicatorView.setTextColor(mTextColorError);
mIndicatorView.setVisibility(View.VISIBLE);
mIndicatorView.setSelected(true);
mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -121,6 +123,7 @@ public class AuthContainerView extends LinearLayout
boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
@BiometricMultiSensorMode int mMultiSensorConfig;
}
public static class Builder {
@@ -166,6 +169,12 @@ public class AuthContainerView extends LinearLayout
return this;
}
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
return this;
}
public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
@Nullable List<FingerprintSensorPropertiesInternal> fpProps,
@Nullable List<FaceSensorPropertiesInternal> faceProps) {
@@ -237,6 +246,9 @@ public class AuthContainerView extends LinearLayout
addCredentialView(false /* animatePanel */, true /* animateContents */);
}, mInjector.getAnimateCredentialStartDelayMs());
break;
case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
mConfig.mCallback.onStartFingerprintNow();
break;
default:
Log.e(TAG, "Unhandled action: " + action);
}
@@ -316,21 +328,9 @@ public class AuthContainerView extends LinearLayout
return;
}
} else if (sensorCount == 2) {
int fingerprintSensorId = -1;
int faceSensorId = -1;
for (final int sensorId : config.mSensorIds) {
if (Utils.containsSensorId(mFpProps, sensorId)) {
fingerprintSensorId = sensorId;
continue;
} else if (Utils.containsSensorId(mFaceProps, sensorId)) {
faceSensorId = sensorId;
continue;
}
if (fingerprintSensorId != -1 && faceSensorId != -1) {
break;
}
}
final int[] allSensors = findFaceAndFingerprintSensors();
final int faceSensorId = allSensors[0];
final int fingerprintSensorId = allSensors[1];
if (fingerprintSensorId == -1 || faceSensorId == -1) {
Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
@@ -747,4 +747,29 @@ public class AuthContainerView extends LinearLayout
lp.token = windowToken;
return lp;
}
private boolean hasFaceAndFingerprintSensors() {
final int[] ids = findFaceAndFingerprintSensors();
return ids[0] >= 0 && ids[1] >= 0;
}
// returns [face, fingerprint] sensor ids (id is -1 if not present)
private int[] findFaceAndFingerprintSensors() {
int faceSensorId = -1;
int fingerprintSensorId = -1;
for (final int sensorId : mConfig.mSensorIds) {
if (Utils.containsSensorId(mFpProps, sensorId)) {
fingerprintSensorId = sensorId;
} else if (Utils.containsSensorId(mFaceProps, sensorId)) {
faceSensorId = sensorId;
}
if (fingerprintSensorId != -1 && faceSensorId != -1) {
break;
}
}
return new int[] {faceSensorId, fingerprintSensorId};
}
}

View File

@@ -19,6 +19,7 @@ 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;
@@ -254,6 +255,20 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
@Override
public void onStartFingerprintNow() {
if (mReceiver == null) {
Log.e(TAG, "onStartUdfpsNow: Receiver is null");
return;
}
try {
mReceiver.onStartFingerprintNow();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
}
}
@Override
public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
switch (reason) {
@@ -423,7 +438,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
int userId, String opPackageName, long operationId) {
int userId, String opPackageName, long operationId,
@BiometricMultiSensorMode int multiSensorConfig) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
if (DEBUG) {
@@ -435,7 +451,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+ ", sensorIds: " + ids.toString()
+ ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId);
+ ", operationId: " + operationId
+ ", multiSensorConfig: " + multiSensorConfig);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
@@ -446,6 +463,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
args.argi2 = multiSensorConfig;
boolean skipAnimation = false;
if (mCurrentDialog != null) {
@@ -576,6 +594,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = (long) args.arg7;
final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
@@ -586,7 +605,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
credentialAllowed,
opPackageName,
skipAnimation,
operationId);
operationId,
multiSensorConfig);
if (newDialog == null) {
Log.e(TAG, "Unsupported type configuration");
@@ -664,7 +684,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
boolean skipIntro, long operationId) {
boolean skipIntro, long operationId,
@BiometricMultiSensorMode int multiSensorConfig) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setPromptInfo(promptInfo)
@@ -673,6 +694,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
.setMultiSensorConfig(multiSensorConfig)
.build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
}
}

View File

@@ -67,7 +67,12 @@ public interface AuthDialogCallback {
void onSystemEvent(int event);
/**
* Notifies when the dialog has finished animating in.
* Notifies when the dialog has finished animating.
*/
void onDialogAnimatedIn();
/**
* Notifies that the fingerprint sensor should be started now.
*/
void onStartFingerprintNow();
}

View File

@@ -18,6 +18,7 @@ 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;
@@ -289,7 +290,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
boolean requireConfirmation, int userId, String opPackageName,
long operationId) {
long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
}
default void onBiometricAuthenticated() {
@@ -838,17 +839,19 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
int userId, String opPackageName, long operationId) {
int userId, String opPackageName, long operationId,
@BiometricMultiSensorMode int multiSensorConfig) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
args.arg2 = receiver;
args.arg3 = sensorIds; //
args.arg4 = credentialAllowed; //
args.arg3 = sensorIds;
args.arg4 = credentialAllowed;
args.arg5 = requireConfirmation;
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
args.argi2 = multiSensorConfig;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
}
@@ -1303,7 +1306,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
(boolean) someArgs.arg5 /* requireConfirmation */,
someArgs.argi1 /* userId */,
(String) someArgs.arg6 /* opPackageName */,
(long) someArgs.arg7 /* operationId */);
(long) someArgs.arg7 /* operationId */,
someArgs.argi2 /* multiSensorConfig */);
}
someArgs.recycle();
break;

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics;
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;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
@Mock AuthBiometricView.Callback mCallback;
private AuthBiometricFaceToFingerprintView mFaceToFpView;
@Mock private Button mNegativeButton;
@Mock private Button mCancelButton;
@Mock private Button mConfirmButton;
@Mock private Button mUseCredentialButton;
@Mock private Button mTryAgainButton;
@Mock private TextView mTitleView;
@Mock private TextView mSubtitleView;
@Mock private TextView mDescriptionView;
@Mock private TextView mIndicatorView;
@Mock private ImageView mIconView;
@Mock private View mIconHolderView;
@Mock private TextView mErrorView;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mFaceToFpView = new TestableView(mContext);
mFaceToFpView.mIconController = mock(AuthBiometricFaceView.IconController.class);
mFaceToFpView.setCallback(mCallback);
mFaceToFpView.mNegativeButton = mNegativeButton;
mFaceToFpView.mCancelButton = mCancelButton;
mFaceToFpView.mUseCredentialButton = mUseCredentialButton;
mFaceToFpView.mConfirmButton = mConfirmButton;
mFaceToFpView.mTryAgainButton = mTryAgainButton;
mFaceToFpView.mIndicatorView = mErrorView;
}
@Test
public void testStateUpdated_whenDialogAnimatedIn() {
mFaceToFpView.onDialogAnimatedIn();
verify(mFaceToFpView.mIconController)
.updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
}
@Test
public void testIconUpdatesState_whenDialogStateUpdated() {
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
verify(mFaceToFpView.mIconController)
.updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
verify(mFaceToFpView.mIconController).updateState(
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED));
}
@Test
public void testStateUpdated_whenSwitchToFingerprint() {
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
verify(mFaceToFpView.mIconController)
.updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR);
mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING);
InOrder order = inOrder(mFaceToFpView.mIconController);
order.verify(mFaceToFpView.mIconController).updateState(
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING),
eq(AuthBiometricFaceToFingerprintView.STATE_ERROR));
order.verify(mFaceToFpView.mIconController).updateState(
eq(AuthBiometricFaceToFingerprintView.STATE_ERROR),
eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING));
verify(mConfirmButton).setVisibility(eq(View.GONE));
}
public class TestableView extends AuthBiometricFaceToFingerprintView {
public TestableView(Context context) {
super(context, null, new MockInjector());
}
@Override
protected int getDelayAfterAuthenticatedDurationMs() {
return 0;
}
@Override
protected IconController createUdfpsIconController() {
return mIconController;
}
}
private class MockInjector extends AuthBiometricView.Injector {
@Override
public Button getNegativeButton() {
return mNegativeButton;
}
@Override
public Button getCancelButton() {
return mCancelButton;
}
@Override
public Button getUseCredentialButton() {
return mUseCredentialButton;
}
@Override
public Button getConfirmButton() {
return mConfirmButton;
}
@Override
public Button getTryAgainButton() {
return mTryAgainButton;
}
@Override
public TextView getTitleView() {
return mTitleView;
}
@Override
public TextView getSubtitleView() {
return mSubtitleView;
}
@Override
public TextView getDescriptionView() {
return mDescriptionView;
}
@Override
public TextView getIndicatorView() {
return mIndicatorView;
}
@Override
public ImageView getIconView() {
return mIconView;
}
@Override
public View getIconHolderView() {
return mIconHolderView;
}
@Override
public int getDelayAfterError() {
return 0;
}
@Override
public int getMediumToLargeAnimationDurationMs() {
return 0;
}
}
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
@@ -41,6 +42,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricSysuiReceiver;
@@ -528,7 +530,8 @@ public class AuthControllerTest extends SysuiTestCase {
true /* requireConfirmation */,
0 /* userId */,
"testPackage",
0 /* operationId */);
0 /* operationId */,
BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
}
private PromptInfo createTestPromptInfo() {
@@ -572,7 +575,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
String opPackageName, boolean skipIntro, long operationId) {
String opPackageName, boolean skipIntro, long operationId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
mLastBiometricPromptInfo = promptInfo;

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
@@ -423,13 +424,15 @@ public class CommandQueueTest extends SysuiTestCase {
final int userId = 10;
final String packageName = "test";
final long operationId = 1;
final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
credentialAllowed, requireConfirmation , userId, packageName, operationId);
credentialAllowed, requireConfirmation , userId, packageName, operationId,
multiSensorConfig);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(packageName),
eq(operationId));
eq(operationId), eq(multiSensorConfig));
}
@Test

View File

@@ -19,8 +19,25 @@ package com.android.server.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
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.*;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_SWITCHING;
import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PAUSED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PAUSED_RESUMING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PENDING_CONFIRM;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_CLIENT_DIED_CANCELLING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_SHOWING_DEVICE_CREDENTIAL;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -49,8 +66,10 @@ import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
/**
* Class that defines the states of an authentication session invoked via
@@ -78,6 +97,14 @@ public final class AuthSession implements IBinder.DeathRecipient {
@Retention(RetentionPolicy.SOURCE)
@interface SessionState {}
/** Defined in biometrics.proto */
@IntDef({
MULTI_SENSOR_STATE_UNKNOWN,
MULTI_SENSOR_STATE_FACE_SCANNING,
MULTI_SENSOR_STATE_FP_SCANNING})
@Retention(RetentionPolicy.SOURCE)
@interface MultiSensorState {}
/**
* Notify the holder of the AuthSession that the caller/client's binder has died. The
* holder (BiometricService) should schedule {@link AuthSession#onClientDied()} to be run
@@ -111,6 +138,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
// The current state, which can be either idle, called, or started
private @SessionState int mState = STATE_AUTH_IDLE;
private @BiometricMultiSensorMode int mMultiSensorMode;
private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -186,6 +216,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
if (DEBUG) {
Slog.v(TAG, "set to unknown state sensor: " + sensor.id);
}
sensor.goToStateUnknown();
}
}
@@ -194,6 +227,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
final boolean requireConfirmation = isConfirmationRequired(sensor);
if (DEBUG) {
Slog.v(TAG, "waiting for cooking for sensor: " + sensor.id);
}
sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
mUserId, mSensorReceiver, mOpPackageName, cookie,
mPromptInfo.isAllowBackgroundAuthentication());
@@ -206,16 +243,20 @@ public final class AuthSession implements IBinder.DeathRecipient {
// since LockSettingsService/Gatekeeper is always ready to check for credential.
// SystemUI invokes that path.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mSensors = new int[0];
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(
mPromptInfo,
mSysuiReceiver,
new int[0] /* sensorIds */,
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
mOpPackageName,
mOperationId);
mOperationId,
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
setSensorsToStateWaitingForCookie();
@@ -235,9 +276,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
if (allCookiesReceived()) {
mStartTimeMs = System.currentTimeMillis();
// For UDFPS, do not start until BiometricPrompt UI is shown. Otherwise, the UDFPS
// affordance will be shown before the BP UI is finished animating in.
startAllPreparedSensorsExceptUdfps();
// Do not start fingerprint sensors until BiometricPrompt UI is shown. Otherwise,
// the affordance may be shown before the BP UI is finished animating in.
startAllPreparedSensorsExceptFingerprint();
// No need to request the UI if we're coming from the paused state.
if (mState != STATE_AUTH_PAUSED_RESUMING) {
@@ -245,19 +286,23 @@ public final class AuthSession implements IBinder.DeathRecipient {
// If any sensor requires confirmation, request it to be shown.
final boolean requireConfirmation = isConfirmationRequiredByAnyEligibleSensor();
final int[] sensorIds = new int[mPreAuthInfo.eligibleSensors.size()];
mSensors = new int[mPreAuthInfo.eligibleSensors.size()];
for (int i = 0; i < mPreAuthInfo.eligibleSensors.size(); i++) {
sensorIds[i] = mPreAuthInfo.eligibleSensors.get(i).id;
mSensors[i] = mPreAuthInfo.eligibleSensors.get(i).id;
}
mMultiSensorMode = getMultiSensorModeForNewSession(
mPreAuthInfo.eligibleSensors);
mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mStatusBarService.showAuthenticationDialog(mPromptInfo,
mSysuiReceiver,
sensorIds,
mSensors,
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
mUserId,
mOpPackageName,
mOperationId);
mOperationId,
mMultiSensorMode);
mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
@@ -266,7 +311,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
// The UI was already showing :)
mState = STATE_AUTH_STARTED_UI_SHOWING;
}
} else {
Slog.v(TAG, "onCookieReceived: still waiting");
}
}
@@ -285,53 +331,50 @@ public final class AuthSession implements IBinder.DeathRecipient {
return false;
}
private boolean isUdfpsSensor(@NonNull BiometricSensor sensor) {
if (sensor.modality != TYPE_FINGERPRINT) {
return false;
}
for (FingerprintSensorPropertiesInternal prop : mFingerprintSensorProperties) {
if (sensor.id == prop.sensorId && prop.isAnyUdfpsType()) {
return true;
}
}
return false;
private void startAllPreparedSensorsExceptFingerprint() {
startAllPreparedSensors(sensor -> sensor.modality != TYPE_FINGERPRINT);
}
private void startAllPreparedSensorsExceptUdfps() {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
if (isUdfpsSensor(sensor)) {
Slog.d(TAG, "Skipping UDFPS, sensorId: " + sensor.id);
continue;
}
try {
sensor.startSensor();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to start prepared client, sensor: " + sensor, e);
}
}
private void startAllPreparedFingerprintSensors() {
startAllPreparedSensors(sensor -> sensor.modality == TYPE_FINGERPRINT);
}
private void startPreparedUdfpsSensors() {
private void startAllPreparedSensors(Function<BiometricSensor, Boolean> filter) {
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
if (isUdfpsSensor(sensor)) {
if (filter.apply(sensor)) {
try {
if (DEBUG) {
Slog.v(TAG, "Starting sensor: " + sensor.id);
}
sensor.startSensor();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to start UDFPS sensor: " + sensor, e);
Slog.e(TAG, "Unable to start prepared client, sensor: " + sensor, e);
}
}
}
}
private void cancelAllFingerprintSensors() {
cancelAllSensors(sensor -> sensor.modality == TYPE_FINGERPRINT);
}
private void cancelAllSensors() {
cancelAllSensors(sensor -> true);
}
private void cancelAllSensors(Function<BiometricSensor, Boolean> filter) {
// TODO: For multiple modalities, send a single ERROR_CANCELED only when all
// drivers have canceled authentication. We'd probably have to add a state for
// STATE_CANCELING for when we're waiting for final ERROR_CANCELED before
// sending the final error callback to the application.
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
try {
sensor.goToStateCancelling(mToken, mOpPackageName);
if (filter.apply(sensor)) {
if (DEBUG) {
Slog.v(TAG, "Canceling sensor: " + sensor.id);
}
sensor.goToStateCancelling(mToken, mOpPackageName);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
}
@@ -374,16 +417,20 @@ public final class AuthSession implements IBinder.DeathRecipient {
mPromptInfo.setAuthenticators(authenticators);
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT;
mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN;
mSensors = new int[0];
mStatusBarService.showAuthenticationDialog(
mPromptInfo,
mSysuiReceiver,
new int[0] /* sensorIds */,
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
mOpPackageName,
mOperationId);
mOperationId,
mMultiSensorMode);
} else {
mClientReceiver.onError(modality, error, vendorCode);
return true;
@@ -406,6 +453,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
// a round trip to SystemUI.
mClientReceiver.onError(modality, error, vendorCode);
return true;
} else if (shouldErrorTriggerMultiSensorTransition()) {
// wait for the UI to signal when modality should switch
mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING;
Slog.d(TAG, "onErrorReceived: waiting for modality switch callback");
} else {
mState = STATE_ERROR_PENDING_SYSUI;
mStatusBarService.onBiometricError(modality, error, vendorCode);
@@ -472,8 +523,36 @@ public final class AuthSession implements IBinder.DeathRecipient {
mState = STATE_AUTH_STARTED_UI_SHOWING;
// For UDFPS devices, we can now start the sensor.
startPreparedUdfpsSensors();
if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
mMultiSensorState = MULTI_SENSOR_STATE_FACE_SCANNING;
} else {
startFingerprintSensorsNow();
}
}
// call anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
// fingerprint sensor (i.e. face auth has failed or is not available)
void onStartFingerprint() {
if (mMultiSensorMode != BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
Slog.e(TAG, "onStartFingerprint, unexpected mode: " + mMultiSensorMode);
return;
}
if (mState != STATE_AUTH_STARTED
&& mState != STATE_AUTH_STARTED_UI_SHOWING
&& mState != STATE_AUTH_PAUSED) {
Slog.e(TAG, "onStartFingerprint, unexpected state: " + mState);
return;
}
mMultiSensorState = MULTI_SENSOR_STATE_FP_SCANNING;
startFingerprintSensorsNow();
}
// unguarded helper for the above methods only
private void startFingerprintSensorsNow() {
startAllPreparedFingerprintSensors();
mState = STATE_AUTH_STARTED_UI_SHOWING;
}
void onTryAgainPressed() {
@@ -613,6 +692,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
+ ", IsCrypto: " + isCrypto()
+ ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", Reason: " + reason
+ ", Error: " + error
+ ", Latency: " + latency);
}
@@ -652,6 +732,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
mClientReceiver.onAuthenticationSucceeded(
Utils.getAuthenticationTypeForResult(reason));
cancelBiometricOnly();
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
@@ -729,6 +810,12 @@ public final class AuthSession implements IBinder.DeathRecipient {
private void cancelBiometricOnly() {
if (mState == STATE_AUTH_STARTED || mState == STATE_AUTH_STARTED_UI_SHOWING) {
cancelAllSensors();
} else if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
cancelAllFingerprintSensors();
} else {
if (DEBUG) {
Slog.v(TAG, "nothing to cancel - wrong state: " + mState);
}
}
}
@@ -808,6 +895,32 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
}
private boolean shouldErrorTriggerMultiSensorTransition() {
if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) {
return mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING;
}
return false;
}
@BiometricMultiSensorMode
private static int getMultiSensorModeForNewSession(Collection<BiometricSensor> sensors) {
boolean hasFace = false;
boolean hasFingerprint = false;
for (BiometricSensor sensor: sensors) {
if (sensor.modality == TYPE_FACE) {
hasFace = true;
} else if (sensor.modality == TYPE_FINGERPRINT) {
hasFingerprint = true;
}
}
if (hasFace && hasFingerprint) {
return BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
}
return BIOMETRIC_MULTI_SENSOR_DEFAULT;
}
@Override
public String toString() {
return "State: " + mState

View File

@@ -104,6 +104,7 @@ public class BiometricService extends SystemService {
private static final int MSG_ON_SYSTEM_EVENT = 13;
private static final int MSG_CLIENT_DIED = 14;
private static final int MSG_ON_DIALOG_ANIMATED_IN = 15;
private static final int MSG_ON_START_FINGERPRINT_NOW = 16;
private final Injector mInjector;
private final DevicePolicyManager mDevicePolicyManager;
@@ -237,6 +238,11 @@ public class BiometricService extends SystemService {
break;
}
case MSG_ON_START_FINGERPRINT_NOW: {
handleOnStartFingerprintNow();
break;
}
default:
Slog.e(TAG, "Unknown message: " + msg);
break;
@@ -618,6 +624,11 @@ public class BiometricService extends SystemService {
public void onDialogAnimatedIn() {
mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget();
}
@Override
public void onStartFingerprintNow() {
mHandler.obtainMessage(MSG_ON_START_FINGERPRINT_NOW).sendToTarget();
}
};
private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> {
@@ -1284,6 +1295,7 @@ public class BiometricService extends SystemService {
}
private void handleOnDialogAnimatedIn() {
Slog.d(TAG, "handleOnDialogAnimatedIn");
if (mCurrentAuthSession == null) {
Slog.e(TAG, "handleOnDialogAnimatedIn: AuthSession is null");
return;
@@ -1292,6 +1304,16 @@ public class BiometricService extends SystemService {
mCurrentAuthSession.onDialogAnimatedIn();
}
private void handleOnStartFingerprintNow() {
Slog.d(TAG, "handleOnStartFingerprintNow");
if (mCurrentAuthSession == null) {
Slog.e(TAG, "handleOnStartFingerprintNow: AuthSession is null");
return;
}
mCurrentAuthSession.onStartFingerprint();
}
/**
* Invoked when each service has notified that its client is ready to be started. When
* all biometrics are ready, this invokes the SystemUI dialog through StatusBar.
@@ -1310,7 +1332,6 @@ public class BiometricService extends SystemService {
private void handleAuthenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
mHandler.post(() -> {
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,

View File

@@ -18,6 +18,7 @@ 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;
@@ -789,12 +790,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
int userId, String opPackageName, long operationId) {
int userId, String opPackageName, long operationId,
@BiometricMultiSensorMode int multiSensorConfig) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
requireConfirmation, userId, opPackageName, operationId);
requireConfirmation, userId, opPackageName, operationId, multiSensorConfig);
} catch (RemoteException ex) {
}
}

View File

@@ -38,6 +38,7 @@ import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
@@ -170,21 +171,42 @@ public class AuthSessionTest {
session.onCookieReceived(cookie2);
assertTrue(session.allCookiesReceived());
// for multi-sensor face then fingerprint is the default policy
for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
verify(sensor.impl).startPreparedClient(eq(sensor.getCookie()));
assertEquals(BiometricSensor.STATE_AUTHENTICATING, sensor.getSensorState());
if (sensor.modality == TYPE_FACE) {
verify(sensor.impl).startPreparedClient(eq(sensor.getCookie()));
assertEquals(BiometricSensor.STATE_AUTHENTICATING, sensor.getSensorState());
} else if (sensor.modality == TYPE_FINGERPRINT) {
assertEquals(BiometricSensor.STATE_COOKIE_RETURNED, sensor.getSensorState());
}
}
}
@Test
public void testUdfpsAuth_sensorStartsAfterDialogAnimationCompletes() throws RemoteException {
// For UDFPS-only setups, ensure that the sensor does not start auth until after the
// BiometricPrompt UI is finished animating. Otherwise, the UDFPS affordance will be
// shown before the BiometricPrompt is shown.
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
testMultiAuth_fingerprintSensorStartsAfter(false /* fingerprintStartsAfterDelay */);
}
@Test
public void testMultiAuth_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class));
testMultiAuth_fingerprintSensorStartsAfter(true /* fingerprintStartsAfterDelay */);
}
public void testMultiAuth_fingerprintSensorStartsAfter(boolean fingerprintStartsAfterDelay)
throws Exception {
final long operationId = 123;
final int userId = 10;
final int fingerprintSensorId = mSensors.stream()
.filter(s -> s.modality == TYPE_FINGERPRINT)
.map(s -> s.id)
.findFirst()
.orElse(-1);
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
@@ -200,27 +222,37 @@ public class AuthSessionTest {
session.goToInitialState();
final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
session.onCookieReceived(cookie1);
for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
if (cookie1 == sensor.getCookie()) {
assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
session.onCookieReceived(
session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
if (fingerprintSensorId == sensor.id) {
assertEquals(BiometricSensor.STATE_COOKIE_RETURNED, sensor.getSensorState());
} else {
assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
assertEquals(BiometricSensor.STATE_AUTHENTICATING, sensor.getSensorState());
}
}
assertTrue(session.allCookiesReceived());
// UDFPS does not start even if all cookies are received
// fingerprint sensor does not start even if all cookies are received
assertEquals(STATE_AUTH_STARTED, session.getState());
verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
anyBoolean(), anyBoolean(), anyInt(), any(), anyLong());
anyBoolean(), anyBoolean(), anyInt(), any(), anyLong(), anyInt());
// Notify AuthSession that the UI is shown. Then, UDFPS sensor should be started.
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
if (fingerprintStartsAfterDelay) {
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
session.onErrorReceived(fingerprintSensorId,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getCookie(),
BiometricConstants.BIOMETRIC_ERROR_VENDOR, 0 /* vendorCode */);
session.onStartFingerprint();
}
assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
assertEquals(BiometricSensor.STATE_AUTHENTICATING,
session.mPreAuthInfo.eligibleSensors.get(0).getSensorState());
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
}
@Test

View File

@@ -17,8 +17,17 @@
package com.android.server.biometrics;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static com.android.server.biometrics.BiometricServiceStateProto.*;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PAUSED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PAUSED_RESUMING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_PENDING_CONFIRM;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_CLIENT_DIED_CANCELLING;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_SHOWING_DEVICE_CREDENTIAL;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -264,7 +273,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -348,7 +358,8 @@ public class BiometricServiceTest {
eq(false) /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -465,6 +476,7 @@ public class BiometricServiceTest {
assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
// startPreparedClient invoked
mBiometricService.mCurrentAuthSession.onDialogAnimatedIn();
verify(mBiometricService.mSensors.get(0).impl)
.startPreparedClient(cookieCaptor.getValue());
@@ -477,7 +489,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Hardware authenticated
final byte[] HAT = generateRandomHAT();
@@ -531,7 +544,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -692,7 +706,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
anyString(),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -791,7 +806,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -870,7 +886,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test
@@ -1367,7 +1384,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Requesting strong and credential, when credential is setup
resetReceivers();
@@ -1388,7 +1406,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Un-downgrading the authenticator allows successful strong auth
for (BiometricSensor sensor : mBiometricService.mSensors) {
@@ -1412,7 +1431,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
anyLong() /* sessionId */);
anyLong() /* sessionId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@Test(expected = IllegalStateException.class)