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