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:
@@ -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}.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user