blob: 361ec40f28779f40cf451c4837402d4ef7e7a353 [file] [log] [blame]
/*
* Copyright (C) 2020 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.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricSchedulerProto;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
/**
* A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor}
* operations, without caring about its implementation details. Operations may perform zero or more
* interactions with the HAL before finishing.
*
* We currently assume (and require) that each biometric sensor have its own instance of a
* {@link BiometricScheduler}. See {@link CoexCoordinator}.
*/
public class BiometricScheduler {
private static final String BASE_TAG = "BiometricScheduler";
// Number of recent operations to keep in our logs for dumpsys
protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Unknown sensor type. This should never be used, and is a sign that something is wrong during
* initialization.
*/
public static final int SENSOR_TYPE_UNKNOWN = 0;
/**
* Face authentication.
*/
public static final int SENSOR_TYPE_FACE = 1;
/**
* Any UDFPS type. See {@link FingerprintSensorPropertiesInternal#isAnyUdfpsType()}.
*/
public static final int SENSOR_TYPE_UDFPS = 2;
/**
* Any other fingerprint sensor. We can add additional definitions in the future when necessary.
*/
public static final int SENSOR_TYPE_FP_OTHER = 3;
@IntDef({SENSOR_TYPE_UNKNOWN, SENSOR_TYPE_FACE, SENSOR_TYPE_UDFPS, SENSOR_TYPE_FP_OTHER})
@Retention(RetentionPolicy.SOURCE)
public @interface SensorType {}
public static @SensorType int sensorTypeFromFingerprintProperties(
@NonNull FingerprintSensorPropertiesInternal props) {
if (props.isAnyUdfpsType()) {
return SENSOR_TYPE_UDFPS;
}
return SENSOR_TYPE_FP_OTHER;
}
public static String sensorTypeToString(@SensorType int sensorType) {
switch (sensorType) {
case SENSOR_TYPE_UNKNOWN:
return "Unknown";
case SENSOR_TYPE_FACE:
return "Face";
case SENSOR_TYPE_UDFPS:
return "Udfps";
case SENSOR_TYPE_FP_OTHER:
return "OtherFp";
default:
return "UnknownUnknown";
}
}
/**
* Contains all the necessary information for a HAL operation.
*/
@VisibleForTesting
static final class Operation {
/**
* The operation is added to the list of pending operations and waiting for its turn.
*/
static final int STATE_WAITING_IN_QUEUE = 0;
/**
* The operation is added to the list of pending operations, but a subsequent operation
* has been added. This state only applies to {@link Interruptable} operations. When this
* operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
*/
static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
/**
* The operation has reached the front of the queue and has started.
*/
static final int STATE_STARTED = 2;
/**
* The operation was started, but is now canceling. Operations should wait for the HAL to
* acknowledge that the operation was canceled, at which point it finishes.
*/
static final int STATE_STARTED_CANCELING = 3;
/**
* The operation has reached the head of the queue but is waiting for BiometricService
* to acknowledge and start the operation.
*/
static final int STATE_WAITING_FOR_COOKIE = 4;
/**
* The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
*/
static final int STATE_FINISHED = 5;
@IntDef({STATE_WAITING_IN_QUEUE,
STATE_WAITING_IN_QUEUE_CANCELING,
STATE_STARTED,
STATE_STARTED_CANCELING,
STATE_WAITING_FOR_COOKIE,
STATE_FINISHED})
@Retention(RetentionPolicy.SOURCE)
@interface OperationState {}
@NonNull final BaseClientMonitor mClientMonitor;
@Nullable final BaseClientMonitor.Callback mClientCallback;
@OperationState int mState;
Operation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback
) {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
protected Operation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback callback,
@OperationState int state
) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
}
public boolean isHalOperation() {
return mClientMonitor instanceof HalClientMonitor<?>;
}
/**
* @return true if the operation requires the HAL, and the HAL is null.
*/
public boolean isUnstartableHalOperation() {
if (isHalOperation()) {
final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
if (client.getFreshDaemon() == null) {
return true;
}
}
return false;
}
@Override
public String toString() {
return mClientMonitor + ", State: " + mState;
}
}
/**
* Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
* kill the current operation and forcibly start the next.
*/
private static final class CancellationWatchdog implements Runnable {
static final int DELAY_MS = 3000;
final String tag;
final Operation operation;
CancellationWatchdog(String tag, Operation operation) {
this.tag = tag;
this.operation = operation;
}
@Override
public void run() {
if (operation.mState != Operation.STATE_FINISHED) {
Slog.e(tag, "[Watchdog Triggered]: " + operation);
operation.mClientMonitor.mCallback
.onClientFinished(operation.mClientMonitor, false /* success */);
}
}
}
private static final class CrashState {
static final int NUM_ENTRIES = 10;
final String timestamp;
final String currentOperation;
final List<String> pendingOperations;
CrashState(String timestamp, String currentOperation, List<String> pendingOperations) {
this.timestamp = timestamp;
this.currentOperation = currentOperation;
this.pendingOperations = pendingOperations;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(timestamp).append(": ");
sb.append("Current Operation: {").append(currentOperation).append("}");
sb.append(", Pending Operations(").append(pendingOperations.size()).append(")");
if (!pendingOperations.isEmpty()) {
sb.append(": ");
}
for (int i = 0; i < pendingOperations.size(); i++) {
sb.append(pendingOperations.get(i));
if (i < pendingOperations.size() - 1) {
sb.append(", ");
}
}
return sb.toString();
}
}
@NonNull protected final String mBiometricTag;
private final @SensorType int mSensorType;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
@NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates;
private int mTotalOperationsHandled;
private final int mRecentOperationsLimit;
@NonNull private final List<Integer> mRecentOperations;
@NonNull private final CoexCoordinator mCoexCoordinator;
// Internal callback, notified when an operation is complete. Notifies the requester
// that the operation is complete, before performing internal scheduler work (such as
// starting the next client).
public class InternalCallback implements BaseClientMonitor.Callback {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
Slog.d(getTag(), "[Started] " + clientMonitor);
if (clientMonitor instanceof AuthenticationClient) {
mCoexCoordinator.addAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
}
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
clientMonitor.destroy();
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success
+ ", possible lifecycle bug in clientMonitor implementation?");
return;
}
if (clientMonitor != mCurrentOperation.mClientMonitor) {
Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
+ " current: " + mCurrentOperation.mClientMonitor);
return;
}
Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
if (clientMonitor instanceof AuthenticationClient) {
mCoexCoordinator.removeAuthenticationClient(mSensorType,
(AuthenticationClient<?>) clientMonitor);
}
mCurrentOperation.mState = Operation.STATE_FINISHED;
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
}
if (mGestureAvailabilityDispatcher != null) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
}
if (mRecentOperations.size() >= mRecentOperationsLimit) {
mRecentOperations.remove(0);
}
mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
mCurrentOperation = null;
mTotalOperationsHandled++;
startNextOperationIfIdle();
});
}
}
@VisibleForTesting
BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull IBiometricService biometricService, int recentOperationsLimit,
@NonNull CoexCoordinator coexCoordinator) {
mBiometricTag = tag;
mSensorType = sensorType;
mInternalCallback = new InternalCallback();
mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
mPendingOperations = new ArrayDeque<>();
mBiometricService = biometricService;
mCrashStates = new ArrayDeque<>();
mRecentOperationsLimit = recentOperationsLimit;
mRecentOperations = new ArrayList<>();
mCoexCoordinator = coexCoordinator;
}
/**
* Creates a new scheduler.
* @param tag for the specific instance of the scheduler. Should be unique.
* @param sensorType the sensorType that this scheduler is handling.
* @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
* (such as fingerprint swipe).
*/
public BiometricScheduler(@NonNull String tag,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
CoexCoordinator.getInstance());
}
/**
* @return A reference to the internal callback that should be invoked whenever the scheduler
* needs to (e.g. client started, client finished).
*/
@NonNull protected InternalCallback getInternalCallback() {
return mInternalCallback;
}
protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
}
if (mPendingOperations.isEmpty()) {
Slog.d(getTag(), "No operations, returning to idle");
return;
}
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
if (!(currentClient instanceof Interruptable)) {
throw new IllegalStateException("Mis-implemented client or scheduler, "
+ "trying to cancel non-interruptable operation: " + mCurrentOperation);
}
final Interruptable interruptable = (Interruptable) currentClient;
interruptable.cancelWithoutStarting(getInternalCallback());
// Now we wait for the client to send its FinishCallback, which kicks off the next
// operation.
return;
}
if (mGestureAvailabilityDispatcher != null
&& mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
mGestureAvailabilityDispatcher.markSensorActive(
mCurrentOperation.mClientMonitor.getSensorId(),
true /* active */);
}
// Not all operations start immediately. BiometricPrompt waits for its operation
// to arrive at the head of the queue, before pinging it to start.
final boolean shouldStartNow = currentClient.getCookie() == 0;
if (shouldStartNow) {
if (mCurrentOperation.isUnstartableHalOperation()) {
final HalClientMonitor<?> halClientMonitor =
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
// Note down current length of queue
final int pendingOperationsLength = mPendingOperations.size();
final Operation lastOperation = mPendingOperations.peekLast();
Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+ ". Last pending operation: " + lastOperation);
// For current operations, 1) unableToStart, which notifies the caller-side, then
// 2) notify operation's callback, to notify applicable system service that the
// operation failed.
halClientMonitor.unableToStart();
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(
mCurrentOperation.mClientMonitor, false /* success */);
}
// Then for each operation currently in the pending queue at the time of this
// failure, do the same as above. Otherwise, it's possible that something like
// setActiveUser fails, but then authenticate (for the wrong user) is invoked.
for (int i = 0; i < pendingOperationsLength; i++) {
final Operation operation = mPendingOperations.pollFirst();
if (operation == null) {
Slog.e(getTag(), "Null operation, index: " + i
+ ", expected length: " + pendingOperationsLength);
break;
}
if (operation.isHalOperation()) {
((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
}
if (operation.mClientCallback != null) {
operation.mClientCallback.onClientFinished(operation.mClientMonitor,
false /* success */);
}
Slog.w(getTag(), "[Aborted Operation] " + operation);
}
// It's possible that during cleanup a new set of operations came in. We can try to
// run these. A single request from the manager layer to the service layer may
// actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null;
startNextOperationIfIdle();
} else {
Slog.d(getTag(), "[Starting] " + mCurrentOperation);
currentClient.start(getInternalCallback());
mCurrentOperation.mState = Operation.STATE_STARTED;
}
} else {
try {
mBiometricService.onReadyForAuthentication(currentClient.getCookie());
} catch (RemoteException e) {
Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
}
Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
}
}
/**
* Starts the {@link #mCurrentOperation} if
* 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
* 2) its cookie matches this cookie
*
* This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
* requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
* the BiometricService client becomes the current client in the scheduler), the cookie is
* returned to BiometricService. Once BiometricService decides that authentication can start,
* it invokes this code path.
*
* @param cookie of the operation to be started
*/
public void startPreparedClient(int cookie) {
if (mCurrentOperation == null) {
Slog.e(getTag(), "Current operation is null");
return;
}
if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
+ mCurrentOperation);
// This should trigger the internal onClientFinished callback, which clears the
// operation and starts the next one.
final ErrorConsumer errorConsumer =
(ErrorConsumer) mCurrentOperation.mClientMonitor;
errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
0 /* vendorCode */);
return;
} else {
Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+ ", expected STATE_WAITING_FOR_COOKIE");
return;
}
}
if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
+ ", received: " + cookie);
return;
}
if (mCurrentOperation.isUnstartableHalOperation()) {
Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
// This is BiometricPrompt trying to auth but something's wrong with the HAL.
final HalClientMonitor<?> halClientMonitor =
(HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
halClientMonitor.unableToStart();
if (mCurrentOperation.mClientCallback != null) {
mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
false /* success */);
}
mCurrentOperation = null;
startNextOperationIfIdle();
} else {
Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
mCurrentOperation.mState = Operation.STATE_STARTED;
mCurrentOperation.mClientMonitor.start(getInternalCallback());
}
}
/**
* Adds a {@link BaseClientMonitor} to the pending queue
*
* @param clientMonitor operation to be scheduled
*/
public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor) {
scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */);
}
/**
* Adds a {@link BaseClientMonitor} to the pending queue
*
* @param clientMonitor operation to be scheduled
* @param clientCallback optional callback, invoked when the client state changes.
*/
public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
@Nullable BaseClientMonitor.Callback clientCallback) {
// If the incoming operation should interrupt preceding clients, mark any interruptable
// pending clients as canceling. Once they reach the head of the queue, the scheduler will
// send ERROR_CANCELED and skip the operation.
if (clientMonitor.interruptsPrecedingClients()) {
for (Operation operation : mPendingOperations) {
if (operation.mClientMonitor instanceof Interruptable
&& operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+ operation.mClientMonitor);
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
}
}
}
mPendingOperations.add(new Operation(clientMonitor, clientCallback));
Slog.d(getTag(), "[Added] " + clientMonitor
+ ", new queue size: " + mPendingOperations.size());
// If the new operation should interrupt preceding clients, and if the current operation is
// cancellable, start the cancellation process.
if (clientMonitor.interruptsPrecedingClients()
&& mCurrentOperation != null
&& mCurrentOperation.mClientMonitor instanceof Interruptable
&& mCurrentOperation.mState == Operation.STATE_STARTED) {
Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
}
startNextOperationIfIdle();
}
private void cancelInternal(Operation operation) {
if (operation != mCurrentOperation) {
Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
return;
}
if (!(operation.mClientMonitor instanceof Interruptable)) {
Slog.w(getTag(), "Operation not interruptable: " + operation);
return;
}
if (operation.mState == Operation.STATE_STARTED_CANCELING) {
Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
return;
}
if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
// We can set it to null immediately, since the HAL was never notified to start.
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.
* @param token from the caller, should match the token passed in when requesting enrollment
*/
public void cancelEnrollment(IBinder token) {
if (mCurrentOperation == null) {
Slog.e(getTag(), "Unable to cancel enrollment, null operation");
return;
}
final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
if (!isEnrolling || !tokenMatches) {
Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
+ " tokenMatches: " + tokenMatches);
return;
}
cancelInternal(mCurrentOperation);
}
/**
* Requests to cancel authentication or detection.
* @param token from the caller, should match the token passed in when requesting authentication
* @param requestId the id returned when requesting authentication
*/
public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+ " current: " + mCurrentOperation
+ " stack size: " + mPendingOperations.size());
if (mCurrentOperation != null
&& canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
} else {
// Look through the current queue for all authentication clients for the specified
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
// all of them, instead of just the first one, since the API surface currently doesn't
// allow us to distinguish between multiple authentication requests from the same
// process. However, this generally does not happen anyway, and would be a class of
// bugs on its own.
for (Operation operation : mPendingOperations) {
if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
}
}
}
}
private static boolean canCancelAuthOperation(Operation operation, IBinder token,
long requestId) {
// TODO: restrict callers that can cancel without requestId (negative value)?
return isAuthenticationOrDetectionOperation(operation)
&& operation.mClientMonitor.getToken() == token
&& isMatchingRequestId(operation, requestId);
}
// By default, monitors are not associated with a request id to retain the original
// behavior (i.e. if no requestId is explicitly set then assume it matches)
private static boolean isMatchingRequestId(Operation operation, long requestId) {
return !operation.mClientMonitor.hasRequestId()
|| operation.mClientMonitor.getRequestId() == requestId;
}
private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
final boolean isAuthentication =
operation.mClientMonitor instanceof AuthenticationConsumer;
final boolean isDetection =
operation.mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
}
/**
* @return the current operation
*/
public BaseClientMonitor getCurrentClient() {
if (mCurrentOperation == null) {
return null;
}
return mCurrentOperation.mClientMonitor;
}
public int getCurrentPendingCount() {
return mPendingOperations.size();
}
public void recordCrashState() {
if (mCrashStates.size() >= CrashState.NUM_ENTRIES) {
mCrashStates.removeFirst();
}
final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
final List<String> pendingOperations = new ArrayList<>();
for (Operation operation : mPendingOperations) {
pendingOperations.add(operation.toString());
}
final CrashState crashState = new CrashState(timestamp,
mCurrentOperation != null ? mCurrentOperation.toString() : null,
pendingOperations);
mCrashStates.add(crashState);
Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
}
public void dump(PrintWriter pw) {
pw.println("Dump of BiometricScheduler " + getTag());
pw.println("Type: " + mSensorType);
pw.println("Current operation: " + mCurrentOperation);
pw.println("Pending operations: " + mPendingOperations.size());
for (Operation operation : mPendingOperations) {
pw.println("Pending operation: " + operation);
}
for (CrashState crashState : mCrashStates) {
pw.println("Crash State " + crashState);
}
}
public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
final ProtoOutputStream proto = new ProtoOutputStream();
proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
if (!mRecentOperations.isEmpty()) {
for (int i = 0; i < mRecentOperations.size(); i++) {
proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
}
} else {
// TODO:(b/178828362) Unsure why protobuf has a problem decoding when an empty list
// is returned. So, let's just add a no-op for this case.
proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, BiometricsProto.CM_NONE);
}
proto.flush();
if (clearSchedulerBuffer) {
mRecentOperations.clear();
}
return proto.getBytes();
}
/**
* Clears the scheduler of anything work-related. This should be used for example when the
* HAL dies.
*/
public void reset() {
mPendingOperations.clear();
mCurrentOperation = null;
}
}