1 /*
2  * Copyright (C) 2021 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.hardware.biometrics.BiometricConstants;
23 import android.os.Build;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.util.Slog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.ArrayUtils;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.Arrays;
34 import java.util.function.BooleanSupplier;
35 
36 /**
37  * Contains all the necessary information for a HAL operation.
38  */
39 public class BiometricSchedulerOperation {
40     protected static final String TAG = "BiometricSchedulerOperation";
41 
42     /**
43      * The operation is added to the list of pending operations and waiting for its turn.
44      */
45     protected static final int STATE_WAITING_IN_QUEUE = 0;
46 
47     /**
48      * The operation is added to the list of pending operations, but a subsequent operation
49      * has been added. This state only applies to interruptable operations. When this
50      * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
51      */
52     protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
53 
54     /**
55      * The operation has reached the front of the queue and has started.
56      */
57     protected static final int STATE_STARTED = 2;
58 
59     /**
60      * The operation was started, but is now canceling. Operations should wait for the HAL to
61      * acknowledge that the operation was canceled, at which point it finishes.
62      */
63     protected static final int STATE_STARTED_CANCELING = 3;
64 
65     /**
66      * The operation has reached the head of the queue but is waiting for BiometricService
67      * to acknowledge and start the operation.
68      */
69     protected static final int STATE_WAITING_FOR_COOKIE = 4;
70 
71     /**
72      * The {@link ClientMonitorCallback} has been invoked and the client is finished.
73      */
74     protected static final int STATE_FINISHED = 5;
75 
76     @IntDef({STATE_WAITING_IN_QUEUE,
77             STATE_WAITING_IN_QUEUE_CANCELING,
78             STATE_STARTED,
79             STATE_STARTED_CANCELING,
80             STATE_WAITING_FOR_COOKIE,
81             STATE_FINISHED})
82     @Retention(RetentionPolicy.SOURCE)
83     protected @interface OperationState {}
84 
85     private static final int CANCEL_WATCHDOG_DELAY_MS = 3000;
86 
87     @NonNull
88     private final BaseClientMonitor mClientMonitor;
89     @Nullable
90     private final ClientMonitorCallback mClientCallback;
91     @NonNull
92     private final BooleanSupplier mIsDebuggable;
93     @Nullable
94     private ClientMonitorCallback mOnStartCallback;
95     @OperationState
96     private int mState;
97     @VisibleForTesting
98     @NonNull
99     final Runnable mCancelWatchdog;
100 
BiometricSchedulerOperation( @onNull BaseClientMonitor clientMonitor, @Nullable ClientMonitorCallback callback )101     BiometricSchedulerOperation(
102             @NonNull BaseClientMonitor clientMonitor,
103             @Nullable ClientMonitorCallback callback
104     ) {
105         this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
106     }
107 
108     @VisibleForTesting
BiometricSchedulerOperation( @onNull BaseClientMonitor clientMonitor, @Nullable ClientMonitorCallback callback, @NonNull BooleanSupplier isDebuggable )109     BiometricSchedulerOperation(
110             @NonNull BaseClientMonitor clientMonitor,
111             @Nullable ClientMonitorCallback callback,
112             @NonNull BooleanSupplier isDebuggable
113     ) {
114         this(clientMonitor, callback, STATE_WAITING_IN_QUEUE, isDebuggable);
115     }
116 
BiometricSchedulerOperation( @onNull BaseClientMonitor clientMonitor, @Nullable ClientMonitorCallback callback, @OperationState int state )117     protected BiometricSchedulerOperation(
118             @NonNull BaseClientMonitor clientMonitor,
119             @Nullable ClientMonitorCallback callback,
120             @OperationState int state
121     ) {
122         this(clientMonitor, callback, state, Build::isDebuggable);
123     }
124 
BiometricSchedulerOperation( @onNull BaseClientMonitor clientMonitor, @Nullable ClientMonitorCallback callback, @OperationState int state, @NonNull BooleanSupplier isDebuggable )125     private BiometricSchedulerOperation(
126             @NonNull BaseClientMonitor clientMonitor,
127             @Nullable ClientMonitorCallback callback,
128             @OperationState int state,
129             @NonNull BooleanSupplier isDebuggable
130     ) {
131         mClientMonitor = clientMonitor;
132         mClientCallback = callback;
133         mState = state;
134         mIsDebuggable = isDebuggable;
135         mCancelWatchdog = () -> {
136             if (!isFinished()) {
137                 Slog.e(TAG, "[Watchdog Triggered]: " + this);
138                 getWrappedCallback(mOnStartCallback)
139                         .onClientFinished(mClientMonitor, false /* success */);
140             }
141         };
142     }
143 
144     /**
145      * Zero if this operation is ready to start or has already started. A non-zero cookie
146      * is returned if the operation has not started and is waiting on
147      * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}.
148      *
149      * @return cookie or 0 if ready/started
150      */
isReadyToStart(@onNull ClientMonitorCallback callback)151     public int isReadyToStart(@NonNull ClientMonitorCallback callback) {
152         if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) {
153             final int cookie = mClientMonitor.getCookie();
154             if (cookie != 0) {
155                 mState = STATE_WAITING_FOR_COOKIE;
156                 mClientMonitor.waitForCookie(getWrappedCallback(callback));
157             }
158             return cookie;
159         }
160 
161         return 0;
162     }
163 
164     /**
165      * Start this operation without waiting for a cookie
166      * (i.e. {@link #isReadyToStart(ClientMonitorCallback)}  returns zero}
167      *
168      * @param callback lifecycle callback
169      * @return if this operation started
170      */
start(@onNull ClientMonitorCallback callback)171     public boolean start(@NonNull ClientMonitorCallback callback) {
172         if (errorWhenNoneOf("start",
173                 STATE_WAITING_IN_QUEUE,
174                 STATE_WAITING_FOR_COOKIE,
175                 STATE_WAITING_IN_QUEUE_CANCELING)) {
176             return false;
177         }
178 
179         if (mClientMonitor.getCookie() != 0) {
180             String err = "operation requires cookie";
181             if (mIsDebuggable.getAsBoolean()) {
182                 throw new IllegalStateException(err);
183             }
184             Slog.e(TAG, err);
185         }
186 
187         return doStart(callback);
188     }
189 
190     /**
191      * Start this operation after receiving the given cookie.
192      *
193      * @param callback lifecycle callback
194      * @param cookie   cookie indicting the operation should begin
195      * @return if this operation started
196      */
startWithCookie(@onNull ClientMonitorCallback callback, int cookie)197     public boolean startWithCookie(@NonNull ClientMonitorCallback callback, int cookie) {
198         if (mClientMonitor.getCookie() != cookie) {
199             Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
200             return false;
201         }
202 
203         if (errorWhenNoneOf("start",
204                 STATE_WAITING_IN_QUEUE,
205                 STATE_WAITING_FOR_COOKIE,
206                 STATE_WAITING_IN_QUEUE_CANCELING)) {
207             return false;
208         }
209 
210         return doStart(callback);
211     }
212 
doStart(@onNull ClientMonitorCallback callback)213     private boolean doStart(@NonNull ClientMonitorCallback callback) {
214         mOnStartCallback = callback;
215         final ClientMonitorCallback cb = getWrappedCallback(callback);
216 
217         if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
218             Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
219 
220             cb.onClientFinished(mClientMonitor, true /* success */);
221             if (mClientMonitor instanceof ErrorConsumer) {
222                 final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor;
223                 errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
224                         0 /* vendorCode */);
225             } else {
226                 Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer");
227             }
228 
229             return false;
230         }
231 
232         if (isUnstartableHalOperation()) {
233             Slog.v(TAG, "unable to start: " + this);
234             ((HalClientMonitor<?>) mClientMonitor).unableToStart();
235             cb.onClientFinished(mClientMonitor, false /* success */);
236             return false;
237         }
238 
239         mState = STATE_STARTED;
240         mClientMonitor.start(cb);
241 
242         Slog.v(TAG, "started: " + this);
243         return true;
244     }
245 
246     /**
247      * Abort a pending operation.
248      *
249      * This is similar to cancel but the operation must not have been started. It will
250      * immediately abort the operation and notify the client that it has finished unsuccessfully.
251      */
abort()252     public void abort() {
253         if (errorWhenNoneOf("abort",
254                 STATE_WAITING_IN_QUEUE,
255                 STATE_WAITING_FOR_COOKIE,
256                 STATE_WAITING_IN_QUEUE_CANCELING)) {
257             return;
258         }
259 
260         if (isHalOperation()) {
261             ((HalClientMonitor<?>) mClientMonitor).unableToStart();
262         }
263         getWrappedCallback().onClientFinished(mClientMonitor, false /* success */);
264 
265         Slog.v(TAG, "Aborted: " + this);
266     }
267 
268     /** Flags this operation as canceled, if possible, but does not cancel it until started. */
markCanceling()269     public boolean markCanceling() {
270         if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) {
271             mState = STATE_WAITING_IN_QUEUE_CANCELING;
272             return true;
273         }
274         return false;
275     }
276 
markCancelingForWatchdog()277     @VisibleForTesting void markCancelingForWatchdog() {
278         mState = STATE_WAITING_IN_QUEUE_CANCELING;
279     }
280 
281     /**
282      * Cancel the operation now.
283      *
284      * @param handler handler to use for the cancellation watchdog
285      * @param callback lifecycle callback (only used if this operation hasn't started, otherwise
286      *                 the callback used from {@link #start(ClientMonitorCallback)} is used)
287      */
cancel(@onNull Handler handler, @NonNull ClientMonitorCallback callback)288     public void cancel(@NonNull Handler handler, @NonNull ClientMonitorCallback callback) {
289         if (errorWhenOneOf("cancel", STATE_FINISHED)) {
290             return;
291         }
292 
293         final int currentState = mState;
294         if (currentState == STATE_STARTED_CANCELING) {
295             Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this);
296             return;
297         }
298 
299         mState = STATE_STARTED_CANCELING;
300         if (currentState == STATE_WAITING_IN_QUEUE
301                 || currentState == STATE_WAITING_IN_QUEUE_CANCELING
302                 || currentState == STATE_WAITING_FOR_COOKIE) {
303             Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor);
304             mClientMonitor.cancelWithoutStarting(getWrappedCallback(callback));
305         } else {
306             Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor);
307             mClientMonitor.cancel();
308         }
309 
310         // forcibly finish this client if the HAL does not acknowledge within the timeout
311         handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS);
312     }
313 
314     @NonNull
getWrappedCallback()315     private ClientMonitorCallback getWrappedCallback() {
316         return getWrappedCallback(null);
317     }
318 
319     @NonNull
getWrappedCallback( @ullable ClientMonitorCallback callback)320     private ClientMonitorCallback getWrappedCallback(
321             @Nullable ClientMonitorCallback callback) {
322         final ClientMonitorCallback destroyCallback = new ClientMonitorCallback() {
323             @Override
324             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
325                     boolean success) {
326                 Slog.d(TAG, "[Finished / destroy]: " + clientMonitor);
327                 mClientMonitor.destroy();
328                 mState = STATE_FINISHED;
329             }
330         };
331         return new ClientMonitorCompositeCallback(destroyCallback, callback, mClientCallback);
332     }
333 
334     /** {@link BaseClientMonitor#getSensorId()}. */
getSensorId()335     public int getSensorId() {
336         return mClientMonitor.getSensorId();
337     }
338 
339     /** {@link BaseClientMonitor#getProtoEnum()}. */
getProtoEnum()340     public int getProtoEnum() {
341         return mClientMonitor.getProtoEnum();
342     }
343 
344     /** {@link BaseClientMonitor#getTargetUserId()}. */
getTargetUserId()345     public int getTargetUserId() {
346         return mClientMonitor.getTargetUserId();
347     }
348 
349     /** If the given clientMonitor is the same as the one in the constructor. */
isFor(@onNull BaseClientMonitor clientMonitor)350     public boolean isFor(@NonNull BaseClientMonitor clientMonitor) {
351         return mClientMonitor == clientMonitor;
352     }
353 
354     /** If this operation is interruptable. */
isInterruptable()355     public boolean isInterruptable() {
356         return mClientMonitor.isInterruptable();
357     }
358 
isHalOperation()359     private boolean isHalOperation() {
360         return mClientMonitor instanceof HalClientMonitor<?>;
361     }
362 
isUnstartableHalOperation()363     private boolean isUnstartableHalOperation() {
364         if (isHalOperation()) {
365             final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor;
366             if (client.getFreshDaemon() == null) {
367                 return true;
368             }
369         }
370         return false;
371     }
372 
373     /** If this operation is an enrollment. */
isEnrollOperation()374     public boolean isEnrollOperation() {
375         return mClientMonitor instanceof EnrollClient;
376     }
377 
378     /** If this operation is authentication. */
isAuthenticateOperation()379     public boolean isAuthenticateOperation() {
380         return mClientMonitor instanceof AuthenticationClient;
381     }
382 
383     /** If this operation is authentication or detection. */
isAuthenticationOrDetectionOperation()384     public boolean isAuthenticationOrDetectionOperation() {
385         final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer;
386         final boolean isDetection = mClientMonitor instanceof DetectionConsumer;
387         return isAuthentication || isDetection;
388     }
389 
390     /** If this operation performs acquisition {@link AcquisitionClient}. */
isAcquisitionOperation()391     public boolean isAcquisitionOperation() {
392         return mClientMonitor instanceof AcquisitionClient;
393     }
394 
395     /**
396      * If this operation matches the original requestId.
397      *
398      * By default, monitors are not associated with a request id to retain the original
399      * behavior (i.e. if no requestId is explicitly set then assume it matches)
400      *
401      * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}.
402      */
isMatchingRequestId(long requestId)403     public boolean isMatchingRequestId(long requestId) {
404         return !mClientMonitor.hasRequestId()
405                 || mClientMonitor.getRequestId() == requestId;
406     }
407 
408     /** If the token matches */
isMatchingToken(@ullable IBinder token)409     public boolean isMatchingToken(@Nullable IBinder token) {
410         return mClientMonitor.getToken() == token;
411     }
412 
413     /** If this operation has started. */
isStarted()414     public boolean isStarted() {
415         return mState == STATE_STARTED;
416     }
417 
418     /** If this operation is cancelling but has not yet completed. */
isCanceling()419     public boolean isCanceling() {
420         return mState == STATE_STARTED_CANCELING;
421     }
422 
423     /** If this operation has finished and completed its lifecycle. */
isFinished()424     public boolean isFinished() {
425         return mState == STATE_FINISHED;
426     }
427 
428     /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */
isMarkedCanceling()429     public boolean isMarkedCanceling() {
430         return mState == STATE_WAITING_IN_QUEUE_CANCELING;
431     }
432 
433     /**
434      * The monitor passed to the constructor.
435      * @deprecated avoid using and move to encapsulate within the operation
436      */
437     @Deprecated
getClientMonitor()438     public BaseClientMonitor getClientMonitor() {
439         return mClientMonitor;
440     }
441 
errorWhenOneOf(String op, @OperationState int... states)442     private boolean errorWhenOneOf(String op, @OperationState int... states) {
443         final boolean isError = ArrayUtils.contains(states, mState);
444         if (isError) {
445             String err = op + ": mState must not be " + mState;
446             if (mIsDebuggable.getAsBoolean()) {
447                 throw new IllegalStateException(err);
448             }
449             Slog.e(TAG, err);
450         }
451         return isError;
452     }
453 
errorWhenNoneOf(String op, @OperationState int... states)454     private boolean errorWhenNoneOf(String op, @OperationState int... states) {
455         final boolean isError = !ArrayUtils.contains(states, mState);
456         if (isError) {
457             String err = op + ": mState=" + mState + " must be one of " + Arrays.toString(states);
458             if (mIsDebuggable.getAsBoolean()) {
459                 throw new IllegalStateException(err);
460             }
461             Slog.e(TAG, err);
462         }
463         return isError;
464     }
465 
466     @Override
toString()467     public String toString() {
468         return mClientMonitor + ", State: " + mState;
469     }
470 }
471