Revert "Revert "Fix enrollment cancelation race conditions.""

This reverts commit f4dc031e6c.

Reason for revert: Reapplying with additional change to fix the issue that broke HIDL clients.

Change-Id: Idd1485f182cf841b96ced1dab4b8330ff3b57c2a
This commit is contained in:
Joe Bolinger
2022-01-13 17:51:55 +00:00
parent 4217ed289c
commit 446644041b
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");
}
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
if (cancel != null && cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enroll");
mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
mContext.getOpPackageName(), disabledFeatures, previewSurface,
debugConsent);
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
previewSurface, debugConsent);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
} catch (RemoteException 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
@@ -359,21 +358,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
throw new IllegalArgumentException("Must supply an enrollment callback");
}
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "enrollRemotely is already canceled.");
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
if (cancel != null && cancel.isCanceled()) {
Slog.w(TAG, "enrollRemotely is already canceled.");
return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
Trace.beginSection("FaceManager#enrollRemotely");
mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver,
mContext.getOpPackageName(), disabledFeatures);
final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
}
} catch (RemoteException 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
@@ -713,10 +711,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
private void cancelEnrollment() {
private void cancelEnrollment(long requestId) {
if (mService != null) {
try {
mService.cancelEnrollment(mToken);
mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1100,9 +1098,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
private class OnEnrollCancelListener implements OnCancelListener {
private final long mAuthRequestId;
private OnEnrollCancelListener(long id) {
mAuthRequestId = id;
}
@Override
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);
// Start face enrollment
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent);
long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures,
in Surface previewSurface, boolean debugConsent);
// 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);
// Cancel enrollment in progress
void cancelEnrollment(IBinder token);
void cancelEnrollment(IBinder token, long requestId);
// Removes the specified face enrollment for the specified userId.
void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,

View File

@@ -183,9 +183,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
private class OnEnrollCancelListener implements OnCancelListener {
private final long mAuthRequestId;
private OnEnrollCancelListener(long id) {
mAuthRequestId = id;
}
@Override
public void onCancel() {
cancelEnrollment();
Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
cancelEnrollment(mAuthRequestId);
}
}
@@ -646,20 +653,19 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
throw new IllegalArgumentException("Must supply an enrollment callback");
}
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
if (cancel != null && cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
mContext.getOpPackageName(), enrollReason);
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
mServiceReceiver, mContext.getOpPackageName(), enrollReason);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
} catch (RemoteException 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
@@ -1302,9 +1308,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return allSensors.isEmpty() ? null : allSensors.get(0);
}
private void cancelEnrollment() {
private void cancelEnrollment(long requestId) {
if (mService != null) try {
mService.cancelEnrollment(mToken);
mService.cancelEnrollment(mToken, requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -84,11 +84,11 @@ interface IFingerprintService {
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// 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);
// 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
void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,

View File

@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -48,7 +50,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor
* Interface that ClientMonitor holders should use to receive callbacks.
*/
public interface Callback {
/**
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of
* 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. */
void destroy() {
@VisibleForTesting(visibility = Visibility.PACKAGE)
public void destroy() {
mAlreadyDone = true;
if (mToken != null) {
try {

View File

@@ -17,15 +17,14 @@
package com.android.server.biometrics.sensors;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
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
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
*/
@MainThread
public class 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 {
static final int NUM_ENTRIES = 10;
final String timestamp;
@@ -263,10 +146,9 @@ public class BiometricScheduler {
private final @SensorType int mSensorType;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@NonNull protected final Handler mHandler;
@VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
@VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates;
private int mTotalOperationsHandled;
@@ -277,7 +159,7 @@ public class BiometricScheduler {
// Internal callback, notified when an operation is complete. Notifies the requester
// that the operation is complete, before performing internal scheduler work (such as
// starting the next client).
public class InternalCallback implements BaseClientMonitor.Callback {
private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -286,16 +168,11 @@ public class BiometricScheduler {
mCoexCoordinator.addAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
}
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
clientMonitor.destroy();
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success
@@ -303,9 +180,9 @@ public class BiometricScheduler {
return;
}
if (clientMonitor != mCurrentOperation.mClientMonitor) {
if (!mCurrentOperation.isFor(clientMonitor)) {
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
+ " current: " + mCurrentOperation.mClientMonitor);
+ " current: " + mCurrentOperation);
return;
}
@@ -315,36 +192,33 @@ public class BiometricScheduler {
(AuthenticationClient<?>) clientMonitor);
}
mCurrentOperation.mState = Operation.STATE_FINISHED;
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
}
if (mGestureAvailabilityDispatcher != null) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
mCurrentOperation.getSensorId(), false /* active */);
}
if (mRecentOperations.size() >= mRecentOperationsLimit) {
mRecentOperations.remove(0);
}
mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
mRecentOperations.add(mCurrentOperation.getProtoEnum());
mCurrentOperation = null;
mTotalOperationsHandled++;
startNextOperationIfIdle();
});
}
}
};
@VisibleForTesting
BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService, int recentOperationsLimit,
@NonNull IBiometricService biometricService,
int recentOperationsLimit,
@NonNull CoexCoordinator coexCoordinator) {
mBiometricTag = tag;
mHandler = handler;
mSensorType = sensorType;
mInternalCallback = new InternalCallback();
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
mPendingOperations = new ArrayDeque<>();
mBiometricService = biometricService;
@@ -356,24 +230,26 @@ public class BiometricScheduler {
/**
* Creates a new scheduler.
*
* @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 gestureAvailabilityDispatcher may be null if the sensor does not support gestures
* (such as fingerprint swipe).
*/
public BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance());
this(tag, handler, sensorType, gestureAvailabilityDispatcher,
IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
}
/**
* @return A reference to the internal callback that should be invoked whenever the scheduler
* needs to (e.g. client started, client finished).
*/
@NonNull protected InternalCallback getInternalCallback() {
@VisibleForTesting
public BaseClientMonitor.Callback getInternalCallback() {
return mInternalCallback;
}
@@ -392,72 +268,46 @@ public class BiometricScheduler {
}
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// 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);
if (!(currentClient instanceof Interruptable)) {
throw new IllegalStateException("Mis-implemented client or scheduler, "
+ "trying to cancel non-interruptable operation: " + mCurrentOperation);
}
final Interruptable interruptable = (Interruptable) currentClient;
interruptable.cancelWithoutStarting(getInternalCallback());
mCurrentOperation.cancel(mHandler, mInternalCallback);
// Now we wait for the client to send its FinishCallback, which kicks off the next
// operation.
return;
}
if (mGestureAvailabilityDispatcher != null
&& mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(),
true /* active */);
mCurrentOperation.getSensorId(), true /* active */);
}
// Not all operations start immediately. BiometricPrompt waits for its operation
// to arrive at the head of the queue, before pinging it to start.
final boolean shouldStartNow = currentClient.getCookie() == 0;
if (shouldStartNow) {
if (mCurrentOperation.isUnstartableHalOperation()) {
final HalClientMonitor<?> halClientMonitor =
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
final int cookie = mCurrentOperation.isReadyToStart();
if (cookie == 0) {
if (!mCurrentOperation.start(mInternalCallback)) {
// Note down current length of queue
final int pendingOperationsLength = mPendingOperations.size();
final Operation lastOperation = mPendingOperations.peekLast();
final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+ ". 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
// failure, do the same as above. Otherwise, it's possible that something like
// setActiveUser fails, but then authenticate (for the wrong user) is invoked.
for (int i = 0; i < pendingOperationsLength; i++) {
final Operation operation = mPendingOperations.pollFirst();
if (operation == null) {
final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
if (operation != null) {
Slog.w(getTag(), "[Aborting Operation] " + operation);
operation.abort();
} else {
Slog.e(getTag(), "Null operation, index: " + i
+ ", 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
@@ -465,25 +315,20 @@ public class BiometricScheduler {
// actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null;
startNextOperationIfIdle();
} else {
Slog.d(getTag(), "[Starting] " + mCurrentOperation);
currentClient.start(getInternalCallback());
mCurrentOperation.mState = Operation.STATE_STARTED;
}
} else {
try {
mBiometricService.onReadyForAuthentication(currentClient.getCookie());
mBiometricService.onReadyForAuthentication(cookie);
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
}
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
}
}
/**
* 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
*
* 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");
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);
// 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;
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
// send ERROR_CANCELED and skip the operation.
if (clientMonitor.interruptsPrecedingClients()) {
for (Operation operation : mPendingOperations) {
if (operation.mClientMonitor instanceof Interruptable
&& operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+ operation.mClientMonitor);
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
}
for (BiometricSchedulerOperation operation : mPendingOperations) {
Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
operation.markCanceling();
}
}
mPendingOperations.add(new Operation(clientMonitor, clientCallback));
mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
Slog.d(getTag(), "[Added] " + clientMonitor
+ ", new queue size: " + mPendingOperations.size());
@@ -580,67 +389,34 @@ public class BiometricScheduler {
// cancellable, start the cancellation process.
if (clientMonitor.interruptsPrecedingClients()
&& mCurrentOperation != null
&& mCurrentOperation.mClientMonitor instanceof Interruptable
&& mCurrentOperation.mState == Operation.STATE_STARTED) {
&& mCurrentOperation.isInterruptable()
&& mCurrentOperation.isStarted()) {
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
}
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;
mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
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.
* @param token from the caller, should match the token passed in when requesting enrollment
*/
public void cancelEnrollment(IBinder token) {
if (mCurrentOperation == null) {
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;
}
public void cancelEnrollment(IBinder token, long requestId) {
Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
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
*/
public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+ " current: " + mCurrentOperation
+ " stack size: " + mPendingOperations.size());
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
if (mCurrentOperation != null
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
// Look through the current queue for all authentication clients for the specified
// 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) {
for (BiometricSchedulerOperation operation : mPendingOperations) {
if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
operation.markCanceling();
}
}
}
}
private static boolean canCancelAuthOperation(Operation operation, IBinder token,
long requestId) {
private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
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)?
return isAuthenticationOrDetectionOperation(operation)
&& operation.mClientMonitor.getToken() == token
&& isMatchingRequestId(operation, 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 operation.isAuthenticationOrDetectionOperation()
&& operation.isMatchingToken(token)
&& operation.isMatchingRequestId(requestId);
}
/**
* @return the current operation
*/
public BaseClientMonitor getCurrentClient() {
if (mCurrentOperation == null) {
return null;
}
return mCurrentOperation.mClientMonitor;
return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
}
public int getCurrentPendingCount() {
@@ -719,7 +475,7 @@ public class BiometricScheduler {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
final List<String> pendingOperations = new ArrayList<>();
for (Operation operation : mPendingOperations) {
for (BiometricSchedulerOperation operation : mPendingOperations) {
pendingOperations.add(operation.toString());
}
@@ -735,7 +491,7 @@ public class BiometricScheduler {
pw.println("Type: " + mSensorType);
pw.println("Current operation: " + mCurrentOperation);
pw.println("Pending operations: " + mPendingOperations.size());
for (Operation operation : mPendingOperations) {
for (BiometricSchedulerOperation operation : mPendingOperations) {
pw.println("Pending operation: " + operation);
}
for (CrashState crashState : mCrashStates) {
@@ -746,7 +502,7 @@ public class BiometricScheduler {
public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
final ProtoOutputStream proto = new ProtoOutputStream();
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
if (!mRecentOperations.isEmpty()) {
@@ -771,6 +527,7 @@ public class BiometricScheduler {
* HAL dies.
*/
public void reset() {
Slog.d(getTag(), "Resetting scheduler");
mPendingOperations.clear();
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
* if the client is still waiting in the pending queue and got notified that a subsequent
* 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.
*/
void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);

View File

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

View File

@@ -213,7 +213,7 @@ public class FaceService extends SystemService {
}
@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 int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -221,23 +221,24 @@ public class FaceService extends SystemService {
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
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);
}
@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 int[] disabledFeatures) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
// TODO(b/145027036): Implement this.
return -1;
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -246,7 +247,7 @@ public class FaceService extends SystemService {
return;
}
provider.second.cancelEnrollment(provider.first, token);
provider.second.cancelEnrollment(provider.first, token, requestId);
}
@Override // Binder call
@@ -624,7 +625,7 @@ public class FaceService extends SystemService {
private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
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,
@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,
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
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,
@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,
@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,
@Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
boolean debugConsent) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
false /* shouldVibrate */);
setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
mEnrollIgnoreListVendor = getContext().getResources()

View File

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

View File

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

View File

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

View File

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

View File

@@ -249,7 +249,7 @@ public class FingerprintService extends SystemService {
}
@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 String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
@@ -257,15 +257,15 @@ public class FingerprintService extends SystemService {
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
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);
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
public void cancelEnrollment(final IBinder token, long requestId) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -274,7 +274,7 @@ public class FingerprintService extends SystemService {
return;
}
provider.second.cancelEnrollment(provider.first, token);
provider.second.cancelEnrollment(provider.first, token, requestId);
}
@SuppressWarnings("deprecation")
@@ -818,7 +818,7 @@ public class FingerprintService extends SystemService {
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
fingerprint21 = Fingerprint21.newInstance(getContext(),
mFingerprintStateCallback, hidlSensor,
mFingerprintStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);

View File

@@ -88,11 +88,11 @@ public interface ServiceProvider {
/**
* 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,
@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,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,

View File

@@ -57,7 +57,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
private boolean mIsPointerDown;
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 byte[] hardwareAuthToken, @NonNull String owner,
@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,
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
!sensorProps.isAnyUdfpsType() /* shouldVibrate */);
setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -135,43 +134,17 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
@NonNull private final RestartAuthRunnable mRestartAuthRunnable;
private static class TestableBiometricScheduler extends BiometricScheduler {
@NonNull private final TestableInternalCallback mInternalCallback;
@NonNull private Fingerprint21UdfpsMock mFingerprint21;
TestableBiometricScheduler(@NonNull String tag,
TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
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) {
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 TestableBiometricScheduler scheduler =
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,9 @@ import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -69,6 +71,7 @@ public class Face10Test {
@Mock
private BiometricScheduler mScheduler;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
private IBinder mBinder;
@@ -97,7 +100,7 @@ public class Face10Test {
resetLockoutRequiresChallenge);
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();
}

View File

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