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