• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.biometrics.sensors;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.hardware.biometrics.BiometricConstants;
24 import android.hardware.biometrics.IBiometricService;
25 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.util.Slog;
32 import android.util.proto.ProtoOutputStream;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.server.biometrics.BiometricSchedulerProto;
36 import com.android.server.biometrics.BiometricsProto;
37 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
38 
39 import java.io.PrintWriter;
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.text.SimpleDateFormat;
43 import java.util.ArrayDeque;
44 import java.util.ArrayList;
45 import java.util.Date;
46 import java.util.Deque;
47 import java.util.List;
48 import java.util.Locale;
49 
50 /**
51  * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor}
52  * operations, without caring about its implementation details. Operations may perform zero or more
53  * interactions with the HAL before finishing.
54  *
55  * We currently assume (and require) that each biometric sensor have its own instance of a
56  * {@link BiometricScheduler}. See {@link CoexCoordinator}.
57  */
58 public class BiometricScheduler {
59 
60     private static final String BASE_TAG = "BiometricScheduler";
61     // Number of recent operations to keep in our logs for dumpsys
62     protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
63 
64     /**
65      * Unknown sensor type. This should never be used, and is a sign that something is wrong during
66      * initialization.
67      */
68     public static final int SENSOR_TYPE_UNKNOWN = 0;
69 
70     /**
71      * Face authentication.
72      */
73     public static final int SENSOR_TYPE_FACE = 1;
74 
75     /**
76      * Any UDFPS type. See {@link FingerprintSensorPropertiesInternal#isAnyUdfpsType()}.
77      */
78     public static final int SENSOR_TYPE_UDFPS = 2;
79 
80     /**
81      * Any other fingerprint sensor. We can add additional definitions in the future when necessary.
82      */
83     public static final int SENSOR_TYPE_FP_OTHER = 3;
84 
85     @IntDef({SENSOR_TYPE_UNKNOWN, SENSOR_TYPE_FACE, SENSOR_TYPE_UDFPS, SENSOR_TYPE_FP_OTHER})
86     @Retention(RetentionPolicy.SOURCE)
87     public @interface SensorType {}
88 
sensorTypeFromFingerprintProperties( @onNull FingerprintSensorPropertiesInternal props)89     public static @SensorType int sensorTypeFromFingerprintProperties(
90             @NonNull FingerprintSensorPropertiesInternal props) {
91         if (props.isAnyUdfpsType()) {
92             return SENSOR_TYPE_UDFPS;
93         }
94 
95         return SENSOR_TYPE_FP_OTHER;
96     }
97 
sensorTypeToString(@ensorType int sensorType)98     public static String sensorTypeToString(@SensorType int sensorType) {
99         switch (sensorType) {
100             case SENSOR_TYPE_UNKNOWN:
101                 return "Unknown";
102             case SENSOR_TYPE_FACE:
103                 return "Face";
104             case SENSOR_TYPE_UDFPS:
105                 return "Udfps";
106             case SENSOR_TYPE_FP_OTHER:
107                 return "OtherFp";
108             default:
109                 return "UnknownUnknown";
110         }
111     }
112 
113     /**
114      * Contains all the necessary information for a HAL operation.
115      */
116     @VisibleForTesting
117     static final class Operation {
118 
119         /**
120          * The operation is added to the list of pending operations and waiting for its turn.
121          */
122         static final int STATE_WAITING_IN_QUEUE = 0;
123 
124         /**
125          * The operation is added to the list of pending operations, but a subsequent operation
126          * has been added. This state only applies to {@link Interruptable} operations. When this
127          * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
128          */
129         static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
130 
131         /**
132          * The operation has reached the front of the queue and has started.
133          */
134         static final int STATE_STARTED = 2;
135 
136         /**
137          * The operation was started, but is now canceling. Operations should wait for the HAL to
138          * acknowledge that the operation was canceled, at which point it finishes.
139          */
140         static final int STATE_STARTED_CANCELING = 3;
141 
142         /**
143          * The operation has reached the head of the queue but is waiting for BiometricService
144          * to acknowledge and start the operation.
145          */
146         static final int STATE_WAITING_FOR_COOKIE = 4;
147 
148         /**
149          * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
150          */
151         static final int STATE_FINISHED = 5;
152 
153         @IntDef({STATE_WAITING_IN_QUEUE,
154                 STATE_WAITING_IN_QUEUE_CANCELING,
155                 STATE_STARTED,
156                 STATE_STARTED_CANCELING,
157                 STATE_WAITING_FOR_COOKIE,
158                 STATE_FINISHED})
159         @Retention(RetentionPolicy.SOURCE)
160         @interface OperationState {}
161 
162         @NonNull final BaseClientMonitor mClientMonitor;
163         @Nullable final BaseClientMonitor.Callback mClientCallback;
164         @OperationState int mState;
165 
Operation( @onNull BaseClientMonitor clientMonitor, @Nullable BaseClientMonitor.Callback callback )166         Operation(
167                 @NonNull BaseClientMonitor clientMonitor,
168                 @Nullable BaseClientMonitor.Callback callback
169         ) {
170             this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
171         }
172 
Operation( @onNull BaseClientMonitor clientMonitor, @Nullable BaseClientMonitor.Callback callback, @OperationState int state )173         protected Operation(
174                 @NonNull BaseClientMonitor clientMonitor,
175                 @Nullable BaseClientMonitor.Callback callback,
176                 @OperationState int state
177         ) {
178             mClientMonitor = clientMonitor;
179             mClientCallback = callback;
180             mState = state;
181         }
182 
isHalOperation()183         public boolean isHalOperation() {
184             return mClientMonitor instanceof HalClientMonitor<?>;
185         }
186 
187         /**
188          * @return true if the operation requires the HAL, and the HAL is null.
189          */
isUnstartableHalOperation()190         public boolean isUnstartableHalOperation() {
191             if (isHalOperation()) {
192                 final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
193                 if (client.getFreshDaemon() == null) {
194                     return true;
195                 }
196             }
197             return false;
198         }
199 
200         @Override
toString()201         public String toString() {
202             return mClientMonitor + ", State: " + mState;
203         }
204     }
205 
206     /**
207      * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
208      * kill the current operation and forcibly start the next.
209      */
210     private static final class CancellationWatchdog implements Runnable {
211         static final int DELAY_MS = 3000;
212 
213         final String tag;
214         final Operation operation;
CancellationWatchdog(String tag, Operation operation)215         CancellationWatchdog(String tag, Operation operation) {
216             this.tag = tag;
217             this.operation = operation;
218         }
219 
220         @Override
run()221         public void run() {
222             if (operation.mState != Operation.STATE_FINISHED) {
223                 Slog.e(tag, "[Watchdog Triggered]: " + operation);
224                 operation.mClientMonitor.mCallback
225                         .onClientFinished(operation.mClientMonitor, false /* success */);
226             }
227         }
228     }
229 
230     private static final class CrashState {
231         static final int NUM_ENTRIES = 10;
232         final String timestamp;
233         final String currentOperation;
234         final List<String> pendingOperations;
235 
CrashState(String timestamp, String currentOperation, List<String> pendingOperations)236         CrashState(String timestamp, String currentOperation, List<String> pendingOperations) {
237             this.timestamp = timestamp;
238             this.currentOperation = currentOperation;
239             this.pendingOperations = pendingOperations;
240         }
241 
242         @Override
toString()243         public String toString() {
244             final StringBuilder sb = new StringBuilder();
245             sb.append(timestamp).append(": ");
246             sb.append("Current Operation: {").append(currentOperation).append("}");
247             sb.append(", Pending Operations(").append(pendingOperations.size()).append(")");
248 
249             if (!pendingOperations.isEmpty()) {
250                 sb.append(": ");
251             }
252             for (int i = 0; i < pendingOperations.size(); i++) {
253                 sb.append(pendingOperations.get(i));
254                 if (i < pendingOperations.size() - 1) {
255                     sb.append(", ");
256                 }
257             }
258             return sb.toString();
259         }
260     }
261 
262     @NonNull protected final String mBiometricTag;
263     private final @SensorType int mSensorType;
264     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
265     @NonNull private final IBiometricService mBiometricService;
266     @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
267     @NonNull private final InternalCallback mInternalCallback;
268     @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
269     @VisibleForTesting @Nullable Operation mCurrentOperation;
270     @NonNull private final ArrayDeque<CrashState> mCrashStates;
271 
272     private int mTotalOperationsHandled;
273     private final int mRecentOperationsLimit;
274     @NonNull private final List<Integer> mRecentOperations;
275     @NonNull private final CoexCoordinator mCoexCoordinator;
276 
277     // Internal callback, notified when an operation is complete. Notifies the requester
278     // that the operation is complete, before performing internal scheduler work (such as
279     // starting the next client).
280     public class InternalCallback implements BaseClientMonitor.Callback {
281         @Override
onClientStarted(@onNull BaseClientMonitor clientMonitor)282         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
283             Slog.d(getTag(), "[Started] " + clientMonitor);
284 
285             if (clientMonitor instanceof AuthenticationClient) {
286                 mCoexCoordinator.addAuthenticationClient(mSensorType,
287                         (AuthenticationClient<?>) clientMonitor);
288             }
289 
290             if (mCurrentOperation.mClientCallback != null) {
291                 mCurrentOperation.mClientCallback.onClientStarted(clientMonitor);
292             }
293         }
294 
295         @Override
onClientFinished(@onNull BaseClientMonitor clientMonitor, boolean success)296         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
297             mHandler.post(() -> {
298                 if (mCurrentOperation == null) {
299                     Slog.e(getTag(), "[Finishing] " + clientMonitor
300                             + " but current operation is null, success: " + success
301                             + ", possible lifecycle bug in clientMonitor implementation?");
302                     return;
303                 }
304 
305                 if (clientMonitor != mCurrentOperation.mClientMonitor) {
306                     Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
307                             + " current: " + mCurrentOperation.mClientMonitor);
308                     return;
309                 }
310 
311                 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
312                 if (clientMonitor instanceof AuthenticationClient) {
313                     mCoexCoordinator.removeAuthenticationClient(mSensorType,
314                             (AuthenticationClient<?>) clientMonitor);
315                 }
316 
317                 mCurrentOperation.mState = Operation.STATE_FINISHED;
318 
319                 if (mCurrentOperation.mClientCallback != null) {
320                     mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success);
321                 }
322 
323                 if (mGestureAvailabilityDispatcher != null) {
324                     mGestureAvailabilityDispatcher.markSensorActive(
325                             mCurrentOperation.mClientMonitor.getSensorId(), false /* active */);
326                 }
327 
328                 if (mRecentOperations.size() >= mRecentOperationsLimit) {
329                     mRecentOperations.remove(0);
330                 }
331                 mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum());
332                 mCurrentOperation = null;
333                 mTotalOperationsHandled++;
334                 startNextOperationIfIdle();
335             });
336         }
337     }
338 
339     @VisibleForTesting
BiometricScheduler(@onNull String tag, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, int recentOperationsLimit, @NonNull CoexCoordinator coexCoordinator)340     BiometricScheduler(@NonNull String tag, @SensorType int sensorType,
341             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
342             @NonNull IBiometricService biometricService, int recentOperationsLimit,
343             @NonNull CoexCoordinator coexCoordinator) {
344         mBiometricTag = tag;
345         mSensorType = sensorType;
346         mInternalCallback = new InternalCallback();
347         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
348         mPendingOperations = new ArrayDeque<>();
349         mBiometricService = biometricService;
350         mCrashStates = new ArrayDeque<>();
351         mRecentOperationsLimit = recentOperationsLimit;
352         mRecentOperations = new ArrayList<>();
353         mCoexCoordinator = coexCoordinator;
354     }
355 
356     /**
357      * Creates a new scheduler.
358      * @param tag for the specific instance of the scheduler. Should be unique.
359      * @param sensorType the sensorType that this scheduler is handling.
360      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
361      *                                      (such as fingerprint swipe).
362      */
BiometricScheduler(@onNull String tag, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher)363     public BiometricScheduler(@NonNull String tag,
364             @SensorType int sensorType,
365             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
366         this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
367                 ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS,
368                 CoexCoordinator.getInstance());
369     }
370 
371     /**
372      * @return A reference to the internal callback that should be invoked whenever the scheduler
373      *         needs to (e.g. client started, client finished).
374      */
getInternalCallback()375     @NonNull protected InternalCallback getInternalCallback() {
376         return mInternalCallback;
377     }
378 
getTag()379     protected String getTag() {
380         return BASE_TAG + "/" + mBiometricTag;
381     }
382 
startNextOperationIfIdle()383     protected void startNextOperationIfIdle() {
384         if (mCurrentOperation != null) {
385             Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
386             return;
387         }
388         if (mPendingOperations.isEmpty()) {
389             Slog.d(getTag(), "No operations, returning to idle");
390             return;
391         }
392 
393         mCurrentOperation = mPendingOperations.poll();
394         final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
395         Slog.d(getTag(), "[Polled] " + mCurrentOperation);
396 
397         // If the operation at the front of the queue has been marked for cancellation, send
398         // ERROR_CANCELED. No need to start this client.
399         if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
400             Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
401             if (!(currentClient instanceof Interruptable)) {
402                 throw new IllegalStateException("Mis-implemented client or scheduler, "
403                         + "trying to cancel non-interruptable operation: " + mCurrentOperation);
404             }
405 
406             final Interruptable interruptable = (Interruptable) currentClient;
407             interruptable.cancelWithoutStarting(getInternalCallback());
408             // Now we wait for the client to send its FinishCallback, which kicks off the next
409             // operation.
410             return;
411         }
412 
413         if (mGestureAvailabilityDispatcher != null
414                 && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) {
415             mGestureAvailabilityDispatcher.markSensorActive(
416                     mCurrentOperation.mClientMonitor.getSensorId(),
417                     true /* active */);
418         }
419 
420         // Not all operations start immediately. BiometricPrompt waits for its operation
421         // to arrive at the head of the queue, before pinging it to start.
422         final boolean shouldStartNow = currentClient.getCookie() == 0;
423         if (shouldStartNow) {
424             if (mCurrentOperation.isUnstartableHalOperation()) {
425                 final HalClientMonitor<?> halClientMonitor =
426                         (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
427                 // Note down current length of queue
428                 final int pendingOperationsLength = mPendingOperations.size();
429                 final Operation lastOperation = mPendingOperations.peekLast();
430                 Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
431                         + ". Last pending operation: " + lastOperation);
432 
433                 // For current operations, 1) unableToStart, which notifies the caller-side, then
434                 // 2) notify operation's callback, to notify applicable system service that the
435                 // operation failed.
436                 halClientMonitor.unableToStart();
437                 if (mCurrentOperation.mClientCallback != null) {
438                     mCurrentOperation.mClientCallback.onClientFinished(
439                             mCurrentOperation.mClientMonitor, false /* success */);
440                 }
441 
442                 // Then for each operation currently in the pending queue at the time of this
443                 // failure, do the same as above. Otherwise, it's possible that something like
444                 // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
445                 for (int i = 0; i < pendingOperationsLength; i++) {
446                     final Operation operation = mPendingOperations.pollFirst();
447                     if (operation == null) {
448                         Slog.e(getTag(), "Null operation, index: " + i
449                                 + ", expected length: " + pendingOperationsLength);
450                         break;
451                     }
452                     if (operation.isHalOperation()) {
453                         ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart();
454                     }
455                     if (operation.mClientCallback != null) {
456                         operation.mClientCallback.onClientFinished(operation.mClientMonitor,
457                                 false /* success */);
458                     }
459                     Slog.w(getTag(), "[Aborted Operation] " + operation);
460                 }
461 
462                 // It's possible that during cleanup a new set of operations came in. We can try to
463                 // run these. A single request from the manager layer to the service layer may
464                 // actually be multiple operations (i.e. updateActiveUser + authenticate).
465                 mCurrentOperation = null;
466                 startNextOperationIfIdle();
467             } else {
468                 Slog.d(getTag(), "[Starting] " + mCurrentOperation);
469                 currentClient.start(getInternalCallback());
470                 mCurrentOperation.mState = Operation.STATE_STARTED;
471             }
472         } else {
473             try {
474                 mBiometricService.onReadyForAuthentication(currentClient.getCookie());
475             } catch (RemoteException e) {
476                 Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
477             }
478             Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
479             mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE;
480         }
481     }
482 
483     /**
484      * Starts the {@link #mCurrentOperation} if
485      * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
486      * 2) its cookie matches this cookie
487      *
488      * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
489      * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
490      * the BiometricService client becomes the current client in the scheduler), the cookie is
491      * returned to BiometricService. Once BiometricService decides that authentication can start,
492      * it invokes this code path.
493      *
494      * @param cookie of the operation to be started
495      */
startPreparedClient(int cookie)496     public void startPreparedClient(int cookie) {
497         if (mCurrentOperation == null) {
498             Slog.e(getTag(), "Current operation is null");
499             return;
500         }
501         if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) {
502             if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
503                 Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: "
504                         + mCurrentOperation);
505                 // This should trigger the internal onClientFinished callback, which clears the
506                 // operation and starts the next one.
507                 final ErrorConsumer errorConsumer =
508                         (ErrorConsumer) mCurrentOperation.mClientMonitor;
509                 errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
510                         0 /* vendorCode */);
511                 return;
512             } else {
513                 Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
514                         + ", expected STATE_WAITING_FOR_COOKIE");
515                 return;
516             }
517         }
518         if (mCurrentOperation.mClientMonitor.getCookie() != cookie) {
519             Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
520                     + ", received: " + cookie);
521             return;
522         }
523 
524         if (mCurrentOperation.isUnstartableHalOperation()) {
525             Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
526             // This is BiometricPrompt trying to auth but something's wrong with the HAL.
527             final HalClientMonitor<?> halClientMonitor =
528                     (HalClientMonitor<?>) mCurrentOperation.mClientMonitor;
529             halClientMonitor.unableToStart();
530             if (mCurrentOperation.mClientCallback != null) {
531                 mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor,
532                         false /* success */);
533             }
534             mCurrentOperation = null;
535             startNextOperationIfIdle();
536         } else {
537             Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
538             mCurrentOperation.mState = Operation.STATE_STARTED;
539             mCurrentOperation.mClientMonitor.start(getInternalCallback());
540         }
541     }
542 
543     /**
544      * Adds a {@link BaseClientMonitor} to the pending queue
545      *
546      * @param clientMonitor operation to be scheduled
547      */
scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor)548     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor) {
549         scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */);
550     }
551 
552     /**
553      * Adds a {@link BaseClientMonitor} to the pending queue
554      *
555      * @param clientMonitor        operation to be scheduled
556      * @param clientCallback optional callback, invoked when the client state changes.
557      */
scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor, @Nullable BaseClientMonitor.Callback clientCallback)558     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
559             @Nullable BaseClientMonitor.Callback clientCallback) {
560         // If the incoming operation should interrupt preceding clients, mark any interruptable
561         // pending clients as canceling. Once they reach the head of the queue, the scheduler will
562         // send ERROR_CANCELED and skip the operation.
563         if (clientMonitor.interruptsPrecedingClients()) {
564             for (Operation operation : mPendingOperations) {
565                 if (operation.mClientMonitor instanceof Interruptable
566                         && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
567                     Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
568                             + operation.mClientMonitor);
569                     operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
570                 }
571             }
572         }
573 
574         mPendingOperations.add(new Operation(clientMonitor, clientCallback));
575         Slog.d(getTag(), "[Added] " + clientMonitor
576                 + ", new queue size: " + mPendingOperations.size());
577 
578         // If the new operation should interrupt preceding clients, and if the current operation is
579         // cancellable, start the cancellation process.
580         if (clientMonitor.interruptsPrecedingClients()
581                 && mCurrentOperation != null
582                 && mCurrentOperation.mClientMonitor instanceof Interruptable
583                 && mCurrentOperation.mState == Operation.STATE_STARTED) {
584             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
585             cancelInternal(mCurrentOperation);
586         }
587 
588         startNextOperationIfIdle();
589     }
590 
cancelInternal(Operation operation)591     private void cancelInternal(Operation operation) {
592         if (operation != mCurrentOperation) {
593             Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
594             return;
595         }
596         if (!(operation.mClientMonitor instanceof Interruptable)) {
597             Slog.w(getTag(), "Operation not interruptable: " + operation);
598             return;
599         }
600         if (operation.mState == Operation.STATE_STARTED_CANCELING) {
601             Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
602             return;
603         }
604         if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
605             Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
606             // We can set it to null immediately, since the HAL was never notified to start.
607             mCurrentOperation = null;
608             startNextOperationIfIdle();
609             return;
610         }
611         Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor);
612         final Interruptable interruptable = (Interruptable) operation.mClientMonitor;
613         interruptable.cancel();
614         operation.mState = Operation.STATE_STARTED_CANCELING;
615 
616         // Add a watchdog. If the HAL does not acknowledge within the timeout, we will
617         // forcibly finish this client.
618         mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
619                 CancellationWatchdog.DELAY_MS);
620     }
621 
622     /**
623      * Requests to cancel enrollment.
624      * @param token from the caller, should match the token passed in when requesting enrollment
625      */
cancelEnrollment(IBinder token)626     public void cancelEnrollment(IBinder token) {
627         if (mCurrentOperation == null) {
628             Slog.e(getTag(), "Unable to cancel enrollment, null operation");
629             return;
630         }
631         final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient;
632         final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
633         if (!isEnrolling || !tokenMatches) {
634             Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
635                     + " tokenMatches: " + tokenMatches);
636             return;
637         }
638 
639         cancelInternal(mCurrentOperation);
640     }
641 
642     /**
643      * Requests to cancel authentication or detection.
644      * @param token from the caller, should match the token passed in when requesting authentication
645      */
cancelAuthenticationOrDetection(IBinder token)646     public void cancelAuthenticationOrDetection(IBinder token) {
647         if (mCurrentOperation == null) {
648             Slog.e(getTag(), "Unable to cancel authentication, null operation");
649             return;
650         }
651         final boolean isCorrectClient = isAuthenticationOrDetectionOperation(mCurrentOperation);
652         final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
653 
654         Slog.d(getTag(), "cancelAuthenticationOrDetection, isCorrectClient: " + isCorrectClient
655                 + ", tokenMatches: " + tokenMatches);
656 
657         if (isCorrectClient && tokenMatches) {
658             Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
659             cancelInternal(mCurrentOperation);
660         } else if (!isCorrectClient) {
661             // Look through the current queue for all authentication clients for the specified
662             // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
663             // all of them, instead of just the first one, since the API surface currently doesn't
664             // allow us to distinguish between multiple authentication requests from the same
665             // process. However, this generally does not happen anyway, and would be a class of
666             // bugs on its own.
667             for (Operation operation : mPendingOperations) {
668                 if (isAuthenticationOrDetectionOperation(operation)
669                         && operation.mClientMonitor.getToken() == token) {
670                     Slog.d(getTag(), "Marking " + operation
671                             + " as STATE_WAITING_IN_QUEUE_CANCELING");
672                     operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
673                 }
674             }
675         }
676     }
677 
isAuthenticationOrDetectionOperation(@onNull Operation operation)678     private boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
679         final boolean isAuthentication = operation.mClientMonitor
680                 instanceof AuthenticationConsumer;
681         final boolean isDetection = operation.mClientMonitor instanceof DetectionConsumer;
682         return isAuthentication || isDetection;
683     }
684 
685     /**
686      * @return the current operation
687      */
getCurrentClient()688     public BaseClientMonitor getCurrentClient() {
689         if (mCurrentOperation == null) {
690             return null;
691         }
692         return mCurrentOperation.mClientMonitor;
693     }
694 
getCurrentPendingCount()695     public int getCurrentPendingCount() {
696         return mPendingOperations.size();
697     }
698 
recordCrashState()699     public void recordCrashState() {
700         if (mCrashStates.size() >= CrashState.NUM_ENTRIES) {
701             mCrashStates.removeFirst();
702         }
703         final SimpleDateFormat dateFormat =
704                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
705         final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
706         final List<String> pendingOperations = new ArrayList<>();
707         for (Operation operation : mPendingOperations) {
708             pendingOperations.add(operation.toString());
709         }
710 
711         final CrashState crashState = new CrashState(timestamp,
712                 mCurrentOperation != null ? mCurrentOperation.toString() : null,
713                 pendingOperations);
714         mCrashStates.add(crashState);
715         Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
716     }
717 
dump(PrintWriter pw)718     public void dump(PrintWriter pw) {
719         pw.println("Dump of BiometricScheduler " + getTag());
720         pw.println("Type: " + mSensorType);
721         pw.println("Current operation: " + mCurrentOperation);
722         pw.println("Pending operations: " + mPendingOperations.size());
723         for (Operation operation : mPendingOperations) {
724             pw.println("Pending operation: " + operation);
725         }
726         for (CrashState crashState : mCrashStates) {
727             pw.println("Crash State " + crashState);
728         }
729     }
730 
dumpProtoState(boolean clearSchedulerBuffer)731     public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
732         final ProtoOutputStream proto = new ProtoOutputStream();
733         proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
734                 ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
735         proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
736 
737         if (!mRecentOperations.isEmpty()) {
738             for (int i = 0; i < mRecentOperations.size(); i++) {
739                 proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
740             }
741         } else {
742             // TODO:(b/178828362) Unsure why protobuf has a problem decoding when an empty list
743             //  is returned. So, let's just add a no-op for this case.
744             proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, BiometricsProto.CM_NONE);
745         }
746         proto.flush();
747 
748         if (clearSchedulerBuffer) {
749             mRecentOperations.clear();
750         }
751         return proto.getBytes();
752     }
753 
754     /**
755      * Clears the scheduler of anything work-related. This should be used for example when the
756      * HAL dies.
757      */
reset()758     public void reset() {
759         mPendingOperations.clear();
760         mCurrentOperation = null;
761     }
762 }
763