Fix enrollment cancelation race conditions. am: abc127673b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16687106

Change-Id: If5fb6553ed99aefb894e00f4baf637329387d50e
This commit is contained in:
Joe Bolinger
2022-01-26 18:50:24 +00:00
committed by Automerger Merge Worker
30 changed files with 1177 additions and 604 deletions

View File

@@ -306,22 +306,21 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
throw new IllegalArgumentException("Must supply an enrollment callback"); throw new IllegalArgumentException("Must supply an enrollment callback");
} }
if (cancel != null) { if (cancel != null && cancel.isCanceled()) {
if (cancel.isCanceled()) { Slog.w(TAG, "enrollment already canceled");
Slog.w(TAG, "enrollment already canceled"); return;
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
} }
if (mService != null) { if (mService != null) {
try { try {
mEnrollmentCallback = callback; mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enroll"); Trace.beginSection("FaceManager#enroll");
mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver, final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
mContext.getOpPackageName(), disabledFeatures, previewSurface, mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
debugConsent); previewSurface, debugConsent);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enroll: ", e); Slog.w(TAG, "Remote exception in enroll: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or // Though this may not be a hardware issue, it will cause apps to give up or
@@ -359,21 +358,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
throw new IllegalArgumentException("Must supply an enrollment callback"); throw new IllegalArgumentException("Must supply an enrollment callback");
} }
if (cancel != null) { if (cancel != null && cancel.isCanceled()) {
if (cancel.isCanceled()) { Slog.w(TAG, "enrollRemotely is already canceled.");
Slog.w(TAG, "enrollRemotely is already canceled."); return;
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
} }
if (mService != null) { if (mService != null) {
try { try {
mEnrollmentCallback = callback; mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enrollRemotely"); Trace.beginSection("FaceManager#enrollRemotely");
mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver, final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
mContext.getOpPackageName(), disabledFeatures); mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
}
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enrollRemotely: ", e); Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or // Though this may not be a hardware issue, it will cause apps to give up or
@@ -713,10 +711,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
} }
} }
private void cancelEnrollment() { private void cancelEnrollment(long requestId) {
if (mService != null) { if (mService != null) {
try { try {
mService.cancelEnrollment(mToken); mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
@@ -1100,9 +1098,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
} }
private class OnEnrollCancelListener implements OnCancelListener { private class OnEnrollCancelListener implements OnCancelListener {
private final long mAuthRequestId;
private OnEnrollCancelListener(long id) {
mAuthRequestId = id;
}
@Override @Override
public void onCancel() { public void onCancel() {
cancelEnrollment(); Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId);
cancelEnrollment(mAuthRequestId);
} }
} }

View File

@@ -76,15 +76,16 @@ interface IFaceService {
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start face enrollment // Start face enrollment
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent); String opPackageName, in int [] disabledFeatures,
in Surface previewSurface, boolean debugConsent);
// Start remote face enrollment // Start remote face enrollment
void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures); String opPackageName, in int [] disabledFeatures);
// Cancel enrollment in progress // Cancel enrollment in progress
void cancelEnrollment(IBinder token); void cancelEnrollment(IBinder token, long requestId);
// Removes the specified face enrollment for the specified userId. // Removes the specified face enrollment for the specified userId.
void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver, void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,

View File

@@ -184,9 +184,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
} }
private class OnEnrollCancelListener implements OnCancelListener { private class OnEnrollCancelListener implements OnCancelListener {
private final long mAuthRequestId;
private OnEnrollCancelListener(long id) {
mAuthRequestId = id;
}
@Override @Override
public void onCancel() { public void onCancel() {
cancelEnrollment(); Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
cancelEnrollment(mAuthRequestId);
} }
} }
@@ -658,20 +665,19 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
throw new IllegalArgumentException("Must supply an enrollment callback"); throw new IllegalArgumentException("Must supply an enrollment callback");
} }
if (cancel != null) { if (cancel != null && cancel.isCanceled()) {
if (cancel.isCanceled()) { Slog.w(TAG, "enrollment already canceled");
Slog.w(TAG, "enrollment already canceled"); return;
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
} }
if (mService != null) { if (mService != null) {
try { try {
mEnrollmentCallback = callback; mEnrollmentCallback = callback;
mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver, final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
mContext.getOpPackageName(), enrollReason); mServiceReceiver, mContext.getOpPackageName(), enrollReason);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enroll: ", e); Slog.w(TAG, "Remote exception in enroll: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try // Though this may not be a hardware issue, it will cause apps to give up or try
@@ -1314,9 +1320,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return allSensors.isEmpty() ? null : allSensors.get(0); return allSensors.isEmpty() ? null : allSensors.get(0);
} }
private void cancelEnrollment() { private void cancelEnrollment(long requestId) {
if (mService != null) try { if (mService != null) try {
mService.cancelEnrollment(mToken); mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }

View File

@@ -84,11 +84,11 @@ interface IFingerprintService {
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start fingerprint enrollment // Start fingerprint enrollment
void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
String opPackageName, int enrollReason); String opPackageName, int enrollReason);
// Cancel enrollment in progress // Cancel enrollment in progress
void cancelEnrollment(IBinder token); void cancelEnrollment(IBinder token, long requestId);
// Any errors resulting from this call will be returned to the listener // Any errors resulting from this call will be returned to the listener
void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver, void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,

View File

@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors; package com.android.server.biometrics.sensors;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
@@ -48,7 +50,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor
* Interface that ClientMonitor holders should use to receive callbacks. * Interface that ClientMonitor holders should use to receive callbacks.
*/ */
public interface Callback { public interface Callback {
/** /**
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
* the queue and becomes the current operation). * the queue and becomes the current operation).
@@ -203,7 +204,8 @@ public abstract class BaseClientMonitor extends LoggableMonitor
} }
/** Signals this operation has completed its lifecycle and should no longer be used. */ /** Signals this operation has completed its lifecycle and should no longer be used. */
void destroy() { @VisibleForTesting(visibility = Visibility.PACKAGE)
public void destroy() {
mAlreadyDone = true; mAlreadyDone = true;
if (mToken != null) { if (mToken != null) {
try { try {

View File

@@ -17,15 +17,14 @@
package com.android.server.biometrics.sensors; package com.android.server.biometrics.sensors;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.util.Slog; import android.util.Slog;
@@ -55,6 +54,7 @@ import java.util.Locale;
* We currently assume (and require) that each biometric sensor have its own instance of a * We currently assume (and require) that each biometric sensor have its own instance of a
* {@link BiometricScheduler}. See {@link CoexCoordinator}. * {@link BiometricScheduler}. See {@link CoexCoordinator}.
*/ */
@MainThread
public class BiometricScheduler { public class BiometricScheduler {
private static final String BASE_TAG = "BiometricScheduler"; private static final String BASE_TAG = "BiometricScheduler";
@@ -110,123 +110,6 @@ public class BiometricScheduler {
} }
} }
/**
* Contains all the necessary information for a HAL operation.
*/
@VisibleForTesting
static final class Operation {
/**
* The operation is added to the list of pending operations and waiting for its turn.
*/
static final int STATE_WAITING_IN_QUEUE = 0;
/**
* The operation is added to the list of pending operations, but a subsequent operation
* has been added. This state only applies to {@link Interruptable} operations. When this
* operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
*/
static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
/**
* The operation has reached the front of the queue and has started.
*/
static final int STATE_STARTED = 2;
/**
* The operation was started, but is now canceling. Operations should wait for the HAL to
* acknowledge that the operation was canceled, at which point it finishes.
*/
static final int STATE_STARTED_CANCELING = 3;
/**
* The operation has reached the head of the queue but is waiting for BiometricService
* to acknowledge and start the operation.
*/
static final int STATE_WAITING_FOR_COOKIE = 4;
/**
* The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
*/
static final int STATE_FINISHED = 5;
@IntDef({STATE_WAITING_IN_QUEUE,
STATE_WAITING_IN_QUEUE_CANCELING,
STATE_STARTED,
STATE_STARTED_CANCELING,
STATE_WAITING_FOR_COOKIE,
STATE_FINISHED})
@Retention(RetentionPolicy.SOURCE)
@interface OperationState {}
@NonNull final BaseClientMonitor mClientMonitor;
@Nullable final BaseClientMonitor.Callback mClientCallback;
@OperationState int mState;
Operation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback
) {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
protected Operation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback,
@OperationState int state
) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
}
public boolean isHalOperation() {
return mClientMonitor instanceof HalClientMonitor<?>;
}
/**
* @return true if the operation requires the HAL, and the HAL is null.
*/
public boolean isUnstartableHalOperation() {
if (isHalOperation()) {
final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
if (client.getFreshDaemon() == null) {
return true;
}
}
return false;
}
@Override
public String toString() {
return mClientMonitor + ", State: " + mState;
}
}
/**
* Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
* kill the current operation and forcibly start the next.
*/
private static final class CancellationWatchdog implements Runnable {
static final int DELAY_MS = 3000;
final String tag;
final Operation operation;
CancellationWatchdog(String tag, Operation operation) {
this.tag = tag;
this.operation = operation;
}
@Override
public void run() {
if (operation.mState != Operation.STATE_FINISHED) {
Slog.e(tag, "[Watchdog Triggered]: " + operation);
operation.mClientMonitor.mCallback
.onClientFinished(operation.mClientMonitor, false /* success */);
}
}
}
private static final class CrashState { private static final class CrashState {
static final int NUM_ENTRIES = 10; static final int NUM_ENTRIES = 10;
final String timestamp; final String timestamp;
@@ -263,10 +146,9 @@ public class BiometricScheduler {
private final @SensorType int mSensorType; private final @SensorType int mSensorType;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService; @NonNull private final IBiometricService mBiometricService;
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper()); @NonNull protected final Handler mHandler;
@NonNull private final InternalCallback mInternalCallback; @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations; @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates; @NonNull private final ArrayDeque<CrashState> mCrashStates;
private int mTotalOperationsHandled; private int mTotalOperationsHandled;
@@ -277,7 +159,7 @@ public class BiometricScheduler {
// Internal callback, notified when an operation is complete. Notifies the requester // Internal callback, notified when an operation is complete. Notifies the requester
// that the operation is complete, before performing internal scheduler work (such as // that the operation is complete, before performing internal scheduler work (such as
// starting the next client). // starting the next client).
public class InternalCallback implements BaseClientMonitor.Callback { private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
@Override @Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor); Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -286,16 +168,11 @@ public class BiometricScheduler {
mCoexCoordinator.addAuthenticationClient(mSensorType, mCoexCoordinator.addAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor); (AuthenticationClient<?>) clientMonitor);
} }
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
}
} }
@Override @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> { mHandler.post(() -> {
clientMonitor.destroy();
if (mCurrentOperation == null) { if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success + " but current operation is null, success: " + success
@@ -303,9 +180,9 @@ public class BiometricScheduler {
return; return;
} }
if (clientMonitor != mCurrentOperation.mClientMonitor) { if (!mCurrentOperation.isFor(clientMonitor)) {
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match" Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
+ " current: " + mCurrentOperation.mClientMonitor); + " current: " + mCurrentOperation);
return; return;
} }
@@ -315,36 +192,33 @@ public class BiometricScheduler {
(AuthenticationClient<?>) clientMonitor); (AuthenticationClient<?>) clientMonitor);
} }
mCurrentOperation.mState = Operation.STATE_FINISHED;
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
}
if (mGestureAvailabilityDispatcher != null) { if (mGestureAvailabilityDispatcher != null) {
mGestureAvailabilityDispatcher.markSensorActive( mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(), false /* active */); mCurrentOperation.getSensorId(), false /* active */);
} }
if (mRecentOperations.size() >= mRecentOperationsLimit) { if (mRecentOperations.size() >= mRecentOperationsLimit) {
mRecentOperations.remove(0); mRecentOperations.remove(0);
} }
mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum()); mRecentOperations.add(mCurrentOperation.getProtoEnum());
mCurrentOperation = null; mCurrentOperation = null;
mTotalOperationsHandled++; mTotalOperationsHandled++;
startNextOperationIfIdle(); startNextOperationIfIdle();
}); });
} }
} };
@VisibleForTesting @VisibleForTesting
BiometricScheduler(@NonNull String tag, @SensorType int sensorType, BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService, int recentOperationsLimit, @NonNull IBiometricService biometricService,
int recentOperationsLimit,
@NonNull CoexCoordinator coexCoordinator) { @NonNull CoexCoordinator coexCoordinator) {
mBiometricTag = tag; mBiometricTag = tag;
mHandler = handler;
mSensorType = sensorType; mSensorType = sensorType;
mInternalCallback = new InternalCallback();
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
mPendingOperations = new ArrayDeque<>(); mPendingOperations = new ArrayDeque<>();
mBiometricService = biometricService; mBiometricService = biometricService;
@@ -356,24 +230,26 @@ public class BiometricScheduler {
/** /**
* Creates a new scheduler. * Creates a new scheduler.
*
* @param tag for the specific instance of the scheduler. Should be unique. * @param tag for the specific instance of the scheduler. Should be unique.
* @param handler handler for callbacks (all methods of this class must be called on the
* thread associated with this handler)
* @param sensorType the sensorType that this scheduler is handling. * @param sensorType the sensorType that this scheduler is handling.
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
* (such as fingerprint swipe). * (such as fingerprint swipe).
*/ */
public BiometricScheduler(@NonNull String tag, public BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( this(tag, handler, sensorType, gestureAvailabilityDispatcher,
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, IBiometricService.Stub.asInterface(
CoexCoordinator.getInstance()); ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
} }
/** @VisibleForTesting
* @return A reference to the internal callback that should be invoked whenever the scheduler public BaseClientMonitor.Callback getInternalCallback() {
* needs to (e.g. client started, client finished).
*/
@NonNull protected InternalCallback getInternalCallback() {
return mInternalCallback; return mInternalCallback;
} }
@@ -392,72 +268,46 @@ public class BiometricScheduler {
} }
mCurrentOperation = mPendingOperations.poll(); mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
Slog.d(getTag(), "[Polled] " + mCurrentOperation); Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send // If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client. // ERROR_CANCELED. No need to start this client.
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) { if (mCurrentOperation.isMarkedCanceling()) {
Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation); Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
if (!(currentClient instanceof Interruptable)) { mCurrentOperation.cancel(mHandler, mInternalCallback);
throw new IllegalStateException("Mis-implemented client or scheduler, "
+ "trying to cancel non-interruptable operation: " + mCurrentOperation);
}
final Interruptable interruptable = (Interruptable) currentClient;
interruptable.cancelWithoutStarting(getInternalCallback());
// Now we wait for the client to send its FinishCallback, which kicks off the next // Now we wait for the client to send its FinishCallback, which kicks off the next
// operation. // operation.
return; return;
} }
if (mGestureAvailabilityDispatcher != null if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
&& mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
mGestureAvailabilityDispatcher.markSensorActive( mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(), mCurrentOperation.getSensorId(), true /* active */);
true /* active */);
} }
// Not all operations start immediately. BiometricPrompt waits for its operation // Not all operations start immediately. BiometricPrompt waits for its operation
// to arrive at the head of the queue, before pinging it to start. // to arrive at the head of the queue, before pinging it to start.
final boolean shouldStartNow = currentClient.getCookie() == 0; final int cookie = mCurrentOperation.isReadyToStart();
if (shouldStartNow) { if (cookie == 0) {
if (mCurrentOperation.isUnstartableHalOperation()) { if (!mCurrentOperation.start(mInternalCallback)) {
final HalClientMonitor<?> halClientMonitor =
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
// Note down current length of queue // Note down current length of queue
final int pendingOperationsLength = mPendingOperations.size(); final int pendingOperationsLength = mPendingOperations.size();
final Operation lastOperation = mPendingOperations.peekLast(); final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+ ". Last pending operation: " + lastOperation); + ". Last pending operation: " + lastOperation);
// For current operations, 1) unableToStart, which notifies the caller-side, then
// 2) notify operation's callback, to notify applicable system service that the
// operation failed.
halClientMonitor.unableToStart();
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(
mCurrentOperation.mClientMonitor, false /* success */);
}
// Then for each operation currently in the pending queue at the time of this // Then for each operation currently in the pending queue at the time of this
// failure, do the same as above. Otherwise, it's possible that something like // failure, do the same as above. Otherwise, it's possible that something like
// setActiveUser fails, but then authenticate (for the wrong user) is invoked. // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
for (int i = 0; i < pendingOperationsLength; i++) { for (int i = 0; i < pendingOperationsLength; i++) {
final Operation operation = mPendingOperations.pollFirst(); final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
if (operation == null) { if (operation != null) {
Slog.w(getTag(), "[Aborting Operation] " + operation);
operation.abort();
} else {
Slog.e(getTag(), "Null operation, index: " + i Slog.e(getTag(), "Null operation, index: " + i
+ ", expected length: " + pendingOperationsLength); + ", expected length: " + pendingOperationsLength);
break;
} }
if (operation.isHalOperation()) {
((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
}
if (operation.mClientCallback != null) {
operation.mClientCallback.onClientFinished(operation.mClientMonitor,
false /* success */);
}
Slog.w(getTag(), "[Aborted Operation] " + operation);
} }
// It's possible that during cleanup a new set of operations came in. We can try to // It's possible that during cleanup a new set of operations came in. We can try to
@@ -465,25 +315,20 @@ public class BiometricScheduler {
// actually be multiple operations (i.e. updateActiveUser + authenticate). // actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null; mCurrentOperation = null;
startNextOperationIfIdle(); startNextOperationIfIdle();
} else {
Slog.d(getTag(), "[Starting] " + mCurrentOperation);
currentClient.start(getInternalCallback());
mCurrentOperation.mState = Operation.STATE_STARTED;
} }
} else { } else {
try { try {
mBiometricService.onReadyForAuthentication(currentClient.getCookie()); mBiometricService.onReadyForAuthentication(cookie);
} catch (RemoteException e) { } catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e); Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
} }
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation); Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
} }
} }
/** /**
* Starts the {@link #mCurrentOperation} if * Starts the {@link #mCurrentOperation} if
* 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
* 2) its cookie matches this cookie * 2) its cookie matches this cookie
* *
* This is currently only used by {@link com.android.server.biometrics.BiometricService}, which * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
@@ -499,45 +344,13 @@ public class BiometricScheduler {
Slog.e(getTag(), "Current operation is null"); Slog.e(getTag(), "Current operation is null");
return; return;
} }
if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
+ mCurrentOperation);
// This should trigger the internal onClientFinished callback, which clears the
// operation and starts the next one.
final ErrorConsumer errorConsumer =
(ErrorConsumer) mCurrentOperation.mClientMonitor;
errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
return;
} else {
Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+ ", expected STATE_WAITING_FOR_COOKIE");
return;
}
}
if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
+ ", received: " + cookie);
return;
}
if (mCurrentOperation.isUnstartableHalOperation()) { if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
} else {
Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation); Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
// This is BiometricPrompt trying to auth but something's wrong with the HAL.
final HalClientMonitor<?> halClientMonitor =
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
halClientMonitor.unableToStart();
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
false /* success */);
}
mCurrentOperation = null; mCurrentOperation = null;
startNextOperationIfIdle(); startNextOperationIfIdle();
} else {
Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
mCurrentOperation.mState = Operation.STATE_STARTED;
mCurrentOperation.mClientMonitor.start(getInternalCallback());
} }
} }
@@ -562,17 +375,13 @@ public class BiometricScheduler {
// pending clients as canceling. Once they reach the head of the queue, the scheduler will // pending clients as canceling. Once they reach the head of the queue, the scheduler will
// send ERROR_CANCELED and skip the operation. // send ERROR_CANCELED and skip the operation.
if (clientMonitor.interruptsPrecedingClients()) { if (clientMonitor.interruptsPrecedingClients()) {
for (Operation operation : mPendingOperations) { for (BiometricSchedulerOperation operation : mPendingOperations) {
if (operation.mClientMonitor instanceof Interruptable Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
&& operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) { operation.markCanceling();
Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+ operation.mClientMonitor);
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
}
} }
} }
mPendingOperations.add(new Operation(clientMonitor, clientCallback)); mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
Slog.d(getTag(), "[Added] " + clientMonitor Slog.d(getTag(), "[Added] " + clientMonitor
+ ", new queue size: " + mPendingOperations.size()); + ", new queue size: " + mPendingOperations.size());
@@ -580,67 +389,34 @@ public class BiometricScheduler {
// cancellable, start the cancellation process. // cancellable, start the cancellation process.
if (clientMonitor.interruptsPrecedingClients() if (clientMonitor.interruptsPrecedingClients()
&& mCurrentOperation != null && mCurrentOperation != null
&& mCurrentOperation.mClientMonitor instanceof Interruptable && mCurrentOperation.isInterruptable()
&& mCurrentOperation.mState == Operation.STATE_STARTED) { && mCurrentOperation.isStarted()) {
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
cancelInternal(mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback);
} } else {
startNextOperationIfIdle();
}
private void cancelInternal(Operation operation) {
if (operation != mCurrentOperation) {
Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
return;
}
if (!(operation.mClientMonitor instanceof Interruptable)) {
Slog.w(getTag(), "Operation not interruptable: " + operation);
return;
}
if (operation.mState == Operation.STATE_STARTED_CANCELING) {
Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
return;
}
if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
// We can set it to null immediately, since the HAL was never notified to start.
if (mCurrentOperation != null) {
mCurrentOperation.mClientMonitor.destroy();
}
mCurrentOperation = null;
startNextOperationIfIdle(); startNextOperationIfIdle();
return;
} }
Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
interruptable.cancel();
operation.mState = Operation.STATE_STARTED_CANCELING;
// Add a watchdog. If the HAL does not acknowledge within the timeout, we will
// forcibly finish this client.
mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
CancellationWatchdog.DELAY_MS);
} }
/** /**
* Requests to cancel enrollment. * Requests to cancel enrollment.
* @param token from the caller, should match the token passed in when requesting enrollment * @param token from the caller, should match the token passed in when requesting enrollment
*/ */
public void cancelEnrollment(IBinder token) { public void cancelEnrollment(IBinder token, long requestId) {
if (mCurrentOperation == null) { Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
Slog.e(getTag(), "Unable to cancel enrollment, null operation");
return;
}
final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
if (!isEnrolling || !tokenMatches) {
Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
+ " tokenMatches: " + tokenMatches);
return;
}
cancelInternal(mCurrentOperation); if (mCurrentOperation != null
&& canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
for (BiometricSchedulerOperation operation : mPendingOperations) {
if (canCancelEnrollOperation(operation, token, requestId)) {
Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
operation.markCanceling();
}
}
}
} }
/** /**
@@ -649,62 +425,42 @@ public class BiometricScheduler {
* @param requestId the id returned when requesting authentication * @param requestId the id returned when requesting authentication
*/ */
public void cancelAuthenticationOrDetection(IBinder token, long requestId) { public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
+ " current: " + mCurrentOperation
+ " stack size: " + mPendingOperations.size());
if (mCurrentOperation != null if (mCurrentOperation != null
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) { && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation); Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
cancelInternal(mCurrentOperation); mCurrentOperation.cancel(mHandler, mInternalCallback);
} else { } else {
// Look through the current queue for all authentication clients for the specified for (BiometricSchedulerOperation operation : mPendingOperations) {
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
// all of them, instead of just the first one, since the API surface currently doesn't
// allow us to distinguish between multiple authentication requests from the same
// process. However, this generally does not happen anyway, and would be a class of
// bugs on its own.
for (Operation operation : mPendingOperations) {
if (canCancelAuthOperation(operation, token, requestId)) { if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
+ " as STATE_WAITING_IN_QUEUE_CANCELING"); operation.markCanceling();
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
} }
} }
} }
} }
private static boolean canCancelAuthOperation(Operation operation, IBinder token, private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
long requestId) { IBinder token, long requestId) {
return operation.isEnrollOperation()
&& operation.isMatchingToken(token)
&& operation.isMatchingRequestId(requestId);
}
private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
IBinder token, long requestId) {
// TODO: restrict callers that can cancel without requestId (negative value)? // TODO: restrict callers that can cancel without requestId (negative value)?
return isAuthenticationOrDetectionOperation(operation) return operation.isAuthenticationOrDetectionOperation()
&& operation.mClientMonitor.getToken() == token && operation.isMatchingToken(token)
&& isMatchingRequestId(operation, requestId); && operation.isMatchingRequestId(requestId);
}
// By default, monitors are not associated with a request id to retain the original
// behavior (i.e. if no requestId is explicitly set then assume it matches)
private static boolean isMatchingRequestId(Operation operation, long requestId) {
return !operation.mClientMonitor.hasRequestId()
|| operation.mClientMonitor.getRequestId() == requestId;
}
private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
final boolean isAuthentication =
operation.mClientMonitor instanceof AuthenticationConsumer;
final boolean isDetection =
operation.mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
} }
/** /**
* @return the current operation * @return the current operation
*/ */
public BaseClientMonitor getCurrentClient() { public BaseClientMonitor getCurrentClient() {
if (mCurrentOperation == null) { return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
return null;
}
return mCurrentOperation.mClientMonitor;
} }
public int getCurrentPendingCount() { public int getCurrentPendingCount() {
@@ -719,7 +475,7 @@ public class BiometricScheduler {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis())); final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
final List<String> pendingOperations = new ArrayList<>(); final List<String> pendingOperations = new ArrayList<>();
for (Operation operation : mPendingOperations) { for (BiometricSchedulerOperation operation : mPendingOperations) {
pendingOperations.add(operation.toString()); pendingOperations.add(operation.toString());
} }
@@ -735,7 +491,7 @@ public class BiometricScheduler {
pw.println("Type: " + mSensorType); pw.println("Type: " + mSensorType);
pw.println("Current operation: " + mCurrentOperation); pw.println("Current operation: " + mCurrentOperation);
pw.println("Pending operations: " + mPendingOperations.size()); pw.println("Pending operations: " + mPendingOperations.size());
for (Operation operation : mPendingOperations) { for (BiometricSchedulerOperation operation : mPendingOperations) {
pw.println("Pending operation: " + operation); pw.println("Pending operation: " + operation);
} }
for (CrashState crashState : mCrashStates) { for (CrashState crashState : mCrashStates) {
@@ -746,7 +502,7 @@ public class BiometricScheduler {
public byte[] dumpProtoState(boolean clearSchedulerBuffer) { public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
final ProtoOutputStream proto = new ProtoOutputStream(); final ProtoOutputStream proto = new ProtoOutputStream();
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE); ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled); proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
if (!mRecentOperations.isEmpty()) { if (!mRecentOperations.isEmpty()) {
@@ -771,6 +527,7 @@ public class BiometricScheduler {
* HAL dies. * HAL dies.
*/ */
public void reset() { public void reset() {
Slog.d(getTag(), "Resetting scheduler");
mPendingOperations.clear(); mPendingOperations.clear();
mCurrentOperation = null; mCurrentOperation = null;
} }

View File

@@ -0,0 +1,419 @@
/*
* 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.server.biometrics.sensors;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
import android.os.IBinder;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains all the necessary information for a HAL operation.
*/
public class BiometricSchedulerOperation {
protected static final String TAG = "BiometricSchedulerOperation";
/**
* The operation is added to the list of pending operations and waiting for its turn.
*/
protected static final int STATE_WAITING_IN_QUEUE = 0;
/**
* The operation is added to the list of pending operations, but a subsequent operation
* has been added. This state only applies to {@link Interruptable} operations. When this
* operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
*/
protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
/**
* The operation has reached the front of the queue and has started.
*/
protected static final int STATE_STARTED = 2;
/**
* The operation was started, but is now canceling. Operations should wait for the HAL to
* acknowledge that the operation was canceled, at which point it finishes.
*/
protected static final int STATE_STARTED_CANCELING = 3;
/**
* The operation has reached the head of the queue but is waiting for BiometricService
* to acknowledge and start the operation.
*/
protected static final int STATE_WAITING_FOR_COOKIE = 4;
/**
* The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
*/
protected static final int STATE_FINISHED = 5;
@IntDef({STATE_WAITING_IN_QUEUE,
STATE_WAITING_IN_QUEUE_CANCELING,
STATE_STARTED,
STATE_STARTED_CANCELING,
STATE_WAITING_FOR_COOKIE,
STATE_FINISHED})
@Retention(RetentionPolicy.SOURCE)
protected @interface OperationState {}
private static final int CANCEL_WATCHDOG_DELAY_MS = 3000;
@NonNull
private final BaseClientMonitor mClientMonitor;
@Nullable
private final BaseClientMonitor.Callback mClientCallback;
@OperationState
private int mState;
@VisibleForTesting
@NonNull
final Runnable mCancelWatchdog;
BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback
) {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
protected BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback,
@OperationState int state
) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
}
};
}
/**
* Zero if this operation is ready to start or has already started. A non-zero cookie
* is returned if the operation has not started and is waiting on
* {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}.
*
* @return cookie or 0 if ready/started
*/
public int isReadyToStart() {
if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) {
final int cookie = mClientMonitor.getCookie();
if (cookie != 0) {
mState = STATE_WAITING_FOR_COOKIE;
}
return cookie;
}
return 0;
}
/**
* Start this operation without waiting for a cookie
* (i.e. {@link #isReadyToStart() returns zero}
*
* @param callback lifecycle callback
* @return if this operation started
*/
public boolean start(@NonNull BaseClientMonitor.Callback callback) {
checkInState("start",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING);
if (mClientMonitor.getCookie() != 0) {
throw new IllegalStateException("operation requires cookie");
}
return doStart(callback);
}
/**
* Start this operation after receiving the given cookie.
*
* @param callback lifecycle callback
* @param cookie cookie indicting the operation should begin
* @return if this operation started
*/
public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) {
checkInState("start",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING);
if (mClientMonitor.getCookie() != cookie) {
Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
return false;
}
return doStart(callback);
}
private boolean doStart(@NonNull BaseClientMonitor.Callback callback) {
final BaseClientMonitor.Callback cb = getWrappedCallback(callback);
if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
cb.onClientFinished(mClientMonitor, true /* success */);
if (mClientMonitor instanceof ErrorConsumer) {
final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor;
errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
} else {
Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer");
}
return false;
}
if (isUnstartableHalOperation()) {
Slog.v(TAG, "unable to start: " + this);
((HalClientMonitor<?>) mClientMonitor).unableToStart();
cb.onClientFinished(mClientMonitor, false /* success */);
return false;
}
mState = STATE_STARTED;
mClientMonitor.start(cb);
Slog.v(TAG, "started: " + this);
return true;
}
/**
* Abort a pending operation.
*
* This is similar to cancel but the operation must not have been started. It will
* immediately abort the operation and notify the client that it has finished unsuccessfully.
*/
public void abort() {
checkInState("cannot abort a non-pending operation",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING);
if (isHalOperation()) {
((HalClientMonitor<?>) mClientMonitor).unableToStart();
}
getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
Slog.v(TAG, "Aborted: " + this);
}
/** Flags this operation as canceled, but does not cancel it until started. */
public void markCanceling() {
if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
mState = STATE_WAITING_IN_QUEUE_CANCELING;
Slog.v(TAG, "Marked cancelling: " + this);
}
}
/**
* Cancel the operation now.
*
* @param handler handler to use for the cancellation watchdog
* @param callback lifecycle callback (only used if this operation hasn't started, otherwise
* the callback used from {@link #start(BaseClientMonitor.Callback)} is used)
*/
public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) {
checkNotInState("cancel", STATE_FINISHED);
final int currentState = mState;
if (!isInterruptable()) {
Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this);
return;
}
if (currentState == STATE_STARTED_CANCELING) {
Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
return;
}
mState = STATE_STARTED_CANCELING;
if (currentState == STATE_WAITING_IN_QUEUE
|| currentState == STATE_WAITING_IN_QUEUE_CANCELING
|| currentState == STATE_WAITING_FOR_COOKIE) {
Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback));
} else {
Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
((Interruptable) mClientMonitor).cancel();
}
// forcibly finish this client if the HAL does not acknowledge within the timeout
handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS);
}
@NonNull
private BaseClientMonitor.Callback getWrappedCallback() {
return getWrappedCallback(null);
}
@NonNull
private BaseClientMonitor.Callback getWrappedCallback(
@Nullable BaseClientMonitor.Callback callback) {
final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mClientMonitor.destroy();
mState = STATE_FINISHED;
}
};
return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback);
}
/** {@link BaseClientMonitor#getSensorId()}. */
public int getSensorId() {
return mClientMonitor.getSensorId();
}
/** {@link BaseClientMonitor#getProtoEnum()}. */
public int getProtoEnum() {
return mClientMonitor.getProtoEnum();
}
/** {@link BaseClientMonitor#getTargetUserId()}. */
public int getTargetUserId() {
return mClientMonitor.getTargetUserId();
}
/** If the given clientMonitor is the same as the one in the constructor. */
public boolean isFor(@NonNull BaseClientMonitor clientMonitor) {
return mClientMonitor == clientMonitor;
}
/** If this operation is {@link Interruptable}. */
public boolean isInterruptable() {
return mClientMonitor instanceof Interruptable;
}
private boolean isHalOperation() {
return mClientMonitor instanceof HalClientMonitor<?>;
}
private boolean isUnstartableHalOperation() {
if (isHalOperation()) {
final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
if (client.getFreshDaemon() == null) {
return true;
}
}
return false;
}
/** If this operation is an enrollment. */
public boolean isEnrollOperation() {
return mClientMonitor instanceof EnrollClient;
}
/** If this operation is authentication. */
public boolean isAuthenticateOperation() {
return mClientMonitor instanceof AuthenticationClient;
}
/** If this operation is authentication or detection. */
public boolean isAuthenticationOrDetectionOperation() {
final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer;
final boolean isDetection = mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
}
/** If this operation performs acquisition {@link AcquisitionClient}. */
public boolean isAcquisitionOperation() {
return mClientMonitor instanceof AcquisitionClient;
}
/**
* If this operation matches the original requestId.
*
* By default, monitors are not associated with a request id to retain the original
* behavior (i.e. if no requestId is explicitly set then assume it matches)
*
* @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}.
*/
public boolean isMatchingRequestId(long requestId) {
return !mClientMonitor.hasRequestId()
|| mClientMonitor.getRequestId() == requestId;
}
/** If the token matches */
public boolean isMatchingToken(@Nullable IBinder token) {
return mClientMonitor.getToken() == token;
}
/** If this operation has started. */
public boolean isStarted() {
return mState == STATE_STARTED;
}
/** If this operation is cancelling but has not yet completed. */
public boolean isCanceling() {
return mState == STATE_STARTED_CANCELING;
}
/** If this operation has finished and completed its lifecycle. */
public boolean isFinished() {
return mState == STATE_FINISHED;
}
/** If {@link #markCanceling()} was called but the operation hasn't been canceled. */
public boolean isMarkedCanceling() {
return mState == STATE_WAITING_IN_QUEUE_CANCELING;
}
/**
* The monitor passed to the constructor.
* @deprecated avoid using and move to encapsulate within the operation
*/
@Deprecated
public BaseClientMonitor getClientMonitor() {
return mClientMonitor;
}
private void checkNotInState(String message, @OperationState int... states) {
for (int state : states) {
if (mState == state) {
throw new IllegalStateException(message + ": illegal state= " + state);
}
}
}
private void checkInState(String message, @OperationState int... states) {
for (int state : states) {
if (mState == state) {
return;
}
}
throw new IllegalStateException(message + ": illegal state= " + mState);
}
@Override
public String toString() {
return mClientMonitor + ", State: " + mState;
}
}

View File

@@ -32,6 +32,11 @@ public interface Interruptable {
* {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
* if the client is still waiting in the pending queue and got notified that a subsequent * if the client is still waiting in the pending queue and got notified that a subsequent
* operation is preempting it. * operation is preempting it.
*
* This method must invoke
* {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
* given callback (with success).
*
* @param callback invoked when the operation is completed. * @param callback invoked when the operation is completed.
*/ */
void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback); void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);

View File

@@ -16,10 +16,13 @@
package com.android.server.biometrics.sensors; package com.android.server.biometrics.sensors;
import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricService;
import android.os.Handler;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Slog; import android.util.Slog;
@@ -68,9 +71,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
return; return;
} }
Slog.d(getTag(), "[Client finished] " Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+ clientMonitor + ", success: " + success); if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) {
mCurrentOperation = null; mCurrentOperation = null;
startNextOperationIfIdle(); startNextOperationIfIdle();
} else { } else {
@@ -83,26 +85,31 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
} }
@VisibleForTesting @VisibleForTesting
UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, UserAwareBiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService, @NonNull IBiometricService biometricService,
@NonNull CurrentUserRetriever currentUserRetriever, @NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback, @NonNull UserSwitchCallback userSwitchCallback,
@NonNull CoexCoordinator coexCoordinator) { @NonNull CoexCoordinator coexCoordinator) {
super(tag, sensorType, gestureAvailabilityDispatcher, biometricService, super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
LOG_NUM_RECENT_OPERATIONS, coexCoordinator); LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
mCurrentUserRetriever = currentUserRetriever; mCurrentUserRetriever = currentUserRetriever;
mUserSwitchCallback = userSwitchCallback; mUserSwitchCallback = userSwitchCallback;
} }
public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, public UserAwareBiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull CurrentUserRetriever currentUserRetriever, @NonNull CurrentUserRetriever currentUserRetriever,
@NonNull UserSwitchCallback userSwitchCallback) { @NonNull UserSwitchCallback userSwitchCallback) {
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( this(tag, handler, sensorType, gestureAvailabilityDispatcher,
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever, IBiometricService.Stub.asInterface(
userSwitchCallback, CoexCoordinator.getInstance()); ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
} }
@Override @Override
@@ -122,7 +129,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
} }
final int currentUserId = mCurrentUserRetriever.getCurrentUserId(); final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId(); final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
if (nextUserId == currentUserId) { if (nextUserId == currentUserId) {
super.startNextOperationIfIdle(); super.startNextOperationIfIdle();
@@ -133,8 +140,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
new ClientFinishedCallback(startClient); new ClientFinishedCallback(startClient);
Slog.d(getTag(), "[Starting User] " + startClient); Slog.d(getTag(), "[Starting User] " + startClient);
mCurrentOperation = new Operation( mCurrentOperation = new BiometricSchedulerOperation(
startClient, finishedCallback, Operation.STATE_STARTED); startClient, finishedCallback, STATE_STARTED);
startClient.start(finishedCallback); startClient.start(finishedCallback);
} else { } else {
if (mStopUserClient != null) { if (mStopUserClient != null) {
@@ -147,8 +154,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
Slog.d(getTag(), "[Stopping User] current: " + currentUserId Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+ ", next: " + nextUserId + ". " + mStopUserClient); + ", next: " + nextUserId + ". " + mStopUserClient);
mCurrentOperation = new Operation( mCurrentOperation = new BiometricSchedulerOperation(
mStopUserClient, finishedCallback, Operation.STATE_STARTED); mStopUserClient, finishedCallback, STATE_STARTED);
mStopUserClient.start(finishedCallback); mStopUserClient.start(finishedCallback);
} }
} }

View File

@@ -213,7 +213,7 @@ public class FaceService extends SystemService {
} }
@Override // Binder call @Override // Binder call
public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName, final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -221,23 +221,24 @@ public class FaceService extends SystemService {
final Pair<Integer, ServiceProvider> provider = getSingleProvider(); final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) { if (provider == null) {
Slog.w(TAG, "Null provider for enroll"); Slog.w(TAG, "Null provider for enroll");
return; return -1;
} }
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
receiver, opPackageName, disabledFeatures, previewSurface, debugConsent); receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
} }
@Override // Binder call @Override // Binder call
public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName, final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) { final int[] disabledFeatures) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
// TODO(b/145027036): Implement this. // TODO(b/145027036): Implement this.
return -1;
} }
@Override // Binder call @Override // Binder call
public void cancelEnrollment(final IBinder token) { public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final Pair<Integer, ServiceProvider> provider = getSingleProvider(); final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -246,7 +247,7 @@ public class FaceService extends SystemService {
return; return;
} }
provider.second.cancelEnrollment(provider.first, token); provider.second.cancelEnrollment(provider.first, token, requestId);
} }
@Override // Binder call @Override // Binder call
@@ -624,7 +625,7 @@ public class FaceService extends SystemService {
private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) { private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
mServiceProviders.add( mServiceProviders.add(
new Face10(getContext(), hidlSensor, mLockoutResetDispatcher)); Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
} }
} }

View File

@@ -94,12 +94,12 @@ public interface ServiceProvider {
void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge); @NonNull String opPackageName, long challenge);
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
boolean debugConsent); boolean debugConsent);
void cancelEnrollment(int sensorId, @NonNull IBinder token); void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId, long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,

View File

@@ -82,13 +82,14 @@ public class FaceEnrollClient extends EnrollClient<ISession> {
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser, @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
boolean debugConsent) { boolean debugConsent) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
false /* shouldVibrate */); false /* shouldVibrate */);
setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources() mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist); .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
mEnrollIgnoreListVendor = getContext().getResources() mEnrollIgnoreListVendor = getContext().getResources()

View File

@@ -327,17 +327,18 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
} }
@Override @Override
public void scheduleEnroll(int sensorId, @NonNull IBinder token, public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures, @NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable Surface previewSurface, boolean debugConsent) { @Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> { mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get( final int maxTemplatesPerUser = mSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser; sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext, final FaceEnrollClient client = new FaceEnrollClient(mContext,
mSensors.get(sensorId).getLazySession(), token, mSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
debugConsent); debugConsent);
scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
@@ -351,11 +352,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
} }
}); });
}); });
return id;
} }
@Override @Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token) { public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); mHandler.post(() ->
mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
} }
@Override @Override

View File

@@ -494,7 +494,7 @@ public class Sensor {
mToken = new Binder(); mToken = new Binder();
mHandler = handler; mHandler = handler;
mSensorProperties = sensorProperties; mSensorProperties = sensorProperties;
mScheduler = new UserAwareBiometricScheduler(tag, mScheduler = new UserAwareBiometricScheduler(tag, mHandler,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() { new UserAwareBiometricScheduler.UserSwitchCallback() {

View File

@@ -333,12 +333,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
Face10(@NonNull Context context, Face10(@NonNull Context context,
@NonNull FaceSensorPropertiesInternal sensorProps, @NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@NonNull BiometricScheduler scheduler) { @NonNull BiometricScheduler scheduler) {
mSensorProperties = sensorProps; mSensorProperties = sensorProps;
mContext = context; mContext = context;
mSensorId = sensorProps.sensorId; mSensorId = sensorProps.sensorId;
mScheduler = scheduler; mScheduler = scheduler;
mHandler = new Handler(Looper.getMainLooper()); mHandler = handler;
mUsageStats = new UsageStats(context); mUsageStats = new UsageStats(context);
mAuthenticatorIds = new HashMap<>(); mAuthenticatorIds = new HashMap<>();
mLazyDaemon = Face10.this::getDaemon; mLazyDaemon = Face10.this::getDaemon;
@@ -357,10 +358,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
} }
} }
public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps, public static Face10 newInstance(@NonNull Context context,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) { @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
this(context, sensorProps, lockoutResetDispatcher, final Handler handler = new Handler(Looper.getMainLooper());
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
new BiometricScheduler(TAG, handler, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */)); null /* gestureAvailabilityTracker */));
} }
@@ -573,10 +576,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
} }
@Override @Override
public void scheduleEnroll(int sensorId, @NonNull IBinder token, public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures, @NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable Surface previewSurface, boolean debugConsent) { @Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> { mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId); scheduleUpdateActiveUserWithoutHandler(userId);
@@ -584,7 +588,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId); ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@@ -598,13 +602,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
} }
}); });
}); });
return id;
} }
@Override @Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token) { public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> { mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
mScheduler.cancelEnrollment(token);
});
} }
@Override @Override

View File

@@ -53,12 +53,13 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId) { @Nullable Surface previewSurface, int sensorId) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
false /* shouldVibrate */); false /* shouldVibrate */);
setRequestId(requestId);
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
mEnrollIgnoreList = getContext().getResources() mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist); .getIntArray(R.array.config_face_acquire_enroll_ignorelist);

View File

@@ -249,7 +249,7 @@ public class FingerprintService extends SystemService {
} }
@Override // Binder call @Override // Binder call
public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver, final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
@@ -257,15 +257,15 @@ public class FingerprintService extends SystemService {
final Pair<Integer, ServiceProvider> provider = getSingleProvider(); final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) { if (provider == null) {
Slog.w(TAG, "Null provider for enroll"); Slog.w(TAG, "Null provider for enroll");
return; return -1;
} }
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
receiver, opPackageName, enrollReason); receiver, opPackageName, enrollReason);
} }
@Override // Binder call @Override // Binder call
public void cancelEnrollment(final IBinder token) { public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
final Pair<Integer, ServiceProvider> provider = getSingleProvider(); final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -274,7 +274,7 @@ public class FingerprintService extends SystemService {
return; return;
} }
provider.second.cancelEnrollment(provider.first, token); provider.second.cancelEnrollment(provider.first, token, requestId);
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@@ -818,7 +818,7 @@ public class FingerprintService extends SystemService {
mLockoutResetDispatcher, mGestureAvailabilityDispatcher); mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else { } else {
fingerprint21 = Fingerprint21.newInstance(getContext(), fingerprint21 = Fingerprint21.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor, mFingerprintStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher); mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} }
mServiceProviders.add(fingerprint21); mServiceProviders.add(fingerprint21);

View File

@@ -88,11 +88,11 @@ public interface ServiceProvider {
/** /**
* Schedules fingerprint enrollment. * Schedules fingerprint enrollment.
*/ */
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver, int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason); @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
void cancelEnrollment(int sensorId, @NonNull IBinder token); void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,

View File

@@ -57,7 +57,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
private boolean mIsPointerDown; private boolean mIsPointerDown;
FingerprintEnrollClient(@NonNull Context context, FingerprintEnrollClient(@NonNull Context context,
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@@ -69,6 +69,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
!sensorProps.isAnyUdfpsType() /* shouldVibrate */); !sensorProps.isAnyUdfpsType() /* shouldVibrate */);
setRequestId(requestId);
mSensorProps = sensorProps; mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser; mMaxTemplatesPerUser = maxTemplatesPerUser;

View File

@@ -343,15 +343,16 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
} }
@Override @Override
public void scheduleEnroll(int sensorId, @NonNull IBinder token, public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
@FingerprintManager.EnrollReason int enrollReason) { @FingerprintManager.EnrollReason int enrollReason) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> { mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties() final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser; .maxEnrollmentsPerUser;
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mSensors.get(sensorId).getLazySession(), token, mSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getSensorProperties(), mSensors.get(sensorId).getSensorProperties(),
@@ -374,11 +375,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
} }
}); });
}); });
return id;
} }
@Override @Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token) { public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); mHandler.post(() ->
mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
} }
@Override @Override

View File

@@ -449,7 +449,7 @@ class Sensor {
mHandler = handler; mHandler = handler;
mSensorProperties = sensorProperties; mSensorProperties = sensorProperties;
mLockoutCache = new LockoutCache(); mLockoutCache = new LockoutCache();
mScheduler = new UserAwareBiometricScheduler(tag, mScheduler = new UserAwareBiometricScheduler(tag, handler,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties), BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher, gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,

View File

@@ -42,7 +42,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.IHwBinder; import android.os.IHwBinder;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
@@ -320,7 +319,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
Fingerprint21(@NonNull Context context, Fingerprint21(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler, @NonNull BiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) { @NonNull HalResultController controller) {
mContext = context; mContext = context;
@@ -356,16 +356,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
public static Fingerprint21 newInstance(@NonNull Context context, public static Fingerprint21 newInstance(@NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
final BiometricScheduler scheduler = final BiometricScheduler scheduler =
new BiometricScheduler(TAG, new BiometricScheduler(TAG, handler,
BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps), BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
gestureAvailabilityDispatcher); gestureAvailabilityDispatcher);
final HalResultController controller = new HalResultController(sensorProps.sensorId, final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler, context, handler, scheduler);
scheduler);
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler, return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
lockoutResetDispatcher, controller); lockoutResetDispatcher, controller);
} }
@@ -558,18 +557,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
} }
@Override @Override
public void scheduleEnroll(int sensorId, @NonNull IBinder token, public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
@FingerprintManager.EnrollReason int enrollReason) { @FingerprintManager.EnrollReason int enrollReason) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> { mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId); scheduleUpdateActiveUserWithoutHandler(userId);
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), userId, hardwareAuthToken, opPackageName,
ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController, FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
mSidefpsController, enrollReason); mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
enrollReason);
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override @Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -588,13 +589,12 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
} }
}); });
}); });
return id;
} }
@Override @Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token) { public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> { mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
mScheduler.cancelEnrollment(token);
});
} }
@Override @Override

View File

@@ -26,7 +26,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
@@ -135,43 +134,17 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
@NonNull private final RestartAuthRunnable mRestartAuthRunnable; @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
private static class TestableBiometricScheduler extends BiometricScheduler { private static class TestableBiometricScheduler extends BiometricScheduler {
@NonNull private final TestableInternalCallback mInternalCallback;
@NonNull private Fingerprint21UdfpsMock mFingerprint21; @NonNull private Fingerprint21UdfpsMock mFingerprint21;
TestableBiometricScheduler(@NonNull String tag, TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
gestureAvailabilityDispatcher); gestureAvailabilityDispatcher);
mInternalCallback = new TestableInternalCallback();
}
class TestableInternalCallback extends InternalCallback {
@Override
public void onClientStarted(BaseClientMonitor clientMonitor) {
super.onClientStarted(clientMonitor);
Slog.d(TAG, "Client started: " + clientMonitor);
mFingerprint21.setDebugMessage("Started: " + clientMonitor);
}
@Override
public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) {
super.onClientFinished(clientMonitor, success);
Slog.d(TAG, "Client finished: " + clientMonitor);
mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
}
} }
void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
mFingerprint21 = fingerprint21; mFingerprint21 = fingerprint21;
} }
/**
* Expose the internal finish callback so it can be used for testing
*/
@Override
@NonNull protected InternalCallback getInternalCallback() {
return mInternalCallback;
}
} }
/** /**
@@ -280,7 +253,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
final Handler handler = new Handler(Looper.getMainLooper()); final Handler handler = new Handler(Looper.getMainLooper());
final TestableBiometricScheduler scheduler = final TestableBiometricScheduler scheduler =
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher); new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
final MockHalResultController controller = final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler, return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,

View File

@@ -55,7 +55,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
FingerprintEnrollClient(@NonNull Context context, FingerprintEnrollClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
@Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable IUdfpsOverlayController udfpsOverlayController,
@@ -64,6 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
true /* shouldVibrate */); true /* shouldVibrate */);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mEnrollReason = enrollReason; mEnrollReason = enrollReason;

View File

@@ -0,0 +1,326 @@
/*
* 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.server.biometrics.sensors;
import static android.testing.TestableLooper.RunWithLooper;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class BiometricSchedulerOperationTest {
public interface FakeHal {}
public abstract static class InterruptableMonitor<T>
extends HalClientMonitor<T> implements Interruptable {
public InterruptableMonitor() {
super(null, null, null, null, 0, null, 0, 0, 0, 0, 0);
}
}
@Mock
private InterruptableMonitor<FakeHal> mClientMonitor;
@Mock
private BaseClientMonitor.Callback mClientCallback;
@Mock
private FakeHal mHal;
@Captor
ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback;
private Handler mHandler;
private BiometricSchedulerOperation mOperation;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(TestableLooper.get(this).getLooper());
mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
}
@Test
public void testStartWithCookie() {
final int cookie = 200;
when(mClientMonitor.getCookie()).thenReturn(cookie);
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mOperation.isReadyToStart()).isEqualTo(cookie);
assertThat(mOperation.isStarted()).isFalse();
assertThat(mOperation.isCanceling()).isFalse();
assertThat(mOperation.isFinished()).isFalse();
final boolean started = mOperation.startWithCookie(
mock(BaseClientMonitor.Callback.class), cookie);
assertThat(started).isTrue();
verify(mClientMonitor).start(mStartCallback.capture());
mStartCallback.getValue().onClientStarted(mClientMonitor);
assertThat(mOperation.isStarted()).isTrue();
}
@Test
public void testNoStartWithoutCookie() {
final int goodCookie = 20;
final int badCookie = 22;
when(mClientMonitor.getCookie()).thenReturn(goodCookie);
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie);
final boolean started = mOperation.startWithCookie(
mock(BaseClientMonitor.Callback.class), badCookie);
assertThat(started).isFalse();
assertThat(mOperation.isStarted()).isFalse();
assertThat(mOperation.isCanceling()).isFalse();
assertThat(mOperation.isFinished()).isFalse();
}
@Test
public void startsWhenReadyAndHalAvailable() {
when(mClientMonitor.getCookie()).thenReturn(0);
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
mOperation.start(cb);
verify(mClientMonitor).start(mStartCallback.capture());
mStartCallback.getValue().onClientStarted(mClientMonitor);
assertThat(mOperation.isStarted()).isTrue();
assertThat(mOperation.isCanceling()).isFalse();
assertThat(mOperation.isFinished()).isFalse();
verify(mClientCallback).onClientStarted(eq(mClientMonitor));
verify(cb).onClientStarted(eq(mClientMonitor));
verify(mClientCallback, never()).onClientFinished(any(), anyBoolean());
verify(cb, never()).onClientFinished(any(), anyBoolean());
mStartCallback.getValue().onClientFinished(mClientMonitor, true);
assertThat(mOperation.isFinished()).isTrue();
assertThat(mOperation.isCanceling()).isFalse();
verify(mClientMonitor).destroy();
verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
}
@Test
public void startFailsWhenReadyButHalNotAvailable() {
when(mClientMonitor.getCookie()).thenReturn(0);
when(mClientMonitor.getFreshDaemon()).thenReturn(null);
final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
mOperation.start(cb);
verify(mClientMonitor, never()).start(any());
assertThat(mOperation.isStarted()).isFalse();
assertThat(mOperation.isCanceling()).isFalse();
assertThat(mOperation.isFinished()).isTrue();
verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor));
verify(cb, never()).onClientStarted(eq(mClientMonitor));
verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false));
verify(cb).onClientFinished(eq(mClientMonitor), eq(false));
}
@Test
public void doesNotStartWithCookie() {
when(mClientMonitor.getCookie()).thenReturn(9);
assertThrows(IllegalStateException.class,
() -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
}
@Test
public void cannotRestart() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mock(BaseClientMonitor.Callback.class));
assertThrows(IllegalStateException.class,
() -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
}
@Test
public void abortsNotRunning() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.abort();
assertThat(mOperation.isFinished()).isTrue();
verify(mClientMonitor).unableToStart();
verify(mClientMonitor).destroy();
assertThrows(IllegalStateException.class,
() -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
}
@Test
public void cannotAbortRunning() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mock(BaseClientMonitor.Callback.class));
assertThrows(IllegalStateException.class, () -> mOperation.abort());
}
@Test
public void cancel() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class);
final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
mOperation.start(startCb);
verify(mClientMonitor).start(mStartCallback.capture());
mStartCallback.getValue().onClientStarted(mClientMonitor);
mOperation.cancel(mHandler, cancelCb);
assertThat(mOperation.isCanceling()).isTrue();
verify(mClientMonitor).cancel();
verify(mClientMonitor, never()).cancelWithoutStarting(any());
verify(mClientMonitor, never()).destroy();
mStartCallback.getValue().onClientFinished(mClientMonitor, true);
assertThat(mOperation.isFinished()).isTrue();
assertThat(mOperation.isCanceling()).isFalse();
verify(mClientMonitor).destroy();
// should be unused since the operation was started
verify(cancelCb, never()).onClientStarted(any());
verify(cancelCb, never()).onClientFinished(any(), anyBoolean());
}
@Test
public void cancelWithoutStarting() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
mOperation.cancel(mHandler, cancelCb);
assertThat(mOperation.isCanceling()).isTrue();
ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor =
ArgumentCaptor.forClass(BaseClientMonitor.Callback.class);
verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
cbCaptor.getValue().onClientFinished(mClientMonitor, true);
verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true));
verify(mClientMonitor, never()).start(any());
verify(mClientMonitor, never()).cancel();
verify(mClientMonitor).destroy();
}
@Test
public void markCanceling() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.markCanceling();
assertThat(mOperation.isMarkedCanceling()).isTrue();
assertThat(mOperation.isCanceling()).isFalse();
assertThat(mOperation.isFinished()).isFalse();
verify(mClientMonitor, never()).start(any());
verify(mClientMonitor, never()).cancel();
verify(mClientMonitor, never()).cancelWithoutStarting(any());
verify(mClientMonitor, never()).unableToStart();
verify(mClientMonitor, never()).destroy();
}
@Test
public void cancelPendingWithCookie() {
markCancellingAndStart(2);
}
@Test
public void cancelPendingWithoutCookie() {
markCancellingAndStart(null);
}
private void markCancellingAndStart(Integer withCookie) {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
if (withCookie != null) {
when(mClientMonitor.getCookie()).thenReturn(withCookie);
}
mOperation.markCanceling();
final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
if (withCookie != null) {
mOperation.startWithCookie(cb, withCookie);
} else {
mOperation.start(cb);
}
assertThat(mOperation.isFinished()).isTrue();
verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
verify(mClientMonitor, never()).start(any());
verify(mClientMonitor, never()).cancel();
verify(mClientMonitor, never()).cancelWithoutStarting(any());
verify(mClientMonitor, never()).unableToStart();
verify(mClientMonitor).destroy();
}
@Test
public void cancelWatchdogWhenStarted() {
cancelWatchdog(true);
}
@Test
public void cancelWatchdogWithoutStarting() {
cancelWatchdog(false);
}
private void cancelWatchdog(boolean start) {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mock(BaseClientMonitor.Callback.class));
if (start) {
verify(mClientMonitor).start(mStartCallback.capture());
mStartCallback.getValue().onClientStarted(mClientMonitor);
}
mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class));
assertThat(mOperation.isCanceling()).isTrue();
// omit call to onClientFinished and trigger watchdog
mOperation.mCancelWatchdog.run();
assertThat(mOperation.isFinished()).isTrue();
assertThat(mOperation.isCanceling()).isFalse();
verify(mClientMonitor).destroy();
}
}

View File

@@ -16,10 +16,14 @@
package com.android.server.biometrics.sensors; package com.android.server.biometrics.sensors;
import static android.testing.TestableLooper.RunWithLooper;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,10 +38,13 @@ import android.content.Context;
import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricService;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext; import android.testing.TestableContext;
import android.testing.TestableLooper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -46,16 +53,18 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.nano.BiometricSchedulerProto; import com.android.server.biometrics.nano.BiometricSchedulerProto;
import com.android.server.biometrics.nano.BiometricsProto; import com.android.server.biometrics.nano.BiometricsProto;
import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@Presubmit @Presubmit
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
public class BiometricSchedulerTest { public class BiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest"; private static final String TAG = "BiometricSchedulerTest";
@@ -76,8 +85,9 @@ public class BiometricSchedulerTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mToken = new Binder(); mToken = new Binder();
mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN, mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS, BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
mBiometricService, LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance()); CoexCoordinator.getInstance());
} }
@@ -86,9 +96,9 @@ public class BiometricSchedulerTest {
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
final HalClientMonitor<Object> client1 = final HalClientMonitor<Object> client1 =
new TestClientMonitor(mContext, mToken, nonNullDaemon); new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
final HalClientMonitor<Object> client2 = final HalClientMonitor<Object> client2 =
new TestClientMonitor(mContext, mToken, nonNullDaemon); new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
mScheduler.scheduleClientMonitor(client1); mScheduler.scheduleClientMonitor(client1);
mScheduler.scheduleClientMonitor(client2); mScheduler.scheduleClientMonitor(client2);
@@ -99,20 +109,17 @@ public class BiometricSchedulerTest {
@Test @Test
public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() { public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
// Even if second client has a non-null daemon, it needs to be canceled. // Even if second client has a non-null daemon, it needs to be canceled.
Object daemon2 = mock(Object.class); final TestHalClientMonitor client1 = new TestHalClientMonitor(
mContext, mToken, () -> null);
final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null; final TestHalClientMonitor client2 = new TestHalClientMonitor(
final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2; mContext, mToken, () -> mock(Object.class));
final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1);
final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
// Pretend the scheduler is busy so the first operation doesn't start right away. We want // Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off // to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricScheduler.Operation( mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
mScheduler.scheduleClientMonitor(client1, callback1); mScheduler.scheduleClientMonitor(client1, callback1);
@@ -122,11 +129,11 @@ public class BiometricSchedulerTest {
mScheduler.scheduleClientMonitor(client2, callback2); mScheduler.scheduleClientMonitor(client2, callback2);
waitForIdle(); waitForIdle();
assertTrue(client1.wasUnableToStart()); assertTrue(client1.mUnableToStart);
verify(callback1).onClientFinished(eq(client1), eq(false) /* success */); verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
verify(callback1, never()).onClientStarted(any()); verify(callback1, never()).onClientStarted(any());
assertTrue(client2.wasUnableToStart()); assertTrue(client2.mUnableToStart);
verify(callback2).onClientFinished(eq(client2), eq(false) /* success */); verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
verify(callback2, never()).onClientStarted(any()); verify(callback2, never()).onClientStarted(any());
@@ -138,21 +145,19 @@ public class BiometricSchedulerTest {
// Second non-BiometricPrompt client has a valid daemon // Second non-BiometricPrompt client has a valid daemon
final Object daemon2 = mock(Object.class); final Object daemon2 = mock(Object.class);
final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client1 = final TestAuthenticationClient client1 =
new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); final TestHalClientMonitor client2 =
new TestHalClientMonitor(mContext, mToken, () -> daemon2);
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
// Pretend the scheduler is busy so the first operation doesn't start right away. We want // Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off // to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricScheduler.Operation( mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
mScheduler.scheduleClientMonitor(client1, callback1); mScheduler.scheduleClientMonitor(client1, callback1);
@@ -172,8 +177,8 @@ public class BiometricSchedulerTest {
verify(callback1, never()).onClientStarted(any()); verify(callback1, never()).onClientStarted(any());
// Client 2 was able to start // Client 2 was able to start
assertFalse(client2.wasUnableToStart()); assertFalse(client2.mUnableToStart);
assertTrue(client2.hasStarted()); assertTrue(client2.mStarted);
verify(callback2).onClientStarted(eq(client2)); verify(callback2).onClientStarted(eq(client2));
} }
@@ -187,16 +192,18 @@ public class BiometricSchedulerTest {
// Schedule a BiometricPrompt authentication request // Schedule a BiometricPrompt authentication request
mScheduler.scheduleClientMonitor(client1, callback1); mScheduler.scheduleClientMonitor(client1, callback1);
assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState); assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor); assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
assertEquals(0, mScheduler.mPendingOperations.size()); assertEquals(0, mScheduler.mPendingOperations.size());
// Request it to be canceled. The operation can be canceled immediately, and the scheduler // Request it to be canceled. The operation can be canceled immediately, and the scheduler
// should go back to idle, since in this case the framework has not even requested the HAL // should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet. // to authenticate yet.
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle();
assertTrue(client1.isAlreadyDone()); assertTrue(client1.isAlreadyDone());
assertTrue(client1.mDestroyed); assertTrue(client1.mDestroyed);
assertFalse(client1.mStartedHal);
assertNull(mScheduler.mCurrentOperation); assertNull(mScheduler.mCurrentOperation);
} }
@@ -210,8 +217,8 @@ public class BiometricSchedulerTest {
// assertEquals(0, bsp.recentOperations.length); // assertEquals(0, bsp.recentOperations.length);
// Pretend the scheduler is busy enrolling, and check the proto dump again. // Pretend the scheduler is busy enrolling, and check the proto dump again.
final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
() -> mock(Object.class), BiometricsProto.CM_ENROLL); () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
mScheduler.scheduleClientMonitor(client); mScheduler.scheduleClientMonitor(client);
waitForIdle(); waitForIdle();
bsp = getDump(true /* clearSchedulerBuffer */); bsp = getDump(true /* clearSchedulerBuffer */);
@@ -230,8 +237,8 @@ public class BiometricSchedulerTest {
@Test @Test
public void testProtoDump_fifo() throws Exception { public void testProtoDump_fifo() throws Exception {
// Add the first operation // Add the first operation
final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
() -> mock(Object.class), BiometricsProto.CM_ENROLL); () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
mScheduler.scheduleClientMonitor(client); mScheduler.scheduleClientMonitor(client);
waitForIdle(); waitForIdle();
BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */); BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
@@ -244,8 +251,8 @@ public class BiometricSchedulerTest {
client.getCallback().onClientFinished(client, true); client.getCallback().onClientFinished(client, true);
// Add another operation // Add another operation
final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken, final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
() -> mock(Object.class), BiometricsProto.CM_REMOVE); () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
mScheduler.scheduleClientMonitor(client2); mScheduler.scheduleClientMonitor(client2);
waitForIdle(); waitForIdle();
bsp = getDump(false /* clearSchedulerBuffer */); bsp = getDump(false /* clearSchedulerBuffer */);
@@ -256,8 +263,8 @@ public class BiometricSchedulerTest {
client2.getCallback().onClientFinished(client2, true); client2.getCallback().onClientFinished(client2, true);
// And another operation // And another operation
final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken, final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
() -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE); () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
mScheduler.scheduleClientMonitor(client3); mScheduler.scheduleClientMonitor(client3);
waitForIdle(); waitForIdle();
bsp = getDump(false /* clearSchedulerBuffer */); bsp = getDump(false /* clearSchedulerBuffer */);
@@ -290,8 +297,7 @@ public class BiometricSchedulerTest {
@Test @Test
public void testCancelPendingAuth() throws RemoteException { public void testCancelPendingAuth() throws RemoteException {
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon, final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
mToken, callback); mToken, callback);
@@ -302,14 +308,12 @@ public class BiometricSchedulerTest {
waitForIdle(); waitForIdle();
assertEquals(mScheduler.getCurrentClient(), client1); assertEquals(mScheduler.getCurrentClient(), client1);
assertEquals(Operation.STATE_WAITING_IN_QUEUE, assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
mScheduler.mPendingOperations.getFirst().mState);
// Request cancel before the authentication client has started // Request cancel before the authentication client has started
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle(); waitForIdle();
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
mScheduler.mPendingOperations.getFirst().mState);
// Finish the blocking client. The authentication client should send ERROR_CANCELED // Finish the blocking client. The authentication client should send ERROR_CANCELED
client1.getCallback().onClientFinished(client1, true /* success */); client1.getCallback().onClientFinished(client1, true /* success */);
@@ -326,67 +330,109 @@ public class BiometricSchedulerTest {
@Test @Test
public void testCancels_whenAuthRequestIdNotSet() { public void testCancels_whenAuthRequestIdNotSet() {
testCancelsWhenRequestId(null /* requestId */, 2, true /* started */); testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
} }
@Test @Test
public void testCancels_whenAuthRequestIdNotSet_notStarted() { public void testCancels_whenAuthRequestIdNotSet_notStarted() {
testCancelsWhenRequestId(null /* requestId */, 2, false /* started */); testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
} }
@Test @Test
public void testCancels_whenAuthRequestIdMatches() { public void testCancels_whenAuthRequestIdMatches() {
testCancelsWhenRequestId(200L, 200, true /* started */); testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
} }
@Test @Test
public void testCancels_whenAuthRequestIdMatches_noStarted() { public void testCancels_whenAuthRequestIdMatches_noStarted() {
testCancelsWhenRequestId(200L, 200, false /* started */); testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
} }
@Test @Test
public void testDoesNotCancel_whenAuthRequestIdMismatched() { public void testDoesNotCancel_whenAuthRequestIdMismatched() {
testCancelsWhenRequestId(10L, 20, true /* started */); testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
} }
@Test @Test
public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() { public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
testCancelsWhenRequestId(10L, 20, false /* started */); testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
}
private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
testCancelsWhenRequestId(requestId, cancelRequestId, started,
new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
}
@Test
public void testCancels_whenEnrollRequestIdNotSet() {
testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
}
@Test
public void testCancels_whenEnrollRequestIdMatches() {
testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
}
@Test
public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
}
private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) {
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
testCancelsWhenRequestId(requestId, cancelRequestId, started,
new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
} }
private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId, private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
boolean started) { boolean started, HalClientMonitor<?> client) {
final boolean matches = requestId == null || requestId == cancelRequestId; final boolean matches = requestId == null || requestId == cancelRequestId;
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
final TestAuthenticationClient client = new TestAuthenticationClient(
mContext, lazyDaemon, mToken, callback);
if (requestId != null) { if (requestId != null) {
client.setRequestId(requestId); client.setRequestId(requestId);
} }
final boolean isAuth = client instanceof TestAuthenticationClient;
final boolean isEnroll = client instanceof TestEnrollClient;
mScheduler.scheduleClientMonitor(client); mScheduler.scheduleClientMonitor(client);
if (started) { if (started) {
mScheduler.startPreparedClient(client.getCookie()); mScheduler.startPreparedClient(client.getCookie());
} }
waitForIdle(); waitForIdle();
mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId); if (isAuth) {
mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
} else if (isEnroll) {
mScheduler.cancelEnrollment(mToken, cancelRequestId);
} else {
fail("unexpected operation type");
}
waitForIdle(); waitForIdle();
assertEquals(matches && started ? 1 : 0, client.mNumCancels); if (isAuth) {
// auth clients that were waiting for cookie when canceled should never invoke the hal
final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
assertEquals(started, authClient.mStartedHal);
} else if (isEnroll) {
final TestEnrollClient enrollClient = (TestEnrollClient) client;
assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
assertTrue(enrollClient.mStartedHal);
}
if (matches) { if (matches) {
if (started) { if (started || isEnroll) { // prep'd auth clients and enroll clients
assertEquals(Operation.STATE_STARTED_CANCELING, assertTrue(mScheduler.mCurrentOperation.isCanceling());
mScheduler.mCurrentOperation.mState);
} }
} else { } else {
if (started) { if (started || isEnroll) { // prep'd auth clients and enroll clients
assertEquals(Operation.STATE_STARTED, assertTrue(mScheduler.mCurrentOperation.isStarted());
mScheduler.mCurrentOperation.mState);
} else { } else {
assertEquals(Operation.STATE_WAITING_FOR_COOKIE, assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
mScheduler.mCurrentOperation.mState);
} }
} }
} }
@@ -411,18 +457,14 @@ public class BiometricSchedulerTest {
mScheduler.cancelAuthenticationOrDetection(mToken, 9999); mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
waitForIdle(); waitForIdle();
assertEquals(Operation.STATE_STARTED, assertTrue(mScheduler.mCurrentOperation.isStarted());
mScheduler.mCurrentOperation.mState); assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
assertEquals(Operation.STATE_WAITING_IN_QUEUE,
mScheduler.mPendingOperations.getFirst().mState);
mScheduler.cancelAuthenticationOrDetection(mToken, requestId2); mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
waitForIdle(); waitForIdle();
assertEquals(Operation.STATE_STARTED, assertTrue(mScheduler.mCurrentOperation.isStarted());
mScheduler.mCurrentOperation.mState); assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
mScheduler.mPendingOperations.getFirst().mState);
} }
@Test @Test
@@ -459,12 +501,12 @@ public class BiometricSchedulerTest {
@Test @Test
public void testClientDestroyed_afterFinish() { public void testClientDestroyed_afterFinish() {
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
final TestClientMonitor client = final TestHalClientMonitor client =
new TestClientMonitor(mContext, mToken, nonNullDaemon); new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
mScheduler.scheduleClientMonitor(client); mScheduler.scheduleClientMonitor(client);
client.mCallback.onClientFinished(client, true /* success */); client.mCallback.onClientFinished(client, true /* success */);
waitForIdle(); waitForIdle();
assertTrue(client.wasDestroyed()); assertTrue(client.mDestroyed);
} }
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
@@ -472,8 +514,10 @@ public class BiometricSchedulerTest {
} }
private static class TestAuthenticationClient extends AuthenticationClient<Object> { private static class TestAuthenticationClient extends AuthenticationClient<Object> {
int mNumCancels = 0; boolean mStartedHal = false;
boolean mStoppedHal = false;
boolean mDestroyed = false; boolean mDestroyed = false;
int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context, public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -488,18 +532,16 @@ public class BiometricSchedulerTest {
@Override @Override
protected void stopHalOperation() { protected void stopHalOperation() {
mStoppedHal = true;
} }
@Override @Override
protected void startHalOperation() { protected void startHalOperation() {
mStartedHal = true;
} }
@Override @Override
protected void handleLifecycleAfterAuth(boolean authenticated) { protected void handleLifecycleAfterAuth(boolean authenticated) {}
}
@Override @Override
public boolean wasUserDetected() { public boolean wasUserDetected() {
@@ -519,36 +561,59 @@ public class BiometricSchedulerTest {
} }
} }
private static class TestClientMonitor2 extends TestClientMonitor { private static class TestEnrollClient extends EnrollClient<Object> {
private final int mProtoEnum; boolean mStartedHal = false;
boolean mStoppedHal = false;
int mNumCancels = 0;
public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token, TestEnrollClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) { @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
super(context, token, lazyDaemon); @NonNull ClientMonitorCallbackConverter listener) {
mProtoEnum = protoEnum; super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
"test" /* owner */, mock(BiometricUtils.class),
5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
true /* shouldVibrate */);
} }
@Override @Override
public int getProtoEnum() { protected void stopHalOperation() {
return mProtoEnum; mStoppedHal = true;
}
@Override
protected void startHalOperation() {
mStartedHal = true;
}
@Override
protected boolean hasReachedEnrollmentLimit() {
return false;
}
@Override
public void cancel() {
mNumCancels++;
super.cancel();
} }
} }
private static class TestClientMonitor extends HalClientMonitor<Object> { private static class TestHalClientMonitor extends HalClientMonitor<Object> {
private final int mProtoEnum;
private boolean mUnableToStart; private boolean mUnableToStart;
private boolean mStarted; private boolean mStarted;
private boolean mDestroyed; private boolean mDestroyed;
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull LazyDaemon<Object> lazyDaemon) { @NonNull LazyDaemon<Object> lazyDaemon) {
this(context, token, lazyDaemon, 0 /* cookie */); this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
} }
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull LazyDaemon<Object> lazyDaemon, int cookie) { @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */, TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
0 /* statsAction */, 0 /* statsClient */); 0 /* statsAction */, 0 /* statsClient */);
mProtoEnum = protoEnum;
} }
@Override @Override
@@ -559,9 +624,7 @@ public class BiometricSchedulerTest {
@Override @Override
public int getProtoEnum() { public int getProtoEnum() {
// Anything other than CM_NONE, which is used to represent "idle". Tests that need return mProtoEnum;
// real proto enums should use TestClientMonitor2
return BiometricsProto.CM_UPDATE_ACTIVE_USER;
} }
@Override @Override
@@ -573,7 +636,7 @@ public class BiometricSchedulerTest {
@Override @Override
protected void startHalOperation() { protected void startHalOperation() {
mStarted = true;
} }
@Override @Override
@@ -581,22 +644,9 @@ public class BiometricSchedulerTest {
super.destroy(); super.destroy();
mDestroyed = true; mDestroyed = true;
} }
public boolean wasUnableToStart() {
return mUnableToStart;
}
public boolean hasStarted() {
return mStarted;
}
public boolean wasDestroyed() {
return mDestroyed;
}
} }
private static void waitForIdle() { private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync(); TestableLooper.get(this).processAllMessages();
} }
} }

View File

@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors; package com.android.server.biometrics.sensors;
import static android.testing.TestableLooper.RunWithLooper;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
@@ -28,52 +30,53 @@ import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricService;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.UserHandle; import android.os.UserHandle;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@Presubmit @Presubmit
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest @SmallTest
public class UserAwareBiometricSchedulerTest { public class UserAwareBiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest"; private static final String TAG = "UserAwareBiometricSchedulerTest";
private static final int TEST_SENSOR_ID = 0; private static final int TEST_SENSOR_ID = 0;
private Handler mHandler;
private UserAwareBiometricScheduler mScheduler; private UserAwareBiometricScheduler mScheduler;
private IBinder mToken; private IBinder mToken = new Binder();
@Mock @Mock
private Context mContext; private Context mContext;
@Mock @Mock
private IBiometricService mBiometricService; private IBiometricService mBiometricService;
private TestUserStartedCallback mUserStartedCallback; private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
private TestUserStoppedCallback mUserStoppedCallback; private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
private int mCurrentUserId = UserHandle.USER_NULL; private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mStartOperationsFinish; private boolean mStartOperationsFinish = true;
private int mStartUserClientCount; private int mStartUserClientCount = 0;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mHandler = new Handler(TestableLooper.get(this).getLooper());
mToken = new Binder();
mStartOperationsFinish = true;
mStartUserClientCount = 0;
mUserStartedCallback = new TestUserStartedCallback();
mUserStoppedCallback = new TestUserStoppedCallback();
mScheduler = new UserAwareBiometricScheduler(TAG, mScheduler = new UserAwareBiometricScheduler(TAG,
mHandler,
BiometricScheduler.SENSOR_TYPE_UNKNOWN, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
null /* gestureAvailabilityDispatcher */, null /* gestureAvailabilityDispatcher */,
mBiometricService, mBiometricService,
@@ -117,7 +120,7 @@ public class UserAwareBiometricSchedulerTest {
mCurrentUserId = UserHandle.USER_NULL; mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false; mStartOperationsFinish = false;
final BaseClientMonitor[] nextClients = new BaseClientMonitor[] { final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
mock(BaseClientMonitor.class), mock(BaseClientMonitor.class),
mock(BaseClientMonitor.class), mock(BaseClientMonitor.class),
mock(BaseClientMonitor.class) mock(BaseClientMonitor.class)
@@ -147,11 +150,11 @@ public class UserAwareBiometricSchedulerTest {
waitForIdle(); waitForIdle();
final TestStartUserClient startUserClient = final TestStartUserClient startUserClient =
(TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor; (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
mScheduler.reset(); mScheduler.reset();
assertNull(mScheduler.mCurrentOperation); assertNull(mScheduler.mCurrentOperation);
final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation( final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {}); mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
mScheduler.mCurrentOperation = fakeOperation; mScheduler.mCurrentOperation = fakeOperation;
startUserClient.mCallback.onClientFinished(startUserClient, true); startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -194,8 +197,8 @@ public class UserAwareBiometricSchedulerTest {
verify(nextClient).start(any()); verify(nextClient).start(any());
} }
private static void waitForIdle() { private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync(); TestableLooper.get(this).processAllMessages();
} }
private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback { private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {

View File

@@ -79,6 +79,7 @@ public class SensorTest {
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG, mScheduler = new UserAwareBiometricScheduler(TAG,
new Handler(mLooper.getLooper()),
BiometricScheduler.SENSOR_TYPE_FACE, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityDispatcher */, null /* gestureAvailabilityDispatcher */,
() -> USER_ID, () -> USER_ID,

View File

@@ -32,7 +32,9 @@ import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver; import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.UserManager; import android.os.UserManager;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -69,6 +71,7 @@ public class Face10Test {
@Mock @Mock
private BiometricScheduler mScheduler; private BiometricScheduler mScheduler;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher; private LockoutResetDispatcher mLockoutResetDispatcher;
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10; private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
private IBinder mBinder; private IBinder mBinder;
@@ -97,7 +100,7 @@ public class Face10Test {
resetLockoutRequiresChallenge); resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST")); Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler); mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
mBinder = new Binder(); mBinder = new Binder();
} }

View File

@@ -79,6 +79,7 @@ public class SensorTest {
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG, mScheduler = new UserAwareBiometricScheduler(TAG,
new Handler(mLooper.getLooper()),
BiometricScheduler.SENSOR_TYPE_FP_OTHER, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
null /* gestureAvailabilityDispatcher */, null /* gestureAvailabilityDispatcher */,
() -> USER_ID, () -> USER_ID,