Fix enrollment cancelation race conditions. am: abc127673b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16687106 Change-Id: If5fb6553ed99aefb894e00f4baf637329387d50e
This commit is contained in:
@@ -306,22 +306,21 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
|
|||||||
throw new IllegalArgumentException("Must supply an enrollment callback");
|
throw new IllegalArgumentException("Must supply an enrollment callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancel != null) {
|
if (cancel != null && cancel.isCanceled()) {
|
||||||
if (cancel.isCanceled()) {
|
|
||||||
Slog.w(TAG, "enrollment already canceled");
|
Slog.w(TAG, "enrollment already canceled");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
cancel.setOnCancelListener(new OnEnrollCancelListener());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mService != null) {
|
if (mService != null) {
|
||||||
try {
|
try {
|
||||||
mEnrollmentCallback = callback;
|
mEnrollmentCallback = callback;
|
||||||
Trace.beginSection("FaceManager#enroll");
|
Trace.beginSection("FaceManager#enroll");
|
||||||
mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
|
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
|
||||||
mContext.getOpPackageName(), disabledFeatures, previewSurface,
|
mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
|
||||||
debugConsent);
|
previewSurface, debugConsent);
|
||||||
|
if (cancel != null) {
|
||||||
|
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
|
||||||
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.w(TAG, "Remote exception in enroll: ", e);
|
Slog.w(TAG, "Remote exception in enroll: ", e);
|
||||||
// Though this may not be a hardware issue, it will cause apps to give up or
|
// Though this may not be a hardware issue, it will cause apps to give up or
|
||||||
@@ -359,21 +358,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
|
|||||||
throw new IllegalArgumentException("Must supply an enrollment callback");
|
throw new IllegalArgumentException("Must supply an enrollment callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancel != null) {
|
if (cancel != null && cancel.isCanceled()) {
|
||||||
if (cancel.isCanceled()) {
|
|
||||||
Slog.w(TAG, "enrollRemotely is already canceled.");
|
Slog.w(TAG, "enrollRemotely is already canceled.");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
cancel.setOnCancelListener(new OnEnrollCancelListener());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mService != null) {
|
if (mService != null) {
|
||||||
try {
|
try {
|
||||||
mEnrollmentCallback = callback;
|
mEnrollmentCallback = callback;
|
||||||
Trace.beginSection("FaceManager#enrollRemotely");
|
Trace.beginSection("FaceManager#enrollRemotely");
|
||||||
mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver,
|
final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
|
||||||
mContext.getOpPackageName(), disabledFeatures);
|
mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
|
||||||
|
if (cancel != null) {
|
||||||
|
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
|
||||||
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
|
Slog.w(TAG, "Remote exception in enrollRemotely: ", e);
|
||||||
// Though this may not be a hardware issue, it will cause apps to give up or
|
// Though this may not be a hardware issue, it will cause apps to give up or
|
||||||
@@ -713,10 +711,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelEnrollment() {
|
private void cancelEnrollment(long requestId) {
|
||||||
if (mService != null) {
|
if (mService != null) {
|
||||||
try {
|
try {
|
||||||
mService.cancelEnrollment(mToken);
|
mService.cancelEnrollment(mToken, requestId);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw e.rethrowFromSystemServer();
|
throw e.rethrowFromSystemServer();
|
||||||
}
|
}
|
||||||
@@ -1100,9 +1098,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class OnEnrollCancelListener implements OnCancelListener {
|
private class OnEnrollCancelListener implements OnCancelListener {
|
||||||
|
private final long mAuthRequestId;
|
||||||
|
|
||||||
|
private OnEnrollCancelListener(long id) {
|
||||||
|
mAuthRequestId = id;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
cancelEnrollment();
|
Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId);
|
||||||
|
cancelEnrollment(mAuthRequestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,15 +76,16 @@ interface IFaceService {
|
|||||||
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
|
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
|
||||||
|
|
||||||
// Start face enrollment
|
// Start face enrollment
|
||||||
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
|
long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
|
||||||
String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent);
|
String opPackageName, in int [] disabledFeatures,
|
||||||
|
in Surface previewSurface, boolean debugConsent);
|
||||||
|
|
||||||
// Start remote face enrollment
|
// Start remote face enrollment
|
||||||
void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
|
long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
|
||||||
String opPackageName, in int [] disabledFeatures);
|
String opPackageName, in int [] disabledFeatures);
|
||||||
|
|
||||||
// Cancel enrollment in progress
|
// Cancel enrollment in progress
|
||||||
void cancelEnrollment(IBinder token);
|
void cancelEnrollment(IBinder token, long requestId);
|
||||||
|
|
||||||
// Removes the specified face enrollment for the specified userId.
|
// Removes the specified face enrollment for the specified userId.
|
||||||
void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
|
void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver,
|
||||||
|
|||||||
@@ -184,9 +184,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class OnEnrollCancelListener implements OnCancelListener {
|
private class OnEnrollCancelListener implements OnCancelListener {
|
||||||
|
private final long mAuthRequestId;
|
||||||
|
|
||||||
|
private OnEnrollCancelListener(long id) {
|
||||||
|
mAuthRequestId = id;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
cancelEnrollment();
|
Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId);
|
||||||
|
cancelEnrollment(mAuthRequestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,20 +665,19 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
|||||||
throw new IllegalArgumentException("Must supply an enrollment callback");
|
throw new IllegalArgumentException("Must supply an enrollment callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancel != null) {
|
if (cancel != null && cancel.isCanceled()) {
|
||||||
if (cancel.isCanceled()) {
|
|
||||||
Slog.w(TAG, "enrollment already canceled");
|
Slog.w(TAG, "enrollment already canceled");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
cancel.setOnCancelListener(new OnEnrollCancelListener());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mService != null) {
|
if (mService != null) {
|
||||||
try {
|
try {
|
||||||
mEnrollmentCallback = callback;
|
mEnrollmentCallback = callback;
|
||||||
mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
|
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
|
||||||
mContext.getOpPackageName(), enrollReason);
|
mServiceReceiver, mContext.getOpPackageName(), enrollReason);
|
||||||
|
if (cancel != null) {
|
||||||
|
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
|
||||||
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.w(TAG, "Remote exception in enroll: ", e);
|
Slog.w(TAG, "Remote exception in enroll: ", e);
|
||||||
// Though this may not be a hardware issue, it will cause apps to give up or try
|
// Though this may not be a hardware issue, it will cause apps to give up or try
|
||||||
@@ -1314,9 +1320,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
|||||||
return allSensors.isEmpty() ? null : allSensors.get(0);
|
return allSensors.isEmpty() ? null : allSensors.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelEnrollment() {
|
private void cancelEnrollment(long requestId) {
|
||||||
if (mService != null) try {
|
if (mService != null) try {
|
||||||
mService.cancelEnrollment(mToken);
|
mService.cancelEnrollment(mToken, requestId);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw e.rethrowFromSystemServer();
|
throw e.rethrowFromSystemServer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ interface IFingerprintService {
|
|||||||
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
|
void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
|
||||||
|
|
||||||
// Start fingerprint enrollment
|
// Start fingerprint enrollment
|
||||||
void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
|
long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
|
||||||
String opPackageName, int enrollReason);
|
String opPackageName, int enrollReason);
|
||||||
|
|
||||||
// Cancel enrollment in progress
|
// Cancel enrollment in progress
|
||||||
void cancelEnrollment(IBinder token);
|
void cancelEnrollment(IBinder token, long requestId);
|
||||||
|
|
||||||
// Any errors resulting from this call will be returned to the listener
|
// Any errors resulting from this call will be returned to the listener
|
||||||
void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
|
void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.server.biometrics.sensors;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import static com.android.internal.annotations.VisibleForTesting.Visibility;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -48,7 +50,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor
|
|||||||
* Interface that ClientMonitor holders should use to receive callbacks.
|
* Interface that ClientMonitor holders should use to receive callbacks.
|
||||||
*/
|
*/
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of
|
* Invoked when the ClientMonitor operation has been started (e.g. reached the head of
|
||||||
* the queue and becomes the current operation).
|
* the queue and becomes the current operation).
|
||||||
@@ -203,7 +204,8 @@ public abstract class BaseClientMonitor extends LoggableMonitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Signals this operation has completed its lifecycle and should no longer be used. */
|
/** Signals this operation has completed its lifecycle and should no longer be used. */
|
||||||
void destroy() {
|
@VisibleForTesting(visibility = Visibility.PACKAGE)
|
||||||
|
public void destroy() {
|
||||||
mAlreadyDone = true;
|
mAlreadyDone = true;
|
||||||
if (mToken != null) {
|
if (mToken != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -17,15 +17,14 @@
|
|||||||
package com.android.server.biometrics.sensors;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
|
import android.annotation.MainThread;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.biometrics.BiometricConstants;
|
|
||||||
import android.hardware.biometrics.IBiometricService;
|
import android.hardware.biometrics.IBiometricService;
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -55,6 +54,7 @@ import java.util.Locale;
|
|||||||
* We currently assume (and require) that each biometric sensor have its own instance of a
|
* We currently assume (and require) that each biometric sensor have its own instance of a
|
||||||
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
|
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
|
||||||
*/
|
*/
|
||||||
|
@MainThread
|
||||||
public class BiometricScheduler {
|
public class BiometricScheduler {
|
||||||
|
|
||||||
private static final String BASE_TAG = "BiometricScheduler";
|
private static final String BASE_TAG = "BiometricScheduler";
|
||||||
@@ -110,123 +110,6 @@ public class BiometricScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains all the necessary information for a HAL operation.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
static final class Operation {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The operation is added to the list of pending operations and waiting for its turn.
|
|
||||||
*/
|
|
||||||
static final int STATE_WAITING_IN_QUEUE = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The operation is added to the list of pending operations, but a subsequent operation
|
|
||||||
* has been added. This state only applies to {@link Interruptable} operations. When this
|
|
||||||
* operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
|
|
||||||
*/
|
|
||||||
static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The operation has reached the front of the queue and has started.
|
|
||||||
*/
|
|
||||||
static final int STATE_STARTED = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The operation was started, but is now canceling. Operations should wait for the HAL to
|
|
||||||
* acknowledge that the operation was canceled, at which point it finishes.
|
|
||||||
*/
|
|
||||||
static final int STATE_STARTED_CANCELING = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The operation has reached the head of the queue but is waiting for BiometricService
|
|
||||||
* to acknowledge and start the operation.
|
|
||||||
*/
|
|
||||||
static final int STATE_WAITING_FOR_COOKIE = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
|
|
||||||
*/
|
|
||||||
static final int STATE_FINISHED = 5;
|
|
||||||
|
|
||||||
@IntDef({STATE_WAITING_IN_QUEUE,
|
|
||||||
STATE_WAITING_IN_QUEUE_CANCELING,
|
|
||||||
STATE_STARTED,
|
|
||||||
STATE_STARTED_CANCELING,
|
|
||||||
STATE_WAITING_FOR_COOKIE,
|
|
||||||
STATE_FINISHED})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@interface OperationState {}
|
|
||||||
|
|
||||||
@NonNull final BaseClientMonitor mClientMonitor;
|
|
||||||
@Nullable final BaseClientMonitor.Callback mClientCallback;
|
|
||||||
@OperationState int mState;
|
|
||||||
|
|
||||||
Operation(
|
|
||||||
@NonNull BaseClientMonitor clientMonitor,
|
|
||||||
@Nullable BaseClientMonitor.Callback callback
|
|
||||||
) {
|
|
||||||
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Operation(
|
|
||||||
@NonNull BaseClientMonitor clientMonitor,
|
|
||||||
@Nullable BaseClientMonitor.Callback callback,
|
|
||||||
@OperationState int state
|
|
||||||
) {
|
|
||||||
mClientMonitor = clientMonitor;
|
|
||||||
mClientCallback = callback;
|
|
||||||
mState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHalOperation() {
|
|
||||||
return mClientMonitor instanceof HalClientMonitor<?>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if the operation requires the HAL, and the HAL is null.
|
|
||||||
*/
|
|
||||||
public boolean isUnstartableHalOperation() {
|
|
||||||
if (isHalOperation()) {
|
|
||||||
final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
|
|
||||||
if (client.getFreshDaemon() == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return mClientMonitor + ", State: " + mState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
|
|
||||||
* kill the current operation and forcibly start the next.
|
|
||||||
*/
|
|
||||||
private static final class CancellationWatchdog implements Runnable {
|
|
||||||
static final int DELAY_MS = 3000;
|
|
||||||
|
|
||||||
final String tag;
|
|
||||||
final Operation operation;
|
|
||||||
CancellationWatchdog(String tag, Operation operation) {
|
|
||||||
this.tag = tag;
|
|
||||||
this.operation = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (operation.mState != Operation.STATE_FINISHED) {
|
|
||||||
Slog.e(tag, "[Watchdog Triggered]: " + operation);
|
|
||||||
operation.mClientMonitor.mCallback
|
|
||||||
.onClientFinished(operation.mClientMonitor, false /* success */);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class CrashState {
|
private static final class CrashState {
|
||||||
static final int NUM_ENTRIES = 10;
|
static final int NUM_ENTRIES = 10;
|
||||||
final String timestamp;
|
final String timestamp;
|
||||||
@@ -263,10 +146,9 @@ public class BiometricScheduler {
|
|||||||
private final @SensorType int mSensorType;
|
private final @SensorType int mSensorType;
|
||||||
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
|
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
|
||||||
@NonNull private final IBiometricService mBiometricService;
|
@NonNull private final IBiometricService mBiometricService;
|
||||||
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
|
@NonNull protected final Handler mHandler;
|
||||||
@NonNull private final InternalCallback mInternalCallback;
|
@VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
|
||||||
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
|
@VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
|
||||||
@VisibleForTesting @Nullable Operation mCurrentOperation;
|
|
||||||
@NonNull private final ArrayDeque<CrashState> mCrashStates;
|
@NonNull private final ArrayDeque<CrashState> mCrashStates;
|
||||||
|
|
||||||
private int mTotalOperationsHandled;
|
private int mTotalOperationsHandled;
|
||||||
@@ -277,7 +159,7 @@ public class BiometricScheduler {
|
|||||||
// Internal callback, notified when an operation is complete. Notifies the requester
|
// Internal callback, notified when an operation is complete. Notifies the requester
|
||||||
// that the operation is complete, before performing internal scheduler work (such as
|
// that the operation is complete, before performing internal scheduler work (such as
|
||||||
// starting the next client).
|
// starting the next client).
|
||||||
public class InternalCallback implements BaseClientMonitor.Callback {
|
private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
|
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
|
||||||
Slog.d(getTag(), "[Started] " + clientMonitor);
|
Slog.d(getTag(), "[Started] " + clientMonitor);
|
||||||
@@ -286,16 +168,11 @@ public class BiometricScheduler {
|
|||||||
mCoexCoordinator.addAuthenticationClient(mSensorType,
|
mCoexCoordinator.addAuthenticationClient(mSensorType,
|
||||||
(AuthenticationClient<?>) clientMonitor);
|
(AuthenticationClient<?>) clientMonitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentOperation.mClientCallback != null) {
|
|
||||||
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
|
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
clientMonitor.destroy();
|
|
||||||
if (mCurrentOperation == null) {
|
if (mCurrentOperation == null) {
|
||||||
Slog.e(getTag(), "[Finishing] " + clientMonitor
|
Slog.e(getTag(), "[Finishing] " + clientMonitor
|
||||||
+ " but current operation is null, success: " + success
|
+ " but current operation is null, success: " + success
|
||||||
@@ -303,9 +180,9 @@ public class BiometricScheduler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientMonitor != mCurrentOperation.mClientMonitor) {
|
if (!mCurrentOperation.isFor(clientMonitor)) {
|
||||||
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
|
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
|
||||||
+ " current: " + mCurrentOperation.mClientMonitor);
|
+ " current: " + mCurrentOperation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,36 +192,33 @@ public class BiometricScheduler {
|
|||||||
(AuthenticationClient<?>) clientMonitor);
|
(AuthenticationClient<?>) clientMonitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentOperation.mState = Operation.STATE_FINISHED;
|
|
||||||
|
|
||||||
if (mCurrentOperation.mClientCallback != null) {
|
|
||||||
mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mGestureAvailabilityDispatcher != null) {
|
if (mGestureAvailabilityDispatcher != null) {
|
||||||
mGestureAvailabilityDispatcher.markSensorActive(
|
mGestureAvailabilityDispatcher.markSensorActive(
|
||||||
mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
|
mCurrentOperation.getSensorId(), false /* active */);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mRecentOperations.size() >= mRecentOperationsLimit) {
|
if (mRecentOperations.size() >= mRecentOperationsLimit) {
|
||||||
mRecentOperations.remove(0);
|
mRecentOperations.remove(0);
|
||||||
}
|
}
|
||||||
mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
|
mRecentOperations.add(mCurrentOperation.getProtoEnum());
|
||||||
mCurrentOperation = null;
|
mCurrentOperation = null;
|
||||||
mTotalOperationsHandled++;
|
mTotalOperationsHandled++;
|
||||||
startNextOperationIfIdle();
|
startNextOperationIfIdle();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
|
BiometricScheduler(@NonNull String tag,
|
||||||
|
@NonNull Handler handler,
|
||||||
|
@SensorType int sensorType,
|
||||||
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
||||||
@NonNull IBiometricService biometricService, int recentOperationsLimit,
|
@NonNull IBiometricService biometricService,
|
||||||
|
int recentOperationsLimit,
|
||||||
@NonNull CoexCoordinator coexCoordinator) {
|
@NonNull CoexCoordinator coexCoordinator) {
|
||||||
mBiometricTag = tag;
|
mBiometricTag = tag;
|
||||||
|
mHandler = handler;
|
||||||
mSensorType = sensorType;
|
mSensorType = sensorType;
|
||||||
mInternalCallback = new InternalCallback();
|
|
||||||
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
|
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
|
||||||
mPendingOperations = new ArrayDeque<>();
|
mPendingOperations = new ArrayDeque<>();
|
||||||
mBiometricService = biometricService;
|
mBiometricService = biometricService;
|
||||||
@@ -356,24 +230,26 @@ public class BiometricScheduler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new scheduler.
|
* Creates a new scheduler.
|
||||||
|
*
|
||||||
* @param tag for the specific instance of the scheduler. Should be unique.
|
* @param tag for the specific instance of the scheduler. Should be unique.
|
||||||
|
* @param handler handler for callbacks (all methods of this class must be called on the
|
||||||
|
* thread associated with this handler)
|
||||||
* @param sensorType the sensorType that this scheduler is handling.
|
* @param sensorType the sensorType that this scheduler is handling.
|
||||||
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
|
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
|
||||||
* (such as fingerprint swipe).
|
* (such as fingerprint swipe).
|
||||||
*/
|
*/
|
||||||
public BiometricScheduler(@NonNull String tag,
|
public BiometricScheduler(@NonNull String tag,
|
||||||
|
@NonNull Handler handler,
|
||||||
@SensorType int sensorType,
|
@SensorType int sensorType,
|
||||||
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
||||||
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
|
this(tag, handler, sensorType, gestureAvailabilityDispatcher,
|
||||||
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
|
IBiometricService.Stub.asInterface(
|
||||||
CoexCoordinator.getInstance());
|
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
|
||||||
|
LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@VisibleForTesting
|
||||||
* @return A reference to the internal callback that should be invoked whenever the scheduler
|
public BaseClientMonitor.Callback getInternalCallback() {
|
||||||
* needs to (e.g. client started, client finished).
|
|
||||||
*/
|
|
||||||
@NonNull protected InternalCallback getInternalCallback() {
|
|
||||||
return mInternalCallback;
|
return mInternalCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,72 +268,46 @@ public class BiometricScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mCurrentOperation = mPendingOperations.poll();
|
mCurrentOperation = mPendingOperations.poll();
|
||||||
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
|
|
||||||
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
|
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
|
||||||
|
|
||||||
// If the operation at the front of the queue has been marked for cancellation, send
|
// If the operation at the front of the queue has been marked for cancellation, send
|
||||||
// ERROR_CANCELED. No need to start this client.
|
// ERROR_CANCELED. No need to start this client.
|
||||||
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
|
if (mCurrentOperation.isMarkedCanceling()) {
|
||||||
Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
|
Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
|
||||||
if (!(currentClient instanceof Interruptable)) {
|
mCurrentOperation.cancel(mHandler, mInternalCallback);
|
||||||
throw new IllegalStateException("Mis-implemented client or scheduler, "
|
|
||||||
+ "trying to cancel non-interruptable operation: " + mCurrentOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Interruptable interruptable = (Interruptable) currentClient;
|
|
||||||
interruptable.cancelWithoutStarting(getInternalCallback());
|
|
||||||
// Now we wait for the client to send its FinishCallback, which kicks off the next
|
// Now we wait for the client to send its FinishCallback, which kicks off the next
|
||||||
// operation.
|
// operation.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mGestureAvailabilityDispatcher != null
|
if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
|
||||||
&& mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
|
|
||||||
mGestureAvailabilityDispatcher.markSensorActive(
|
mGestureAvailabilityDispatcher.markSensorActive(
|
||||||
mCurrentOperation.mClientMonitor.getSensorId(),
|
mCurrentOperation.getSensorId(), true /* active */);
|
||||||
true /* active */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not all operations start immediately. BiometricPrompt waits for its operation
|
// Not all operations start immediately. BiometricPrompt waits for its operation
|
||||||
// to arrive at the head of the queue, before pinging it to start.
|
// to arrive at the head of the queue, before pinging it to start.
|
||||||
final boolean shouldStartNow = currentClient.getCookie() == 0;
|
final int cookie = mCurrentOperation.isReadyToStart();
|
||||||
if (shouldStartNow) {
|
if (cookie == 0) {
|
||||||
if (mCurrentOperation.isUnstartableHalOperation()) {
|
if (!mCurrentOperation.start(mInternalCallback)) {
|
||||||
final HalClientMonitor<?> halClientMonitor =
|
|
||||||
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
|
|
||||||
// Note down current length of queue
|
// Note down current length of queue
|
||||||
final int pendingOperationsLength = mPendingOperations.size();
|
final int pendingOperationsLength = mPendingOperations.size();
|
||||||
final Operation lastOperation = mPendingOperations.peekLast();
|
final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
|
||||||
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
|
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
|
||||||
+ ". Last pending operation: " + lastOperation);
|
+ ". Last pending operation: " + lastOperation);
|
||||||
|
|
||||||
// For current operations, 1) unableToStart, which notifies the caller-side, then
|
|
||||||
// 2) notify operation's callback, to notify applicable system service that the
|
|
||||||
// operation failed.
|
|
||||||
halClientMonitor.unableToStart();
|
|
||||||
if (mCurrentOperation.mClientCallback != null) {
|
|
||||||
mCurrentOperation.mClientCallback.onClientFinished(
|
|
||||||
mCurrentOperation.mClientMonitor, false /* success */);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then for each operation currently in the pending queue at the time of this
|
// Then for each operation currently in the pending queue at the time of this
|
||||||
// failure, do the same as above. Otherwise, it's possible that something like
|
// failure, do the same as above. Otherwise, it's possible that something like
|
||||||
// setActiveUser fails, but then authenticate (for the wrong user) is invoked.
|
// setActiveUser fails, but then authenticate (for the wrong user) is invoked.
|
||||||
for (int i = 0; i < pendingOperationsLength; i++) {
|
for (int i = 0; i < pendingOperationsLength; i++) {
|
||||||
final Operation operation = mPendingOperations.pollFirst();
|
final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
|
||||||
if (operation == null) {
|
if (operation != null) {
|
||||||
|
Slog.w(getTag(), "[Aborting Operation] " + operation);
|
||||||
|
operation.abort();
|
||||||
|
} else {
|
||||||
Slog.e(getTag(), "Null operation, index: " + i
|
Slog.e(getTag(), "Null operation, index: " + i
|
||||||
+ ", expected length: " + pendingOperationsLength);
|
+ ", expected length: " + pendingOperationsLength);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (operation.isHalOperation()) {
|
|
||||||
((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
|
|
||||||
}
|
|
||||||
if (operation.mClientCallback != null) {
|
|
||||||
operation.mClientCallback.onClientFinished(operation.mClientMonitor,
|
|
||||||
false /* success */);
|
|
||||||
}
|
|
||||||
Slog.w(getTag(), "[Aborted Operation] " + operation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible that during cleanup a new set of operations came in. We can try to
|
// It's possible that during cleanup a new set of operations came in. We can try to
|
||||||
@@ -465,25 +315,20 @@ public class BiometricScheduler {
|
|||||||
// actually be multiple operations (i.e. updateActiveUser + authenticate).
|
// actually be multiple operations (i.e. updateActiveUser + authenticate).
|
||||||
mCurrentOperation = null;
|
mCurrentOperation = null;
|
||||||
startNextOperationIfIdle();
|
startNextOperationIfIdle();
|
||||||
} else {
|
|
||||||
Slog.d(getTag(), "[Starting] " + mCurrentOperation);
|
|
||||||
currentClient.start(getInternalCallback());
|
|
||||||
mCurrentOperation.mState = Operation.STATE_STARTED;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
mBiometricService.onReadyForAuthentication(currentClient.getCookie());
|
mBiometricService.onReadyForAuthentication(cookie);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
|
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
|
||||||
}
|
}
|
||||||
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
|
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
|
||||||
mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the {@link #mCurrentOperation} if
|
* Starts the {@link #mCurrentOperation} if
|
||||||
* 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
|
* 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
|
||||||
* 2) its cookie matches this cookie
|
* 2) its cookie matches this cookie
|
||||||
*
|
*
|
||||||
* This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
|
* This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
|
||||||
@@ -499,45 +344,13 @@ public class BiometricScheduler {
|
|||||||
Slog.e(getTag(), "Current operation is null");
|
Slog.e(getTag(), "Current operation is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
|
|
||||||
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
|
|
||||||
Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
|
|
||||||
+ mCurrentOperation);
|
|
||||||
// This should trigger the internal onClientFinished callback, which clears the
|
|
||||||
// operation and starts the next one.
|
|
||||||
final ErrorConsumer errorConsumer =
|
|
||||||
(ErrorConsumer) mCurrentOperation.mClientMonitor;
|
|
||||||
errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
|
|
||||||
0 /* vendorCode */);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
|
|
||||||
+ ", expected STATE_WAITING_FOR_COOKIE");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
|
|
||||||
Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
|
|
||||||
+ ", received: " + cookie);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCurrentOperation.isUnstartableHalOperation()) {
|
if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
|
||||||
|
Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
|
||||||
|
} else {
|
||||||
Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
|
Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
|
||||||
// This is BiometricPrompt trying to auth but something's wrong with the HAL.
|
|
||||||
final HalClientMonitor<?> halClientMonitor =
|
|
||||||
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
|
|
||||||
halClientMonitor.unableToStart();
|
|
||||||
if (mCurrentOperation.mClientCallback != null) {
|
|
||||||
mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
|
|
||||||
false /* success */);
|
|
||||||
}
|
|
||||||
mCurrentOperation = null;
|
mCurrentOperation = null;
|
||||||
startNextOperationIfIdle();
|
startNextOperationIfIdle();
|
||||||
} else {
|
|
||||||
Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
|
|
||||||
mCurrentOperation.mState = Operation.STATE_STARTED;
|
|
||||||
mCurrentOperation.mClientMonitor.start(getInternalCallback());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,17 +375,13 @@ public class BiometricScheduler {
|
|||||||
// pending clients as canceling. Once they reach the head of the queue, the scheduler will
|
// pending clients as canceling. Once they reach the head of the queue, the scheduler will
|
||||||
// send ERROR_CANCELED and skip the operation.
|
// send ERROR_CANCELED and skip the operation.
|
||||||
if (clientMonitor.interruptsPrecedingClients()) {
|
if (clientMonitor.interruptsPrecedingClients()) {
|
||||||
for (Operation operation : mPendingOperations) {
|
for (BiometricSchedulerOperation operation : mPendingOperations) {
|
||||||
if (operation.mClientMonitor instanceof Interruptable
|
Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
|
||||||
&& operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
|
operation.markCanceling();
|
||||||
Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
|
|
||||||
+ operation.mClientMonitor);
|
|
||||||
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mPendingOperations.add(new Operation(clientMonitor, clientCallback));
|
mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
|
||||||
Slog.d(getTag(), "[Added] " + clientMonitor
|
Slog.d(getTag(), "[Added] " + clientMonitor
|
||||||
+ ", new queue size: " + mPendingOperations.size());
|
+ ", new queue size: " + mPendingOperations.size());
|
||||||
|
|
||||||
@@ -580,67 +389,34 @@ public class BiometricScheduler {
|
|||||||
// cancellable, start the cancellation process.
|
// cancellable, start the cancellation process.
|
||||||
if (clientMonitor.interruptsPrecedingClients()
|
if (clientMonitor.interruptsPrecedingClients()
|
||||||
&& mCurrentOperation != null
|
&& mCurrentOperation != null
|
||||||
&& mCurrentOperation.mClientMonitor instanceof Interruptable
|
&& mCurrentOperation.isInterruptable()
|
||||||
&& mCurrentOperation.mState == Operation.STATE_STARTED) {
|
&& mCurrentOperation.isStarted()) {
|
||||||
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
|
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
|
||||||
cancelInternal(mCurrentOperation);
|
mCurrentOperation.cancel(mHandler, mInternalCallback);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
startNextOperationIfIdle();
|
startNextOperationIfIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelInternal(Operation operation) {
|
|
||||||
if (operation != mCurrentOperation) {
|
|
||||||
Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(operation.mClientMonitor instanceof Interruptable)) {
|
|
||||||
Slog.w(getTag(), "Operation not interruptable: " + operation);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (operation.mState == Operation.STATE_STARTED_CANCELING) {
|
|
||||||
Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
|
|
||||||
Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
|
|
||||||
// We can set it to null immediately, since the HAL was never notified to start.
|
|
||||||
if (mCurrentOperation != null) {
|
|
||||||
mCurrentOperation.mClientMonitor.destroy();
|
|
||||||
}
|
|
||||||
mCurrentOperation = null;
|
|
||||||
startNextOperationIfIdle();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
|
|
||||||
final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
|
|
||||||
interruptable.cancel();
|
|
||||||
operation.mState = Operation.STATE_STARTED_CANCELING;
|
|
||||||
|
|
||||||
// Add a watchdog. If the HAL does not acknowledge within the timeout, we will
|
|
||||||
// forcibly finish this client.
|
|
||||||
mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
|
|
||||||
CancellationWatchdog.DELAY_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to cancel enrollment.
|
* Requests to cancel enrollment.
|
||||||
* @param token from the caller, should match the token passed in when requesting enrollment
|
* @param token from the caller, should match the token passed in when requesting enrollment
|
||||||
*/
|
*/
|
||||||
public void cancelEnrollment(IBinder token) {
|
public void cancelEnrollment(IBinder token, long requestId) {
|
||||||
if (mCurrentOperation == null) {
|
Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
|
||||||
Slog.e(getTag(), "Unable to cancel enrollment, null operation");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
|
|
||||||
final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
|
|
||||||
if (!isEnrolling || !tokenMatches) {
|
|
||||||
Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
|
|
||||||
+ " tokenMatches: " + tokenMatches);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelInternal(mCurrentOperation);
|
if (mCurrentOperation != null
|
||||||
|
&& canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
|
||||||
|
Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
|
||||||
|
mCurrentOperation.cancel(mHandler, mInternalCallback);
|
||||||
|
} else {
|
||||||
|
for (BiometricSchedulerOperation operation : mPendingOperations) {
|
||||||
|
if (canCancelEnrollOperation(operation, token, requestId)) {
|
||||||
|
Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
|
||||||
|
operation.markCanceling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -649,62 +425,42 @@ public class BiometricScheduler {
|
|||||||
* @param requestId the id returned when requesting authentication
|
* @param requestId the id returned when requesting authentication
|
||||||
*/
|
*/
|
||||||
public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
|
public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
|
||||||
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
|
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
|
||||||
+ " current: " + mCurrentOperation
|
|
||||||
+ " stack size: " + mPendingOperations.size());
|
|
||||||
|
|
||||||
if (mCurrentOperation != null
|
if (mCurrentOperation != null
|
||||||
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) {
|
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) {
|
||||||
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
|
Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
|
||||||
cancelInternal(mCurrentOperation);
|
mCurrentOperation.cancel(mHandler, mInternalCallback);
|
||||||
} else {
|
} else {
|
||||||
// Look through the current queue for all authentication clients for the specified
|
for (BiometricSchedulerOperation operation : mPendingOperations) {
|
||||||
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
|
|
||||||
// all of them, instead of just the first one, since the API surface currently doesn't
|
|
||||||
// allow us to distinguish between multiple authentication requests from the same
|
|
||||||
// process. However, this generally does not happen anyway, and would be a class of
|
|
||||||
// bugs on its own.
|
|
||||||
for (Operation operation : mPendingOperations) {
|
|
||||||
if (canCancelAuthOperation(operation, token, requestId)) {
|
if (canCancelAuthOperation(operation, token, requestId)) {
|
||||||
Slog.d(getTag(), "Marking " + operation
|
Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
|
||||||
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
|
operation.markCanceling();
|
||||||
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canCancelAuthOperation(Operation operation, IBinder token,
|
private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
|
||||||
long requestId) {
|
IBinder token, long requestId) {
|
||||||
|
return operation.isEnrollOperation()
|
||||||
|
&& operation.isMatchingToken(token)
|
||||||
|
&& operation.isMatchingRequestId(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
|
||||||
|
IBinder token, long requestId) {
|
||||||
// TODO: restrict callers that can cancel without requestId (negative value)?
|
// TODO: restrict callers that can cancel without requestId (negative value)?
|
||||||
return isAuthenticationOrDetectionOperation(operation)
|
return operation.isAuthenticationOrDetectionOperation()
|
||||||
&& operation.mClientMonitor.getToken() == token
|
&& operation.isMatchingToken(token)
|
||||||
&& isMatchingRequestId(operation, requestId);
|
&& operation.isMatchingRequestId(requestId);
|
||||||
}
|
|
||||||
|
|
||||||
// By default, monitors are not associated with a request id to retain the original
|
|
||||||
// behavior (i.e. if no requestId is explicitly set then assume it matches)
|
|
||||||
private static boolean isMatchingRequestId(Operation operation, long requestId) {
|
|
||||||
return !operation.mClientMonitor.hasRequestId()
|
|
||||||
|| operation.mClientMonitor.getRequestId() == requestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
|
|
||||||
final boolean isAuthentication =
|
|
||||||
operation.mClientMonitor instanceof AuthenticationConsumer;
|
|
||||||
final boolean isDetection =
|
|
||||||
operation.mClientMonitor instanceof DetectionConsumer;
|
|
||||||
return isAuthentication || isDetection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current operation
|
* @return the current operation
|
||||||
*/
|
*/
|
||||||
public BaseClientMonitor getCurrentClient() {
|
public BaseClientMonitor getCurrentClient() {
|
||||||
if (mCurrentOperation == null) {
|
return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return mCurrentOperation.mClientMonitor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentPendingCount() {
|
public int getCurrentPendingCount() {
|
||||||
@@ -719,7 +475,7 @@ public class BiometricScheduler {
|
|||||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
|
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
|
||||||
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
|
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
|
||||||
final List<String> pendingOperations = new ArrayList<>();
|
final List<String> pendingOperations = new ArrayList<>();
|
||||||
for (Operation operation : mPendingOperations) {
|
for (BiometricSchedulerOperation operation : mPendingOperations) {
|
||||||
pendingOperations.add(operation.toString());
|
pendingOperations.add(operation.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,7 +491,7 @@ public class BiometricScheduler {
|
|||||||
pw.println("Type: " + mSensorType);
|
pw.println("Type: " + mSensorType);
|
||||||
pw.println("Current operation: " + mCurrentOperation);
|
pw.println("Current operation: " + mCurrentOperation);
|
||||||
pw.println("Pending operations: " + mPendingOperations.size());
|
pw.println("Pending operations: " + mPendingOperations.size());
|
||||||
for (Operation operation : mPendingOperations) {
|
for (BiometricSchedulerOperation operation : mPendingOperations) {
|
||||||
pw.println("Pending operation: " + operation);
|
pw.println("Pending operation: " + operation);
|
||||||
}
|
}
|
||||||
for (CrashState crashState : mCrashStates) {
|
for (CrashState crashState : mCrashStates) {
|
||||||
@@ -746,7 +502,7 @@ public class BiometricScheduler {
|
|||||||
public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
|
public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
|
||||||
final ProtoOutputStream proto = new ProtoOutputStream();
|
final ProtoOutputStream proto = new ProtoOutputStream();
|
||||||
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
|
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
|
||||||
? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
|
? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
|
||||||
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
|
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
|
||||||
|
|
||||||
if (!mRecentOperations.isEmpty()) {
|
if (!mRecentOperations.isEmpty()) {
|
||||||
@@ -771,6 +527,7 @@ public class BiometricScheduler {
|
|||||||
* HAL dies.
|
* HAL dies.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
Slog.d(getTag(), "Resetting scheduler");
|
||||||
mPendingOperations.clear();
|
mPendingOperations.clear();
|
||||||
mCurrentOperation = null;
|
mCurrentOperation = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
|
||||||
* if the client is still waiting in the pending queue and got notified that a subsequent
|
* if the client is still waiting in the pending queue and got notified that a subsequent
|
||||||
* operation is preempting it.
|
* operation is preempting it.
|
||||||
|
*
|
||||||
|
* This method must invoke
|
||||||
|
* {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
|
||||||
|
* given callback (with success).
|
||||||
|
*
|
||||||
* @param callback invoked when the operation is completed.
|
* @param callback invoked when the operation is completed.
|
||||||
*/
|
*/
|
||||||
void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
|
void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
|
||||||
|
|||||||
@@ -16,10 +16,13 @@
|
|||||||
|
|
||||||
package com.android.server.biometrics.sensors;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.biometrics.IBiometricService;
|
import android.hardware.biometrics.IBiometricService;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -68,9 +71,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Slog.d(getTag(), "[Client finished] "
|
Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
|
||||||
+ clientMonitor + ", success: " + success);
|
if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
|
||||||
if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) {
|
|
||||||
mCurrentOperation = null;
|
mCurrentOperation = null;
|
||||||
startNextOperationIfIdle();
|
startNextOperationIfIdle();
|
||||||
} else {
|
} else {
|
||||||
@@ -83,26 +85,31 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
|
UserAwareBiometricScheduler(@NonNull String tag,
|
||||||
|
@NonNull Handler handler,
|
||||||
|
@SensorType int sensorType,
|
||||||
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
||||||
@NonNull IBiometricService biometricService,
|
@NonNull IBiometricService biometricService,
|
||||||
@NonNull CurrentUserRetriever currentUserRetriever,
|
@NonNull CurrentUserRetriever currentUserRetriever,
|
||||||
@NonNull UserSwitchCallback userSwitchCallback,
|
@NonNull UserSwitchCallback userSwitchCallback,
|
||||||
@NonNull CoexCoordinator coexCoordinator) {
|
@NonNull CoexCoordinator coexCoordinator) {
|
||||||
super(tag, sensorType, gestureAvailabilityDispatcher, biometricService,
|
super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService,
|
||||||
LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
|
LOG_NUM_RECENT_OPERATIONS, coexCoordinator);
|
||||||
|
|
||||||
mCurrentUserRetriever = currentUserRetriever;
|
mCurrentUserRetriever = currentUserRetriever;
|
||||||
mUserSwitchCallback = userSwitchCallback;
|
mUserSwitchCallback = userSwitchCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType,
|
public UserAwareBiometricScheduler(@NonNull String tag,
|
||||||
|
@NonNull Handler handler,
|
||||||
|
@SensorType int sensorType,
|
||||||
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
|
||||||
@NonNull CurrentUserRetriever currentUserRetriever,
|
@NonNull CurrentUserRetriever currentUserRetriever,
|
||||||
@NonNull UserSwitchCallback userSwitchCallback) {
|
@NonNull UserSwitchCallback userSwitchCallback) {
|
||||||
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
|
this(tag, handler, sensorType, gestureAvailabilityDispatcher,
|
||||||
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
|
IBiometricService.Stub.asInterface(
|
||||||
userSwitchCallback, CoexCoordinator.getInstance());
|
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
|
||||||
|
currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,7 +129,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
|
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
|
||||||
final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
|
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
|
||||||
|
|
||||||
if (nextUserId == currentUserId) {
|
if (nextUserId == currentUserId) {
|
||||||
super.startNextOperationIfIdle();
|
super.startNextOperationIfIdle();
|
||||||
@@ -133,8 +140,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
|
|||||||
new ClientFinishedCallback(startClient);
|
new ClientFinishedCallback(startClient);
|
||||||
|
|
||||||
Slog.d(getTag(), "[Starting User] " + startClient);
|
Slog.d(getTag(), "[Starting User] " + startClient);
|
||||||
mCurrentOperation = new Operation(
|
mCurrentOperation = new BiometricSchedulerOperation(
|
||||||
startClient, finishedCallback, Operation.STATE_STARTED);
|
startClient, finishedCallback, STATE_STARTED);
|
||||||
startClient.start(finishedCallback);
|
startClient.start(finishedCallback);
|
||||||
} else {
|
} else {
|
||||||
if (mStopUserClient != null) {
|
if (mStopUserClient != null) {
|
||||||
@@ -147,8 +154,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
|
|||||||
|
|
||||||
Slog.d(getTag(), "[Stopping User] current: " + currentUserId
|
Slog.d(getTag(), "[Stopping User] current: " + currentUserId
|
||||||
+ ", next: " + nextUserId + ". " + mStopUserClient);
|
+ ", next: " + nextUserId + ". " + mStopUserClient);
|
||||||
mCurrentOperation = new Operation(
|
mCurrentOperation = new BiometricSchedulerOperation(
|
||||||
mStopUserClient, finishedCallback, Operation.STATE_STARTED);
|
mStopUserClient, finishedCallback, STATE_STARTED);
|
||||||
mStopUserClient.start(finishedCallback);
|
mStopUserClient.start(finishedCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ public class FaceService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
|
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
|
||||||
final IFaceServiceReceiver receiver, final String opPackageName,
|
final IFaceServiceReceiver receiver, final String opPackageName,
|
||||||
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
|
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
|
||||||
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
||||||
@@ -221,23 +221,24 @@ public class FaceService extends SystemService {
|
|||||||
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
Slog.w(TAG, "Null provider for enroll");
|
Slog.w(TAG, "Null provider for enroll");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
|
return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
|
||||||
receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
|
receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
|
public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
|
||||||
final IFaceServiceReceiver receiver, final String opPackageName,
|
final IFaceServiceReceiver receiver, final String opPackageName,
|
||||||
final int[] disabledFeatures) {
|
final int[] disabledFeatures) {
|
||||||
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
||||||
// TODO(b/145027036): Implement this.
|
// TODO(b/145027036): Implement this.
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void cancelEnrollment(final IBinder token) {
|
public void cancelEnrollment(final IBinder token, long requestId) {
|
||||||
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
|
||||||
|
|
||||||
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
||||||
@@ -246,7 +247,7 @@ public class FaceService extends SystemService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.second.cancelEnrollment(provider.first, token);
|
provider.second.cancelEnrollment(provider.first, token, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
@@ -624,7 +625,7 @@ public class FaceService extends SystemService {
|
|||||||
private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
|
private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
|
||||||
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
|
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
|
||||||
mServiceProviders.add(
|
mServiceProviders.add(
|
||||||
new Face10(getContext(), hidlSensor, mLockoutResetDispatcher));
|
Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,12 +94,12 @@ public interface ServiceProvider {
|
|||||||
void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
|
void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
|
||||||
@NonNull String opPackageName, long challenge);
|
@NonNull String opPackageName, long challenge);
|
||||||
|
|
||||||
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
|
long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
|
||||||
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
|
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
|
||||||
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
|
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
|
||||||
boolean debugConsent);
|
boolean debugConsent);
|
||||||
|
|
||||||
void cancelEnrollment(int sensorId, @NonNull IBinder token);
|
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
|
||||||
|
|
||||||
long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
|
long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
|
||||||
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
|
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
|
||||||
|
|||||||
@@ -82,13 +82,14 @@ public class FaceEnrollClient extends EnrollClient<ISession> {
|
|||||||
|
|
||||||
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
|
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
|
||||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||||
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
|
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
|
||||||
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
|
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
|
||||||
@Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
|
@Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser,
|
||||||
boolean debugConsent) {
|
boolean debugConsent) {
|
||||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
|
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
|
||||||
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
||||||
false /* shouldVibrate */);
|
false /* shouldVibrate */);
|
||||||
|
setRequestId(requestId);
|
||||||
mEnrollIgnoreList = getContext().getResources()
|
mEnrollIgnoreList = getContext().getResources()
|
||||||
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
||||||
mEnrollIgnoreListVendor = getContext().getResources()
|
mEnrollIgnoreListVendor = getContext().getResources()
|
||||||
|
|||||||
@@ -327,17 +327,18 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
|
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
|
||||||
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
|
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
|
||||||
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
|
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
|
||||||
@Nullable Surface previewSurface, boolean debugConsent) {
|
@Nullable Surface previewSurface, boolean debugConsent) {
|
||||||
|
final long id = mRequestCounter.incrementAndGet();
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
final int maxTemplatesPerUser = mSensors.get(
|
final int maxTemplatesPerUser = mSensors.get(
|
||||||
sensorId).getSensorProperties().maxEnrollmentsPerUser;
|
sensorId).getSensorProperties().maxEnrollmentsPerUser;
|
||||||
final FaceEnrollClient client = new FaceEnrollClient(mContext,
|
final FaceEnrollClient client = new FaceEnrollClient(mContext,
|
||||||
mSensors.get(sensorId).getLazySession(), token,
|
mSensors.get(sensorId).getLazySession(), token,
|
||||||
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
||||||
opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
|
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
|
||||||
ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
|
ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
|
||||||
debugConsent);
|
debugConsent);
|
||||||
scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
|
scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
|
||||||
@@ -351,11 +352,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
|
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
|
||||||
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
|
mHandler.post(() ->
|
||||||
|
mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ public class Sensor {
|
|||||||
mToken = new Binder();
|
mToken = new Binder();
|
||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mSensorProperties = sensorProperties;
|
mSensorProperties = sensorProperties;
|
||||||
mScheduler = new UserAwareBiometricScheduler(tag,
|
mScheduler = new UserAwareBiometricScheduler(tag, mHandler,
|
||||||
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
|
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
|
||||||
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
|
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
|
||||||
new UserAwareBiometricScheduler.UserSwitchCallback() {
|
new UserAwareBiometricScheduler.UserSwitchCallback() {
|
||||||
|
|||||||
@@ -333,12 +333,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
|
|||||||
Face10(@NonNull Context context,
|
Face10(@NonNull Context context,
|
||||||
@NonNull FaceSensorPropertiesInternal sensorProps,
|
@NonNull FaceSensorPropertiesInternal sensorProps,
|
||||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
||||||
|
@NonNull Handler handler,
|
||||||
@NonNull BiometricScheduler scheduler) {
|
@NonNull BiometricScheduler scheduler) {
|
||||||
mSensorProperties = sensorProps;
|
mSensorProperties = sensorProps;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mSensorId = sensorProps.sensorId;
|
mSensorId = sensorProps.sensorId;
|
||||||
mScheduler = scheduler;
|
mScheduler = scheduler;
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
mHandler = handler;
|
||||||
mUsageStats = new UsageStats(context);
|
mUsageStats = new UsageStats(context);
|
||||||
mAuthenticatorIds = new HashMap<>();
|
mAuthenticatorIds = new HashMap<>();
|
||||||
mLazyDaemon = Face10.this::getDaemon;
|
mLazyDaemon = Face10.this::getDaemon;
|
||||||
@@ -357,10 +358,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps,
|
public static Face10 newInstance(@NonNull Context context,
|
||||||
|
@NonNull FaceSensorPropertiesInternal sensorProps,
|
||||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
|
||||||
this(context, sensorProps, lockoutResetDispatcher,
|
final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
|
return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
|
||||||
|
new BiometricScheduler(TAG, handler, BiometricScheduler.SENSOR_TYPE_FACE,
|
||||||
null /* gestureAvailabilityTracker */));
|
null /* gestureAvailabilityTracker */));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,10 +576,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
|
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
|
||||||
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
|
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
|
||||||
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
|
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
|
||||||
@Nullable Surface previewSurface, boolean debugConsent) {
|
@Nullable Surface previewSurface, boolean debugConsent) {
|
||||||
|
final long id = mRequestCounter.incrementAndGet();
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
scheduleUpdateActiveUserWithoutHandler(userId);
|
scheduleUpdateActiveUserWithoutHandler(userId);
|
||||||
|
|
||||||
@@ -584,7 +588,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
|
|||||||
|
|
||||||
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
|
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
|
||||||
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
||||||
opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
|
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
|
||||||
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
|
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
|
||||||
|
|
||||||
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
|
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
|
||||||
@@ -598,13 +602,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
|
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
|
||||||
mScheduler.cancelEnrollment(token);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -53,12 +53,13 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
|
|||||||
|
|
||||||
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
|
FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
|
||||||
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||||
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
|
@NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
|
||||||
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
|
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
|
||||||
@Nullable Surface previewSurface, int sensorId) {
|
@Nullable Surface previewSurface, int sensorId) {
|
||||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||||
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
|
||||||
false /* shouldVibrate */);
|
false /* shouldVibrate */);
|
||||||
|
setRequestId(requestId);
|
||||||
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
|
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
|
||||||
mEnrollIgnoreList = getContext().getResources()
|
mEnrollIgnoreList = getContext().getResources()
|
||||||
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ public class FingerprintService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
|
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
|
||||||
final int userId, final IFingerprintServiceReceiver receiver,
|
final int userId, final IFingerprintServiceReceiver receiver,
|
||||||
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
|
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
|
||||||
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
|
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
|
||||||
@@ -257,15 +257,15 @@ public class FingerprintService extends SystemService {
|
|||||||
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
Slog.w(TAG, "Null provider for enroll");
|
Slog.w(TAG, "Null provider for enroll");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
|
return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
|
||||||
receiver, opPackageName, enrollReason);
|
receiver, opPackageName, enrollReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // Binder call
|
@Override // Binder call
|
||||||
public void cancelEnrollment(final IBinder token) {
|
public void cancelEnrollment(final IBinder token, long requestId) {
|
||||||
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
|
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
|
||||||
|
|
||||||
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
|
||||||
@@ -274,7 +274,7 @@ public class FingerprintService extends SystemService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.second.cancelEnrollment(provider.first, token);
|
provider.second.cancelEnrollment(provider.first, token, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@@ -818,7 +818,7 @@ public class FingerprintService extends SystemService {
|
|||||||
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
|
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
|
||||||
} else {
|
} else {
|
||||||
fingerprint21 = Fingerprint21.newInstance(getContext(),
|
fingerprint21 = Fingerprint21.newInstance(getContext(),
|
||||||
mFingerprintStateCallback, hidlSensor,
|
mFingerprintStateCallback, hidlSensor, mHandler,
|
||||||
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
|
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
|
||||||
}
|
}
|
||||||
mServiceProviders.add(fingerprint21);
|
mServiceProviders.add(fingerprint21);
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ public interface ServiceProvider {
|
|||||||
/**
|
/**
|
||||||
* Schedules fingerprint enrollment.
|
* Schedules fingerprint enrollment.
|
||||||
*/
|
*/
|
||||||
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
|
long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
|
||||||
int userId, @NonNull IFingerprintServiceReceiver receiver,
|
int userId, @NonNull IFingerprintServiceReceiver receiver,
|
||||||
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
|
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
|
||||||
|
|
||||||
void cancelEnrollment(int sensorId, @NonNull IBinder token);
|
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
|
||||||
|
|
||||||
long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
|
long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
|
||||||
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
|
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
|
|||||||
private boolean mIsPointerDown;
|
private boolean mIsPointerDown;
|
||||||
|
|
||||||
FingerprintEnrollClient(@NonNull Context context,
|
FingerprintEnrollClient(@NonNull Context context,
|
||||||
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
|
@NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId,
|
||||||
@NonNull ClientMonitorCallbackConverter listener, int userId,
|
@NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||||
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
|
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
|
||||||
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
|
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
|
||||||
@@ -69,6 +69,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
|
|||||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||||
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
||||||
!sensorProps.isAnyUdfpsType() /* shouldVibrate */);
|
!sensorProps.isAnyUdfpsType() /* shouldVibrate */);
|
||||||
|
setRequestId(requestId);
|
||||||
mSensorProps = sensorProps;
|
mSensorProps = sensorProps;
|
||||||
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
|
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
|
||||||
mMaxTemplatesPerUser = maxTemplatesPerUser;
|
mMaxTemplatesPerUser = maxTemplatesPerUser;
|
||||||
|
|||||||
@@ -343,15 +343,16 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
|
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
|
||||||
@NonNull byte[] hardwareAuthToken, int userId,
|
@NonNull byte[] hardwareAuthToken, int userId,
|
||||||
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
|
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
|
||||||
@FingerprintManager.EnrollReason int enrollReason) {
|
@FingerprintManager.EnrollReason int enrollReason) {
|
||||||
|
final long id = mRequestCounter.incrementAndGet();
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
|
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
|
||||||
.maxEnrollmentsPerUser;
|
.maxEnrollmentsPerUser;
|
||||||
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
|
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
|
||||||
mSensors.get(sensorId).getLazySession(), token,
|
mSensors.get(sensorId).getLazySession(), token, id,
|
||||||
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
|
||||||
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
|
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
|
||||||
mSensors.get(sensorId).getSensorProperties(),
|
mSensors.get(sensorId).getSensorProperties(),
|
||||||
@@ -374,11 +375,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
|
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
|
||||||
mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
|
mHandler.post(() ->
|
||||||
|
mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ class Sensor {
|
|||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mSensorProperties = sensorProperties;
|
mSensorProperties = sensorProperties;
|
||||||
mLockoutCache = new LockoutCache();
|
mLockoutCache = new LockoutCache();
|
||||||
mScheduler = new UserAwareBiometricScheduler(tag,
|
mScheduler = new UserAwareBiometricScheduler(tag, handler,
|
||||||
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
|
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
|
||||||
gestureAvailabilityDispatcher,
|
gestureAvailabilityDispatcher,
|
||||||
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
|
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.IHwBinder;
|
import android.os.IHwBinder;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
@@ -320,7 +319,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
|||||||
Fingerprint21(@NonNull Context context,
|
Fingerprint21(@NonNull Context context,
|
||||||
@NonNull FingerprintStateCallback fingerprintStateCallback,
|
@NonNull FingerprintStateCallback fingerprintStateCallback,
|
||||||
@NonNull FingerprintSensorPropertiesInternal sensorProps,
|
@NonNull FingerprintSensorPropertiesInternal sensorProps,
|
||||||
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
|
@NonNull BiometricScheduler scheduler,
|
||||||
|
@NonNull Handler handler,
|
||||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
||||||
@NonNull HalResultController controller) {
|
@NonNull HalResultController controller) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -356,16 +356,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
|||||||
public static Fingerprint21 newInstance(@NonNull Context context,
|
public static Fingerprint21 newInstance(@NonNull Context context,
|
||||||
@NonNull FingerprintStateCallback fingerprintStateCallback,
|
@NonNull FingerprintStateCallback fingerprintStateCallback,
|
||||||
@NonNull FingerprintSensorPropertiesInternal sensorProps,
|
@NonNull FingerprintSensorPropertiesInternal sensorProps,
|
||||||
|
@NonNull Handler handler,
|
||||||
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
|
||||||
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
||||||
final Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
final BiometricScheduler scheduler =
|
final BiometricScheduler scheduler =
|
||||||
new BiometricScheduler(TAG,
|
new BiometricScheduler(TAG, handler,
|
||||||
BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
|
BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
|
||||||
gestureAvailabilityDispatcher);
|
gestureAvailabilityDispatcher);
|
||||||
final HalResultController controller = new HalResultController(sensorProps.sensorId,
|
final HalResultController controller = new HalResultController(sensorProps.sensorId,
|
||||||
context, handler,
|
context, handler, scheduler);
|
||||||
scheduler);
|
|
||||||
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
|
return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
|
||||||
lockoutResetDispatcher, controller);
|
lockoutResetDispatcher, controller);
|
||||||
}
|
}
|
||||||
@@ -558,18 +557,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
|
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
|
||||||
@NonNull byte[] hardwareAuthToken, int userId,
|
@NonNull byte[] hardwareAuthToken, int userId,
|
||||||
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
|
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
|
||||||
@FingerprintManager.EnrollReason int enrollReason) {
|
@FingerprintManager.EnrollReason int enrollReason) {
|
||||||
|
final long id = mRequestCounter.incrementAndGet();
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
scheduleUpdateActiveUserWithoutHandler(userId);
|
scheduleUpdateActiveUserWithoutHandler(userId);
|
||||||
|
|
||||||
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
|
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
|
||||||
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
|
mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
|
||||||
hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
|
userId, hardwareAuthToken, opPackageName,
|
||||||
ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
|
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
|
||||||
mSidefpsController, enrollReason);
|
mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
|
||||||
|
enrollReason);
|
||||||
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
|
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
|
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
|
||||||
@@ -588,13 +589,12 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
|
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
|
||||||
mScheduler.cancelEnrollment(token);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
|
|||||||
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
|
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
|
||||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
import android.hardware.fingerprint.FingerprintStateListener;
|
|
||||||
import android.hardware.fingerprint.IUdfpsOverlayController;
|
import android.hardware.fingerprint.IUdfpsOverlayController;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -135,43 +134,17 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
|
|||||||
@NonNull private final RestartAuthRunnable mRestartAuthRunnable;
|
@NonNull private final RestartAuthRunnable mRestartAuthRunnable;
|
||||||
|
|
||||||
private static class TestableBiometricScheduler extends BiometricScheduler {
|
private static class TestableBiometricScheduler extends BiometricScheduler {
|
||||||
@NonNull private final TestableInternalCallback mInternalCallback;
|
|
||||||
@NonNull private Fingerprint21UdfpsMock mFingerprint21;
|
@NonNull private Fingerprint21UdfpsMock mFingerprint21;
|
||||||
|
|
||||||
TestableBiometricScheduler(@NonNull String tag,
|
TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler,
|
||||||
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
|
||||||
super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
|
super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER,
|
||||||
gestureAvailabilityDispatcher);
|
gestureAvailabilityDispatcher);
|
||||||
mInternalCallback = new TestableInternalCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestableInternalCallback extends InternalCallback {
|
|
||||||
@Override
|
|
||||||
public void onClientStarted(BaseClientMonitor clientMonitor) {
|
|
||||||
super.onClientStarted(clientMonitor);
|
|
||||||
Slog.d(TAG, "Client started: " + clientMonitor);
|
|
||||||
mFingerprint21.setDebugMessage("Started: " + clientMonitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) {
|
|
||||||
super.onClientFinished(clientMonitor, success);
|
|
||||||
Slog.d(TAG, "Client finished: " + clientMonitor);
|
|
||||||
mFingerprint21.setDebugMessage("Finished: " + clientMonitor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
|
void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
|
||||||
mFingerprint21 = fingerprint21;
|
mFingerprint21 = fingerprint21;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose the internal finish callback so it can be used for testing
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@NonNull protected InternalCallback getInternalCallback() {
|
|
||||||
return mInternalCallback;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -280,7 +253,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
|
|||||||
|
|
||||||
final Handler handler = new Handler(Looper.getMainLooper());
|
final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
final TestableBiometricScheduler scheduler =
|
final TestableBiometricScheduler scheduler =
|
||||||
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
|
new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
|
||||||
final MockHalResultController controller =
|
final MockHalResultController controller =
|
||||||
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
|
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
|
||||||
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
|
return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
|
|||||||
|
|
||||||
FingerprintEnrollClient(@NonNull Context context,
|
FingerprintEnrollClient(@NonNull Context context,
|
||||||
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
|
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
|
||||||
@NonNull ClientMonitorCallbackConverter listener, int userId,
|
long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
|
||||||
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
|
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
|
||||||
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
|
@NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
|
||||||
@Nullable IUdfpsOverlayController udfpsOverlayController,
|
@Nullable IUdfpsOverlayController udfpsOverlayController,
|
||||||
@@ -64,6 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
|
|||||||
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
|
||||||
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
|
||||||
true /* shouldVibrate */);
|
true /* shouldVibrate */);
|
||||||
|
setRequestId(requestId);
|
||||||
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
|
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
|
||||||
|
|
||||||
mEnrollReason = enrollReason;
|
mEnrollReason = enrollReason;
|
||||||
|
|||||||
@@ -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;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import static android.testing.TestableLooper.RunWithLooper;
|
||||||
|
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
import static junit.framework.Assert.fail;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
@@ -34,10 +38,13 @@ import android.content.Context;
|
|||||||
import android.hardware.biometrics.BiometricConstants;
|
import android.hardware.biometrics.BiometricConstants;
|
||||||
import android.hardware.biometrics.IBiometricService;
|
import android.hardware.biometrics.IBiometricService;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.testing.AndroidTestingRunner;
|
||||||
import android.testing.TestableContext;
|
import android.testing.TestableContext;
|
||||||
|
import android.testing.TestableLooper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -46,16 +53,18 @@ import androidx.test.filters.SmallTest;
|
|||||||
|
|
||||||
import com.android.server.biometrics.nano.BiometricSchedulerProto;
|
import com.android.server.biometrics.nano.BiometricSchedulerProto;
|
||||||
import com.android.server.biometrics.nano.BiometricsProto;
|
import com.android.server.biometrics.nano.BiometricsProto;
|
||||||
import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
@Presubmit
|
@Presubmit
|
||||||
@SmallTest
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner.class)
|
||||||
|
@RunWithLooper(setAsMainLooper = true)
|
||||||
public class BiometricSchedulerTest {
|
public class BiometricSchedulerTest {
|
||||||
|
|
||||||
private static final String TAG = "BiometricSchedulerTest";
|
private static final String TAG = "BiometricSchedulerTest";
|
||||||
@@ -76,8 +85,9 @@ public class BiometricSchedulerTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mToken = new Binder();
|
mToken = new Binder();
|
||||||
mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN,
|
mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
|
||||||
null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS,
|
BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
|
||||||
|
mBiometricService, LOG_NUM_RECENT_OPERATIONS,
|
||||||
CoexCoordinator.getInstance());
|
CoexCoordinator.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +96,9 @@ public class BiometricSchedulerTest {
|
|||||||
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
|
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
|
||||||
|
|
||||||
final HalClientMonitor<Object> client1 =
|
final HalClientMonitor<Object> client1 =
|
||||||
new TestClientMonitor(mContext, mToken, nonNullDaemon);
|
new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
|
||||||
final HalClientMonitor<Object> client2 =
|
final HalClientMonitor<Object> client2 =
|
||||||
new TestClientMonitor(mContext, mToken, nonNullDaemon);
|
new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
|
||||||
mScheduler.scheduleClientMonitor(client1);
|
mScheduler.scheduleClientMonitor(client1);
|
||||||
mScheduler.scheduleClientMonitor(client2);
|
mScheduler.scheduleClientMonitor(client2);
|
||||||
|
|
||||||
@@ -99,20 +109,17 @@ public class BiometricSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
|
public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
|
||||||
// Even if second client has a non-null daemon, it needs to be canceled.
|
// Even if second client has a non-null daemon, it needs to be canceled.
|
||||||
Object daemon2 = mock(Object.class);
|
final TestHalClientMonitor client1 = new TestHalClientMonitor(
|
||||||
|
mContext, mToken, () -> null);
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
|
final TestHalClientMonitor client2 = new TestHalClientMonitor(
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
|
mContext, mToken, () -> mock(Object.class));
|
||||||
|
|
||||||
final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1);
|
|
||||||
final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
|
|
||||||
|
|
||||||
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
|
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
|
||||||
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
|
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
|
||||||
|
|
||||||
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
|
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
|
||||||
// to pretend like there are two operations in the queue before kicking things off
|
// to pretend like there are two operations in the queue before kicking things off
|
||||||
mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
|
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
|
||||||
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
|
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
|
||||||
|
|
||||||
mScheduler.scheduleClientMonitor(client1, callback1);
|
mScheduler.scheduleClientMonitor(client1, callback1);
|
||||||
@@ -122,11 +129,11 @@ public class BiometricSchedulerTest {
|
|||||||
mScheduler.scheduleClientMonitor(client2, callback2);
|
mScheduler.scheduleClientMonitor(client2, callback2);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertTrue(client1.wasUnableToStart());
|
assertTrue(client1.mUnableToStart);
|
||||||
verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
|
verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
|
||||||
verify(callback1, never()).onClientStarted(any());
|
verify(callback1, never()).onClientStarted(any());
|
||||||
|
|
||||||
assertTrue(client2.wasUnableToStart());
|
assertTrue(client2.mUnableToStart);
|
||||||
verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
|
verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
|
||||||
verify(callback2, never()).onClientStarted(any());
|
verify(callback2, never()).onClientStarted(any());
|
||||||
|
|
||||||
@@ -138,21 +145,19 @@ public class BiometricSchedulerTest {
|
|||||||
// Second non-BiometricPrompt client has a valid daemon
|
// Second non-BiometricPrompt client has a valid daemon
|
||||||
final Object daemon2 = mock(Object.class);
|
final Object daemon2 = mock(Object.class);
|
||||||
|
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
|
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
|
|
||||||
|
|
||||||
final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
|
final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
|
||||||
|
|
||||||
final TestAuthenticationClient client1 =
|
final TestAuthenticationClient client1 =
|
||||||
new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1);
|
new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
|
||||||
final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
|
final TestHalClientMonitor client2 =
|
||||||
|
new TestHalClientMonitor(mContext, mToken, () -> daemon2);
|
||||||
|
|
||||||
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
|
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
|
||||||
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
|
final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
|
||||||
|
|
||||||
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
|
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
|
||||||
// to pretend like there are two operations in the queue before kicking things off
|
// to pretend like there are two operations in the queue before kicking things off
|
||||||
mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
|
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
|
||||||
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
|
mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
|
||||||
|
|
||||||
mScheduler.scheduleClientMonitor(client1, callback1);
|
mScheduler.scheduleClientMonitor(client1, callback1);
|
||||||
@@ -172,8 +177,8 @@ public class BiometricSchedulerTest {
|
|||||||
verify(callback1, never()).onClientStarted(any());
|
verify(callback1, never()).onClientStarted(any());
|
||||||
|
|
||||||
// Client 2 was able to start
|
// Client 2 was able to start
|
||||||
assertFalse(client2.wasUnableToStart());
|
assertFalse(client2.mUnableToStart);
|
||||||
assertTrue(client2.hasStarted());
|
assertTrue(client2.mStarted);
|
||||||
verify(callback2).onClientStarted(eq(client2));
|
verify(callback2).onClientStarted(eq(client2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,16 +192,18 @@ public class BiometricSchedulerTest {
|
|||||||
// Schedule a BiometricPrompt authentication request
|
// Schedule a BiometricPrompt authentication request
|
||||||
mScheduler.scheduleClientMonitor(client1, callback1);
|
mScheduler.scheduleClientMonitor(client1, callback1);
|
||||||
|
|
||||||
assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState);
|
assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
|
||||||
assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor);
|
assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
|
||||||
assertEquals(0, mScheduler.mPendingOperations.size());
|
assertEquals(0, mScheduler.mPendingOperations.size());
|
||||||
|
|
||||||
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
|
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
|
||||||
// should go back to idle, since in this case the framework has not even requested the HAL
|
// should go back to idle, since in this case the framework has not even requested the HAL
|
||||||
// to authenticate yet.
|
// to authenticate yet.
|
||||||
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
|
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
|
||||||
|
waitForIdle();
|
||||||
assertTrue(client1.isAlreadyDone());
|
assertTrue(client1.isAlreadyDone());
|
||||||
assertTrue(client1.mDestroyed);
|
assertTrue(client1.mDestroyed);
|
||||||
|
assertFalse(client1.mStartedHal);
|
||||||
assertNull(mScheduler.mCurrentOperation);
|
assertNull(mScheduler.mCurrentOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,8 +217,8 @@ public class BiometricSchedulerTest {
|
|||||||
// assertEquals(0, bsp.recentOperations.length);
|
// assertEquals(0, bsp.recentOperations.length);
|
||||||
|
|
||||||
// Pretend the scheduler is busy enrolling, and check the proto dump again.
|
// Pretend the scheduler is busy enrolling, and check the proto dump again.
|
||||||
final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
|
final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
|
||||||
() -> mock(Object.class), BiometricsProto.CM_ENROLL);
|
() -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
|
||||||
mScheduler.scheduleClientMonitor(client);
|
mScheduler.scheduleClientMonitor(client);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
bsp = getDump(true /* clearSchedulerBuffer */);
|
bsp = getDump(true /* clearSchedulerBuffer */);
|
||||||
@@ -230,8 +237,8 @@ public class BiometricSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testProtoDump_fifo() throws Exception {
|
public void testProtoDump_fifo() throws Exception {
|
||||||
// Add the first operation
|
// Add the first operation
|
||||||
final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
|
final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
|
||||||
() -> mock(Object.class), BiometricsProto.CM_ENROLL);
|
() -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
|
||||||
mScheduler.scheduleClientMonitor(client);
|
mScheduler.scheduleClientMonitor(client);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
|
BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
|
||||||
@@ -244,8 +251,8 @@ public class BiometricSchedulerTest {
|
|||||||
client.getCallback().onClientFinished(client, true);
|
client.getCallback().onClientFinished(client, true);
|
||||||
|
|
||||||
// Add another operation
|
// Add another operation
|
||||||
final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken,
|
final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
|
||||||
() -> mock(Object.class), BiometricsProto.CM_REMOVE);
|
() -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
|
||||||
mScheduler.scheduleClientMonitor(client2);
|
mScheduler.scheduleClientMonitor(client2);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
bsp = getDump(false /* clearSchedulerBuffer */);
|
bsp = getDump(false /* clearSchedulerBuffer */);
|
||||||
@@ -256,8 +263,8 @@ public class BiometricSchedulerTest {
|
|||||||
client2.getCallback().onClientFinished(client2, true);
|
client2.getCallback().onClientFinished(client2, true);
|
||||||
|
|
||||||
// And another operation
|
// And another operation
|
||||||
final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken,
|
final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
|
||||||
() -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE);
|
() -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
|
||||||
mScheduler.scheduleClientMonitor(client3);
|
mScheduler.scheduleClientMonitor(client3);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
bsp = getDump(false /* clearSchedulerBuffer */);
|
bsp = getDump(false /* clearSchedulerBuffer */);
|
||||||
@@ -290,8 +297,7 @@ public class BiometricSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCancelPendingAuth() throws RemoteException {
|
public void testCancelPendingAuth() throws RemoteException {
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
|
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
|
||||||
|
final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
|
||||||
final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
|
|
||||||
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
|
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
|
||||||
final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
|
final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
|
||||||
mToken, callback);
|
mToken, callback);
|
||||||
@@ -302,14 +308,12 @@ public class BiometricSchedulerTest {
|
|||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertEquals(mScheduler.getCurrentClient(), client1);
|
assertEquals(mScheduler.getCurrentClient(), client1);
|
||||||
assertEquals(Operation.STATE_WAITING_IN_QUEUE,
|
assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
|
||||||
mScheduler.mPendingOperations.getFirst().mState);
|
|
||||||
|
|
||||||
// Request cancel before the authentication client has started
|
// Request cancel before the authentication client has started
|
||||||
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
|
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
|
assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
|
||||||
mScheduler.mPendingOperations.getFirst().mState);
|
|
||||||
|
|
||||||
// Finish the blocking client. The authentication client should send ERROR_CANCELED
|
// Finish the blocking client. The authentication client should send ERROR_CANCELED
|
||||||
client1.getCallback().onClientFinished(client1, true /* success */);
|
client1.getCallback().onClientFinished(client1, true /* success */);
|
||||||
@@ -326,67 +330,109 @@ public class BiometricSchedulerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancels_whenAuthRequestIdNotSet() {
|
public void testCancels_whenAuthRequestIdNotSet() {
|
||||||
testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
|
testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancels_whenAuthRequestIdNotSet_notStarted() {
|
public void testCancels_whenAuthRequestIdNotSet_notStarted() {
|
||||||
testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
|
testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancels_whenAuthRequestIdMatches() {
|
public void testCancels_whenAuthRequestIdMatches() {
|
||||||
testCancelsWhenRequestId(200L, 200, true /* started */);
|
testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancels_whenAuthRequestIdMatches_noStarted() {
|
public void testCancels_whenAuthRequestIdMatches_noStarted() {
|
||||||
testCancelsWhenRequestId(200L, 200, false /* started */);
|
testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoesNotCancel_whenAuthRequestIdMismatched() {
|
public void testDoesNotCancel_whenAuthRequestIdMismatched() {
|
||||||
testCancelsWhenRequestId(10L, 20, true /* started */);
|
testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
|
public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
|
||||||
testCancelsWhenRequestId(10L, 20, false /* started */);
|
testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
|
||||||
|
boolean started) {
|
||||||
|
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
|
||||||
|
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
|
||||||
|
testCancelsWhenRequestId(requestId, cancelRequestId, started,
|
||||||
|
new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCancels_whenEnrollRequestIdNotSet() {
|
||||||
|
testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCancels_whenEnrollRequestIdMatches() {
|
||||||
|
testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
|
||||||
|
testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
|
||||||
|
boolean started) {
|
||||||
|
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
|
||||||
|
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
|
||||||
|
testCancelsWhenRequestId(requestId, cancelRequestId, started,
|
||||||
|
new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
|
private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
|
||||||
boolean started) {
|
boolean started, HalClientMonitor<?> client) {
|
||||||
final boolean matches = requestId == null || requestId == cancelRequestId;
|
final boolean matches = requestId == null || requestId == cancelRequestId;
|
||||||
final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
|
|
||||||
final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
|
|
||||||
final TestAuthenticationClient client = new TestAuthenticationClient(
|
|
||||||
mContext, lazyDaemon, mToken, callback);
|
|
||||||
if (requestId != null) {
|
if (requestId != null) {
|
||||||
client.setRequestId(requestId);
|
client.setRequestId(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean isAuth = client instanceof TestAuthenticationClient;
|
||||||
|
final boolean isEnroll = client instanceof TestEnrollClient;
|
||||||
|
|
||||||
mScheduler.scheduleClientMonitor(client);
|
mScheduler.scheduleClientMonitor(client);
|
||||||
if (started) {
|
if (started) {
|
||||||
mScheduler.startPreparedClient(client.getCookie());
|
mScheduler.startPreparedClient(client.getCookie());
|
||||||
}
|
}
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
if (isAuth) {
|
||||||
mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
|
mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
|
||||||
|
} else if (isEnroll) {
|
||||||
|
mScheduler.cancelEnrollment(mToken, cancelRequestId);
|
||||||
|
} else {
|
||||||
|
fail("unexpected operation type");
|
||||||
|
}
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertEquals(matches && started ? 1 : 0, client.mNumCancels);
|
if (isAuth) {
|
||||||
|
// auth clients that were waiting for cookie when canceled should never invoke the hal
|
||||||
|
final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
|
||||||
|
assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
|
||||||
|
assertEquals(started, authClient.mStartedHal);
|
||||||
|
} else if (isEnroll) {
|
||||||
|
final TestEnrollClient enrollClient = (TestEnrollClient) client;
|
||||||
|
assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
|
||||||
|
assertTrue(enrollClient.mStartedHal);
|
||||||
|
}
|
||||||
|
|
||||||
if (matches) {
|
if (matches) {
|
||||||
if (started) {
|
if (started || isEnroll) { // prep'd auth clients and enroll clients
|
||||||
assertEquals(Operation.STATE_STARTED_CANCELING,
|
assertTrue(mScheduler.mCurrentOperation.isCanceling());
|
||||||
mScheduler.mCurrentOperation.mState);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (started) {
|
if (started || isEnroll) { // prep'd auth clients and enroll clients
|
||||||
assertEquals(Operation.STATE_STARTED,
|
assertTrue(mScheduler.mCurrentOperation.isStarted());
|
||||||
mScheduler.mCurrentOperation.mState);
|
|
||||||
} else {
|
} else {
|
||||||
assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
|
assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
|
||||||
mScheduler.mCurrentOperation.mState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,18 +457,14 @@ public class BiometricSchedulerTest {
|
|||||||
mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
|
mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertEquals(Operation.STATE_STARTED,
|
assertTrue(mScheduler.mCurrentOperation.isStarted());
|
||||||
mScheduler.mCurrentOperation.mState);
|
assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
|
||||||
assertEquals(Operation.STATE_WAITING_IN_QUEUE,
|
|
||||||
mScheduler.mPendingOperations.getFirst().mState);
|
|
||||||
|
|
||||||
mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
|
mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertEquals(Operation.STATE_STARTED,
|
assertTrue(mScheduler.mCurrentOperation.isStarted());
|
||||||
mScheduler.mCurrentOperation.mState);
|
assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
|
||||||
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
|
|
||||||
mScheduler.mPendingOperations.getFirst().mState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -459,12 +501,12 @@ public class BiometricSchedulerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testClientDestroyed_afterFinish() {
|
public void testClientDestroyed_afterFinish() {
|
||||||
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
|
final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
|
||||||
final TestClientMonitor client =
|
final TestHalClientMonitor client =
|
||||||
new TestClientMonitor(mContext, mToken, nonNullDaemon);
|
new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
|
||||||
mScheduler.scheduleClientMonitor(client);
|
mScheduler.scheduleClientMonitor(client);
|
||||||
client.mCallback.onClientFinished(client, true /* success */);
|
client.mCallback.onClientFinished(client, true /* success */);
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
assertTrue(client.wasDestroyed());
|
assertTrue(client.mDestroyed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
|
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
|
||||||
@@ -472,8 +514,10 @@ public class BiometricSchedulerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
|
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
|
||||||
int mNumCancels = 0;
|
boolean mStartedHal = false;
|
||||||
|
boolean mStoppedHal = false;
|
||||||
boolean mDestroyed = false;
|
boolean mDestroyed = false;
|
||||||
|
int mNumCancels = 0;
|
||||||
|
|
||||||
public TestAuthenticationClient(@NonNull Context context,
|
public TestAuthenticationClient(@NonNull Context context,
|
||||||
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
|
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
|
||||||
@@ -488,18 +532,16 @@ public class BiometricSchedulerTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void stopHalOperation() {
|
protected void stopHalOperation() {
|
||||||
|
mStoppedHal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void startHalOperation() {
|
protected void startHalOperation() {
|
||||||
|
mStartedHal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleLifecycleAfterAuth(boolean authenticated) {
|
protected void handleLifecycleAfterAuth(boolean authenticated) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wasUserDetected() {
|
public boolean wasUserDetected() {
|
||||||
@@ -519,36 +561,59 @@ public class BiometricSchedulerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestClientMonitor2 extends TestClientMonitor {
|
private static class TestEnrollClient extends EnrollClient<Object> {
|
||||||
private final int mProtoEnum;
|
boolean mStartedHal = false;
|
||||||
|
boolean mStoppedHal = false;
|
||||||
|
int mNumCancels = 0;
|
||||||
|
|
||||||
public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token,
|
TestEnrollClient(@NonNull Context context,
|
||||||
@NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) {
|
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
|
||||||
super(context, token, lazyDaemon);
|
@NonNull ClientMonitorCallbackConverter listener) {
|
||||||
mProtoEnum = protoEnum;
|
super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
|
||||||
|
"test" /* owner */, mock(BiometricUtils.class),
|
||||||
|
5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
|
||||||
|
true /* shouldVibrate */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProtoEnum() {
|
protected void stopHalOperation() {
|
||||||
return mProtoEnum;
|
mStoppedHal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startHalOperation() {
|
||||||
|
mStartedHal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean hasReachedEnrollmentLimit() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
mNumCancels++;
|
||||||
|
super.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestClientMonitor extends HalClientMonitor<Object> {
|
private static class TestHalClientMonitor extends HalClientMonitor<Object> {
|
||||||
|
private final int mProtoEnum;
|
||||||
private boolean mUnableToStart;
|
private boolean mUnableToStart;
|
||||||
private boolean mStarted;
|
private boolean mStarted;
|
||||||
private boolean mDestroyed;
|
private boolean mDestroyed;
|
||||||
|
|
||||||
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
|
TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
|
||||||
@NonNull LazyDaemon<Object> lazyDaemon) {
|
@NonNull LazyDaemon<Object> lazyDaemon) {
|
||||||
this(context, token, lazyDaemon, 0 /* cookie */);
|
this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
|
TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
|
||||||
@NonNull LazyDaemon<Object> lazyDaemon, int cookie) {
|
@NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
|
||||||
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
|
super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
|
||||||
TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
|
TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
|
||||||
0 /* statsAction */, 0 /* statsClient */);
|
0 /* statsAction */, 0 /* statsClient */);
|
||||||
|
mProtoEnum = protoEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -559,9 +624,7 @@ public class BiometricSchedulerTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProtoEnum() {
|
public int getProtoEnum() {
|
||||||
// Anything other than CM_NONE, which is used to represent "idle". Tests that need
|
return mProtoEnum;
|
||||||
// real proto enums should use TestClientMonitor2
|
|
||||||
return BiometricsProto.CM_UPDATE_ACTIVE_USER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -573,7 +636,7 @@ public class BiometricSchedulerTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void startHalOperation() {
|
protected void startHalOperation() {
|
||||||
|
mStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -581,22 +644,9 @@ public class BiometricSchedulerTest {
|
|||||||
super.destroy();
|
super.destroy();
|
||||||
mDestroyed = true;
|
mDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean wasUnableToStart() {
|
|
||||||
return mUnableToStart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasStarted() {
|
private void waitForIdle() {
|
||||||
return mStarted;
|
TestableLooper.get(this).processAllMessages();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasDestroyed() {
|
|
||||||
return mDestroyed;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void waitForIdle() {
|
|
||||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.server.biometrics.sensors;
|
package com.android.server.biometrics.sensors;
|
||||||
|
|
||||||
|
import static android.testing.TestableLooper.RunWithLooper;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
@@ -28,52 +30,53 @@ import static org.mockito.Mockito.when;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.biometrics.IBiometricService;
|
import android.hardware.biometrics.IBiometricService;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.testing.AndroidTestingRunner;
|
||||||
|
import android.testing.TestableLooper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
@Presubmit
|
@Presubmit
|
||||||
|
@RunWith(AndroidTestingRunner.class)
|
||||||
|
@RunWithLooper
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class UserAwareBiometricSchedulerTest {
|
public class UserAwareBiometricSchedulerTest {
|
||||||
|
|
||||||
private static final String TAG = "BiometricSchedulerTest";
|
private static final String TAG = "UserAwareBiometricSchedulerTest";
|
||||||
private static final int TEST_SENSOR_ID = 0;
|
private static final int TEST_SENSOR_ID = 0;
|
||||||
|
|
||||||
|
private Handler mHandler;
|
||||||
private UserAwareBiometricScheduler mScheduler;
|
private UserAwareBiometricScheduler mScheduler;
|
||||||
private IBinder mToken;
|
private IBinder mToken = new Binder();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
private IBiometricService mBiometricService;
|
private IBiometricService mBiometricService;
|
||||||
|
|
||||||
private TestUserStartedCallback mUserStartedCallback;
|
private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
|
||||||
private TestUserStoppedCallback mUserStoppedCallback;
|
private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
|
||||||
private int mCurrentUserId = UserHandle.USER_NULL;
|
private int mCurrentUserId = UserHandle.USER_NULL;
|
||||||
private boolean mStartOperationsFinish;
|
private boolean mStartOperationsFinish = true;
|
||||||
private int mStartUserClientCount;
|
private int mStartUserClientCount = 0;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mHandler = new Handler(TestableLooper.get(this).getLooper());
|
||||||
mToken = new Binder();
|
|
||||||
mStartOperationsFinish = true;
|
|
||||||
mStartUserClientCount = 0;
|
|
||||||
mUserStartedCallback = new TestUserStartedCallback();
|
|
||||||
mUserStoppedCallback = new TestUserStoppedCallback();
|
|
||||||
|
|
||||||
mScheduler = new UserAwareBiometricScheduler(TAG,
|
mScheduler = new UserAwareBiometricScheduler(TAG,
|
||||||
|
mHandler,
|
||||||
BiometricScheduler.SENSOR_TYPE_UNKNOWN,
|
BiometricScheduler.SENSOR_TYPE_UNKNOWN,
|
||||||
null /* gestureAvailabilityDispatcher */,
|
null /* gestureAvailabilityDispatcher */,
|
||||||
mBiometricService,
|
mBiometricService,
|
||||||
@@ -147,11 +150,11 @@ public class UserAwareBiometricSchedulerTest {
|
|||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
final TestStartUserClient startUserClient =
|
final TestStartUserClient startUserClient =
|
||||||
(TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor;
|
(TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
|
||||||
mScheduler.reset();
|
mScheduler.reset();
|
||||||
assertNull(mScheduler.mCurrentOperation);
|
assertNull(mScheduler.mCurrentOperation);
|
||||||
|
|
||||||
final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation(
|
final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
|
||||||
mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
|
mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
|
||||||
mScheduler.mCurrentOperation = fakeOperation;
|
mScheduler.mCurrentOperation = fakeOperation;
|
||||||
startUserClient.mCallback.onClientFinished(startUserClient, true);
|
startUserClient.mCallback.onClientFinished(startUserClient, true);
|
||||||
@@ -194,8 +197,8 @@ public class UserAwareBiometricSchedulerTest {
|
|||||||
verify(nextClient).start(any());
|
verify(nextClient).start(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void waitForIdle() {
|
private void waitForIdle() {
|
||||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
TestableLooper.get(this).processAllMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
|
private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public class SensorTest {
|
|||||||
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
|
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
|
||||||
|
|
||||||
mScheduler = new UserAwareBiometricScheduler(TAG,
|
mScheduler = new UserAwareBiometricScheduler(TAG,
|
||||||
|
new Handler(mLooper.getLooper()),
|
||||||
BiometricScheduler.SENSOR_TYPE_FACE,
|
BiometricScheduler.SENSOR_TYPE_FACE,
|
||||||
null /* gestureAvailabilityDispatcher */,
|
null /* gestureAvailabilityDispatcher */,
|
||||||
() -> USER_ID,
|
() -> USER_ID,
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ import android.hardware.face.FaceSensorProperties;
|
|||||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||||
import android.hardware.face.IFaceServiceReceiver;
|
import android.hardware.face.IFaceServiceReceiver;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ public class Face10Test {
|
|||||||
@Mock
|
@Mock
|
||||||
private BiometricScheduler mScheduler;
|
private BiometricScheduler mScheduler;
|
||||||
|
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
private LockoutResetDispatcher mLockoutResetDispatcher;
|
private LockoutResetDispatcher mLockoutResetDispatcher;
|
||||||
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
|
private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
|
||||||
private IBinder mBinder;
|
private IBinder mBinder;
|
||||||
@@ -97,7 +100,7 @@ public class Face10Test {
|
|||||||
resetLockoutRequiresChallenge);
|
resetLockoutRequiresChallenge);
|
||||||
|
|
||||||
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
|
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
|
||||||
mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler);
|
mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler);
|
||||||
mBinder = new Binder();
|
mBinder = new Binder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public class SensorTest {
|
|||||||
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
|
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
|
||||||
|
|
||||||
mScheduler = new UserAwareBiometricScheduler(TAG,
|
mScheduler = new UserAwareBiometricScheduler(TAG,
|
||||||
|
new Handler(mLooper.getLooper()),
|
||||||
BiometricScheduler.SENSOR_TYPE_FP_OTHER,
|
BiometricScheduler.SENSOR_TYPE_FP_OTHER,
|
||||||
null /* gestureAvailabilityDispatcher */,
|
null /* gestureAvailabilityDispatcher */,
|
||||||
() -> USER_ID,
|
() -> USER_ID,
|
||||||
|
|||||||
Reference in New Issue
Block a user