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