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.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
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 @MainThread
59 public class BiometricScheduler {
60 
61     private static final String BASE_TAG = "BiometricScheduler";
62     // Number of recent operations to keep in our logs for dumpsys
63     protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
64 
65     /**
66      * Unknown sensor type. This should never be used, and is a sign that something is wrong during
67      * initialization.
68      */
69     public static final int SENSOR_TYPE_UNKNOWN = 0;
70 
71     /**
72      * Face authentication.
73      */
74     public static final int SENSOR_TYPE_FACE = 1;
75 
76     /**
77      * Any UDFPS type. See {@link FingerprintSensorPropertiesInternal#isAnyUdfpsType()}.
78      */
79     public static final int SENSOR_TYPE_UDFPS = 2;
80 
81     /**
82      * Any other fingerprint sensor. We can add additional definitions in the future when necessary.
83      */
84     public static final int SENSOR_TYPE_FP_OTHER = 3;
85 
86     @IntDef({SENSOR_TYPE_UNKNOWN, SENSOR_TYPE_FACE, SENSOR_TYPE_UDFPS, SENSOR_TYPE_FP_OTHER})
87     @Retention(RetentionPolicy.SOURCE)
88     public @interface SensorType {}
89 
sensorTypeFromFingerprintProperties( @onNull FingerprintSensorPropertiesInternal props)90     public static @SensorType int sensorTypeFromFingerprintProperties(
91             @NonNull FingerprintSensorPropertiesInternal props) {
92         if (props.isAnyUdfpsType()) {
93             return SENSOR_TYPE_UDFPS;
94         }
95 
96         return SENSOR_TYPE_FP_OTHER;
97     }
98 
sensorTypeToString(@ensorType int sensorType)99     public static String sensorTypeToString(@SensorType int sensorType) {
100         switch (sensorType) {
101             case SENSOR_TYPE_UNKNOWN:
102                 return "Unknown";
103             case SENSOR_TYPE_FACE:
104                 return "Face";
105             case SENSOR_TYPE_UDFPS:
106                 return "Udfps";
107             case SENSOR_TYPE_FP_OTHER:
108                 return "OtherFp";
109             default:
110                 return "UnknownUnknown";
111         }
112     }
113 
114     private static final class CrashState {
115         static final int NUM_ENTRIES = 10;
116         final String timestamp;
117         final String currentOperation;
118         final List<String> pendingOperations;
119 
CrashState(String timestamp, String currentOperation, List<String> pendingOperations)120         CrashState(String timestamp, String currentOperation, List<String> pendingOperations) {
121             this.timestamp = timestamp;
122             this.currentOperation = currentOperation;
123             this.pendingOperations = pendingOperations;
124         }
125 
126         @Override
toString()127         public String toString() {
128             final StringBuilder sb = new StringBuilder();
129             sb.append(timestamp).append(": ");
130             sb.append("Current Operation: {").append(currentOperation).append("}");
131             sb.append(", Pending Operations(").append(pendingOperations.size()).append(")");
132 
133             if (!pendingOperations.isEmpty()) {
134                 sb.append(": ");
135             }
136             for (int i = 0; i < pendingOperations.size(); i++) {
137                 sb.append(pendingOperations.get(i));
138                 if (i < pendingOperations.size() - 1) {
139                     sb.append(", ");
140                 }
141             }
142             return sb.toString();
143         }
144     }
145 
146     @NonNull protected final String mBiometricTag;
147     private final @SensorType int mSensorType;
148     @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
149     @NonNull private final IBiometricService mBiometricService;
150     @NonNull protected final Handler mHandler;
151     @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations;
152     @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation;
153     @NonNull private final ArrayDeque<CrashState> mCrashStates;
154 
155     private int mTotalOperationsHandled;
156     private final int mRecentOperationsLimit;
157     @NonNull private final List<Integer> mRecentOperations;
158     @NonNull private final CoexCoordinator mCoexCoordinator;
159 
160     // Internal callback, notified when an operation is complete. Notifies the requester
161     // that the operation is complete, before performing internal scheduler work (such as
162     // starting the next client).
163     private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
164         @Override
165         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
166             Slog.d(getTag(), "[Started] " + clientMonitor);
167 
168             if (clientMonitor instanceof AuthenticationClient) {
169                 mCoexCoordinator.addAuthenticationClient(mSensorType,
170                         (AuthenticationClient<?>) clientMonitor);
171             }
172         }
173 
174         @Override
175         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
176             mHandler.post(() -> {
177                 if (mCurrentOperation == null) {
178                     Slog.e(getTag(), "[Finishing] " + clientMonitor
179                             + " but current operation is null, success: " + success
180                             + ", possible lifecycle bug in clientMonitor implementation?");
181                     return;
182                 }
183 
184                 if (!mCurrentOperation.isFor(clientMonitor)) {
185                     Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match"
186                             + " current: " + mCurrentOperation);
187                     return;
188                 }
189 
190                 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success);
191                 if (clientMonitor instanceof AuthenticationClient) {
192                     mCoexCoordinator.removeAuthenticationClient(mSensorType,
193                             (AuthenticationClient<?>) clientMonitor);
194                 }
195 
196                 if (mGestureAvailabilityDispatcher != null) {
197                     mGestureAvailabilityDispatcher.markSensorActive(
198                             mCurrentOperation.getSensorId(), false /* active */);
199                 }
200 
201                 if (mRecentOperations.size() >= mRecentOperationsLimit) {
202                     mRecentOperations.remove(0);
203                 }
204                 mRecentOperations.add(mCurrentOperation.getProtoEnum());
205                 mCurrentOperation = null;
206                 mTotalOperationsHandled++;
207                 startNextOperationIfIdle();
208             });
209         }
210     };
211 
212     @VisibleForTesting
BiometricScheduler(@onNull String tag, @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, int recentOperationsLimit, @NonNull CoexCoordinator coexCoordinator)213     BiometricScheduler(@NonNull String tag,
214             @NonNull Handler handler,
215             @SensorType int sensorType,
216             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
217             @NonNull IBiometricService biometricService,
218             int recentOperationsLimit,
219             @NonNull CoexCoordinator coexCoordinator) {
220         mBiometricTag = tag;
221         mHandler = handler;
222         mSensorType = sensorType;
223         mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
224         mPendingOperations = new ArrayDeque<>();
225         mBiometricService = biometricService;
226         mCrashStates = new ArrayDeque<>();
227         mRecentOperationsLimit = recentOperationsLimit;
228         mRecentOperations = new ArrayList<>();
229         mCoexCoordinator = coexCoordinator;
230     }
231 
232     /**
233      * Creates a new scheduler.
234      *
235      * @param tag for the specific instance of the scheduler. Should be unique.
236      * @param sensorType the sensorType that this scheduler is handling.
237      * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
238      *                                      (such as fingerprint swipe).
239      */
BiometricScheduler(@onNull String tag, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher)240     public BiometricScheduler(@NonNull String tag,
241             @SensorType int sensorType,
242             @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
243         this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
244                 IBiometricService.Stub.asInterface(
245                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
246                 LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance());
247     }
248 
249     @VisibleForTesting
getInternalCallback()250     public BaseClientMonitor.Callback getInternalCallback() {
251         return mInternalCallback;
252     }
253 
getTag()254     protected String getTag() {
255         return BASE_TAG + "/" + mBiometricTag;
256     }
257 
startNextOperationIfIdle()258     protected void startNextOperationIfIdle() {
259         if (mCurrentOperation != null) {
260             Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
261             return;
262         }
263         if (mPendingOperations.isEmpty()) {
264             Slog.d(getTag(), "No operations, returning to idle");
265             return;
266         }
267 
268         mCurrentOperation = mPendingOperations.poll();
269         Slog.d(getTag(), "[Polled] " + mCurrentOperation);
270 
271         // If the operation at the front of the queue has been marked for cancellation, send
272         // ERROR_CANCELED. No need to start this client.
273         if (mCurrentOperation.isMarkedCanceling()) {
274             Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
275             mCurrentOperation.cancel(mHandler, mInternalCallback);
276             // Now we wait for the client to send its FinishCallback, which kicks off the next
277             // operation.
278             return;
279         }
280 
281         if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) {
282             mGestureAvailabilityDispatcher.markSensorActive(
283                     mCurrentOperation.getSensorId(), true /* active */);
284         }
285 
286         // Not all operations start immediately. BiometricPrompt waits for its operation
287         // to arrive at the head of the queue, before pinging it to start.
288         final int cookie = mCurrentOperation.isReadyToStart();
289         if (cookie == 0) {
290             if (!mCurrentOperation.start(mInternalCallback)) {
291                 // Note down current length of queue
292                 final int pendingOperationsLength = mPendingOperations.size();
293                 final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast();
294                 Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
295                         + ". Last pending operation: " + lastOperation);
296 
297                 // Then for each operation currently in the pending queue at the time of this
298                 // failure, do the same as above. Otherwise, it's possible that something like
299                 // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
300                 for (int i = 0; i < pendingOperationsLength; i++) {
301                     final BiometricSchedulerOperation operation = mPendingOperations.pollFirst();
302                     if (operation != null) {
303                         Slog.w(getTag(), "[Aborting Operation] " + operation);
304                         operation.abort();
305                     } else {
306                         Slog.e(getTag(), "Null operation, index: " + i
307                                 + ", expected length: " + pendingOperationsLength);
308                     }
309                 }
310 
311                 // It's possible that during cleanup a new set of operations came in. We can try to
312                 // run these. A single request from the manager layer to the service layer may
313                 // actually be multiple operations (i.e. updateActiveUser + authenticate).
314                 mCurrentOperation = null;
315                 startNextOperationIfIdle();
316             }
317         } else {
318             try {
319                 mBiometricService.onReadyForAuthentication(cookie);
320             } catch (RemoteException e) {
321                 Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
322             }
323             Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
324         }
325     }
326 
327     /**
328      * Starts the {@link #mCurrentOperation} if
329      * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and
330      * 2) its cookie matches this cookie
331      *
332      * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
333      * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
334      * the BiometricService client becomes the current client in the scheduler), the cookie is
335      * returned to BiometricService. Once BiometricService decides that authentication can start,
336      * it invokes this code path.
337      *
338      * @param cookie of the operation to be started
339      */
startPreparedClient(int cookie)340     public void startPreparedClient(int cookie) {
341         if (mCurrentOperation == null) {
342             Slog.e(getTag(), "Current operation is null");
343             return;
344         }
345 
346         if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) {
347             Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation);
348         } else {
349             Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
350             mCurrentOperation = null;
351             startNextOperationIfIdle();
352         }
353     }
354 
355     /**
356      * Adds a {@link BaseClientMonitor} to the pending queue
357      *
358      * @param clientMonitor operation to be scheduled
359      */
scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor)360     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor) {
361         scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */);
362     }
363 
364     /**
365      * Adds a {@link BaseClientMonitor} to the pending queue
366      *
367      * @param clientMonitor        operation to be scheduled
368      * @param clientCallback optional callback, invoked when the client state changes.
369      */
scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor, @Nullable BaseClientMonitor.Callback clientCallback)370     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
371             @Nullable BaseClientMonitor.Callback clientCallback) {
372         // If the incoming operation should interrupt preceding clients, mark any interruptable
373         // pending clients as canceling. Once they reach the head of the queue, the scheduler will
374         // send ERROR_CANCELED and skip the operation.
375         if (clientMonitor.interruptsPrecedingClients()) {
376             for (BiometricSchedulerOperation operation : mPendingOperations) {
377                 if (operation.markCanceling()) {
378                     Slog.d(getTag(), "New client, marking pending op as canceling: " + operation);
379                 }
380             }
381         }
382 
383         mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback));
384         Slog.d(getTag(), "[Added] " + clientMonitor
385                 + ", new queue size: " + mPendingOperations.size());
386 
387         // If the new operation should interrupt preceding clients, and if the current operation is
388         // cancellable, start the cancellation process.
389         if (clientMonitor.interruptsPrecedingClients()
390                 && mCurrentOperation != null
391                 && mCurrentOperation.isInterruptable()
392                 && mCurrentOperation.isStarted()) {
393             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
394             mCurrentOperation.cancel(mHandler, mInternalCallback);
395         } else {
396             startNextOperationIfIdle();
397         }
398     }
399 
400     /**
401      * Requests to cancel enrollment.
402      * @param token from the caller, should match the token passed in when requesting enrollment
403      */
cancelEnrollment(IBinder token, long requestId)404     public void cancelEnrollment(IBinder token, long requestId) {
405         Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId);
406 
407         if (mCurrentOperation != null
408                 && canCancelEnrollOperation(mCurrentOperation, token, requestId)) {
409             Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation);
410             mCurrentOperation.cancel(mHandler, mInternalCallback);
411         } else {
412             for (BiometricSchedulerOperation operation : mPendingOperations) {
413                 if (canCancelEnrollOperation(operation, token, requestId)) {
414                     Slog.d(getTag(), "Cancelling pending enrollment op: " + operation);
415                     operation.markCanceling();
416                 }
417             }
418         }
419     }
420 
421     /**
422      * Requests to cancel authentication or detection.
423      * @param token from the caller, should match the token passed in when requesting authentication
424      * @param requestId the id returned when requesting authentication
425      */
cancelAuthenticationOrDetection(IBinder token, long requestId)426     public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
427         Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId);
428 
429         if (mCurrentOperation != null
430                 && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
431             Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation);
432             mCurrentOperation.cancel(mHandler, mInternalCallback);
433         } else {
434             for (BiometricSchedulerOperation operation : mPendingOperations) {
435                 if (canCancelAuthOperation(operation, token, requestId)) {
436                     Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation);
437                     operation.markCanceling();
438                 }
439             }
440         }
441     }
442 
canCancelEnrollOperation(BiometricSchedulerOperation operation, IBinder token, long requestId)443     private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation,
444             IBinder token, long requestId) {
445         return operation.isEnrollOperation()
446                 && operation.isMatchingToken(token)
447                 && operation.isMatchingRequestId(requestId);
448     }
449 
canCancelAuthOperation(BiometricSchedulerOperation operation, IBinder token, long requestId)450     private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation,
451             IBinder token, long requestId) {
452         // TODO: restrict callers that can cancel without requestId (negative value)?
453         return operation.isAuthenticationOrDetectionOperation()
454                 && operation.isMatchingToken(token)
455                 && operation.isMatchingRequestId(requestId);
456     }
457 
458     /**
459      * @return the current operation
460      */
getCurrentClient()461     public BaseClientMonitor getCurrentClient() {
462         return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null;
463     }
464 
getCurrentPendingCount()465     public int getCurrentPendingCount() {
466         return mPendingOperations.size();
467     }
468 
recordCrashState()469     public void recordCrashState() {
470         if (mCrashStates.size() >= CrashState.NUM_ENTRIES) {
471             mCrashStates.removeFirst();
472         }
473         final SimpleDateFormat dateFormat =
474                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
475         final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
476         final List<String> pendingOperations = new ArrayList<>();
477         for (BiometricSchedulerOperation operation : mPendingOperations) {
478             pendingOperations.add(operation.toString());
479         }
480 
481         final CrashState crashState = new CrashState(timestamp,
482                 mCurrentOperation != null ? mCurrentOperation.toString() : null,
483                 pendingOperations);
484         mCrashStates.add(crashState);
485         Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
486     }
487 
dump(PrintWriter pw)488     public void dump(PrintWriter pw) {
489         pw.println("Dump of BiometricScheduler " + getTag());
490         pw.println("Type: " + mSensorType);
491         pw.println("Current operation: " + mCurrentOperation);
492         pw.println("Pending operations: " + mPendingOperations.size());
493         for (BiometricSchedulerOperation operation : mPendingOperations) {
494             pw.println("Pending operation: " + operation);
495         }
496         for (CrashState crashState : mCrashStates) {
497             pw.println("Crash State " + crashState);
498         }
499     }
500 
dumpProtoState(boolean clearSchedulerBuffer)501     public byte[] dumpProtoState(boolean clearSchedulerBuffer) {
502         final ProtoOutputStream proto = new ProtoOutputStream();
503         proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
504                 ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE);
505         proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
506 
507         if (!mRecentOperations.isEmpty()) {
508             for (int i = 0; i < mRecentOperations.size(); i++) {
509                 proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
510             }
511         } else {
512             // TODO:(b/178828362) Unsure why protobuf has a problem decoding when an empty list
513             //  is returned. So, let's just add a no-op for this case.
514             proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, BiometricsProto.CM_NONE);
515         }
516         proto.flush();
517 
518         if (clearSchedulerBuffer) {
519             mRecentOperations.clear();
520         }
521         return proto.getBytes();
522     }
523 
524     /**
525      * Clears the scheduler of anything work-related. This should be used for example when the
526      * HAL dies.
527      */
reset()528     public void reset() {
529         Slog.d(getTag(), "Resetting scheduler");
530         mPendingOperations.clear();
531         mCurrentOperation = null;
532     }
533 }
534