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