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