1 /* 2 * Copyright (C) 2020 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.MainThread; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.hardware.biometrics.IBiometricService; 25 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Looper; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.util.Slog; 32 import android.util.proto.ProtoOutputStream; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.server.biometrics.BiometricSchedulerProto; 36 import com.android.server.biometrics.BiometricsProto; 37 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; 38 39 import java.io.PrintWriter; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.text.SimpleDateFormat; 43 import java.util.ArrayDeque; 44 import java.util.ArrayList; 45 import java.util.Date; 46 import java.util.Deque; 47 import java.util.List; 48 import java.util.Locale; 49 50 /** 51 * A scheduler for biometric HAL operations. Maintains a queue of {@link BaseClientMonitor} 52 * operations, without caring about its implementation details. Operations may perform zero or more 53 * interactions with the HAL before finishing. 54 * 55 * We currently assume (and require) that each biometric sensor have its own instance of a 56 * {@link BiometricScheduler}. See {@link CoexCoordinator}. 57 */ 58 @MainThread 59 public class BiometricScheduler { 60 61 private static final String BASE_TAG = "BiometricScheduler"; 62 // Number of recent operations to keep in our logs for dumpsys 63 protected static final int LOG_NUM_RECENT_OPERATIONS = 50; 64 65 /** 66 * Unknown sensor type. This should never be used, and is a sign that something is wrong during 67 * initialization. 68 */ 69 public static final int SENSOR_TYPE_UNKNOWN = 0; 70 71 /** 72 * Face authentication. 73 */ 74 public static final int SENSOR_TYPE_FACE = 1; 75 76 /** 77 * Any UDFPS type. See {@link FingerprintSensorPropertiesInternal#isAnyUdfpsType()}. 78 */ 79 public static final int SENSOR_TYPE_UDFPS = 2; 80 81 /** 82 * Any other fingerprint sensor. We can add additional definitions in the future when necessary. 83 */ 84 public static final int SENSOR_TYPE_FP_OTHER = 3; 85 86 @IntDef({SENSOR_TYPE_UNKNOWN, SENSOR_TYPE_FACE, SENSOR_TYPE_UDFPS, SENSOR_TYPE_FP_OTHER}) 87 @Retention(RetentionPolicy.SOURCE) 88 public @interface SensorType {} 89 sensorTypeFromFingerprintProperties( @onNull FingerprintSensorPropertiesInternal props)90 public static @SensorType int sensorTypeFromFingerprintProperties( 91 @NonNull FingerprintSensorPropertiesInternal props) { 92 if (props.isAnyUdfpsType()) { 93 return SENSOR_TYPE_UDFPS; 94 } 95 96 return SENSOR_TYPE_FP_OTHER; 97 } 98 sensorTypeToString(@ensorType int sensorType)99 public static String sensorTypeToString(@SensorType int sensorType) { 100 switch (sensorType) { 101 case SENSOR_TYPE_UNKNOWN: 102 return "Unknown"; 103 case SENSOR_TYPE_FACE: 104 return "Face"; 105 case SENSOR_TYPE_UDFPS: 106 return "Udfps"; 107 case SENSOR_TYPE_FP_OTHER: 108 return "OtherFp"; 109 default: 110 return "UnknownUnknown"; 111 } 112 } 113 114 private static final class CrashState { 115 static final int NUM_ENTRIES = 10; 116 final String timestamp; 117 final String currentOperation; 118 final List<String> pendingOperations; 119 CrashState(String timestamp, String currentOperation, List<String> pendingOperations)120 CrashState(String timestamp, String currentOperation, List<String> pendingOperations) { 121 this.timestamp = timestamp; 122 this.currentOperation = currentOperation; 123 this.pendingOperations = pendingOperations; 124 } 125 126 @Override toString()127 public String toString() { 128 final StringBuilder sb = new StringBuilder(); 129 sb.append(timestamp).append(": "); 130 sb.append("Current Operation: {").append(currentOperation).append("}"); 131 sb.append(", Pending Operations(").append(pendingOperations.size()).append(")"); 132 133 if (!pendingOperations.isEmpty()) { 134 sb.append(": "); 135 } 136 for (int i = 0; i < pendingOperations.size(); i++) { 137 sb.append(pendingOperations.get(i)); 138 if (i < pendingOperations.size() - 1) { 139 sb.append(", "); 140 } 141 } 142 return sb.toString(); 143 } 144 } 145 146 @NonNull protected final String mBiometricTag; 147 private final @SensorType int mSensorType; 148 @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; 149 @NonNull private final IBiometricService mBiometricService; 150 @NonNull protected final Handler mHandler; 151 @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations; 152 @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation; 153 @NonNull private final ArrayDeque<CrashState> mCrashStates; 154 155 private int mTotalOperationsHandled; 156 private final int mRecentOperationsLimit; 157 @NonNull private final List<Integer> mRecentOperations; 158 @NonNull private final CoexCoordinator mCoexCoordinator; 159 160 // Internal callback, notified when an operation is complete. Notifies the requester 161 // that the operation is complete, before performing internal scheduler work (such as 162 // starting the next client). 163 private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() { 164 @Override 165 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { 166 Slog.d(getTag(), "[Started] " + clientMonitor); 167 168 if (clientMonitor instanceof AuthenticationClient) { 169 mCoexCoordinator.addAuthenticationClient(mSensorType, 170 (AuthenticationClient<?>) clientMonitor); 171 } 172 } 173 174 @Override 175 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { 176 mHandler.post(() -> { 177 if (mCurrentOperation == null) { 178 Slog.e(getTag(), "[Finishing] " + clientMonitor 179 + " but current operation is null, success: " + success 180 + ", possible lifecycle bug in clientMonitor implementation?"); 181 return; 182 } 183 184 if (!mCurrentOperation.isFor(clientMonitor)) { 185 Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match" 186 + " current: " + mCurrentOperation); 187 return; 188 } 189 190 Slog.d(getTag(), "[Finishing] " + clientMonitor + ", success: " + success); 191 if (clientMonitor instanceof AuthenticationClient) { 192 mCoexCoordinator.removeAuthenticationClient(mSensorType, 193 (AuthenticationClient<?>) clientMonitor); 194 } 195 196 if (mGestureAvailabilityDispatcher != null) { 197 mGestureAvailabilityDispatcher.markSensorActive( 198 mCurrentOperation.getSensorId(), false /* active */); 199 } 200 201 if (mRecentOperations.size() >= mRecentOperationsLimit) { 202 mRecentOperations.remove(0); 203 } 204 mRecentOperations.add(mCurrentOperation.getProtoEnum()); 205 mCurrentOperation = null; 206 mTotalOperationsHandled++; 207 startNextOperationIfIdle(); 208 }); 209 } 210 }; 211 212 @VisibleForTesting BiometricScheduler(@onNull String tag, @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, int recentOperationsLimit, @NonNull CoexCoordinator coexCoordinator)213 BiometricScheduler(@NonNull String tag, 214 @NonNull Handler handler, 215 @SensorType int sensorType, 216 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, 217 @NonNull IBiometricService biometricService, 218 int recentOperationsLimit, 219 @NonNull CoexCoordinator coexCoordinator) { 220 mBiometricTag = tag; 221 mHandler = handler; 222 mSensorType = sensorType; 223 mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; 224 mPendingOperations = new ArrayDeque<>(); 225 mBiometricService = biometricService; 226 mCrashStates = new ArrayDeque<>(); 227 mRecentOperationsLimit = recentOperationsLimit; 228 mRecentOperations = new ArrayList<>(); 229 mCoexCoordinator = coexCoordinator; 230 } 231 232 /** 233 * Creates a new scheduler. 234 * 235 * @param tag for the specific instance of the scheduler. Should be unique. 236 * @param sensorType the sensorType that this scheduler is handling. 237 * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures 238 * (such as fingerprint swipe). 239 */ BiometricScheduler(@onNull String tag, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher)240 public BiometricScheduler(@NonNull String tag, 241 @SensorType int sensorType, 242 @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { 243 this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher, 244 IBiometricService.Stub.asInterface( 245 ServiceManager.getService(Context.BIOMETRIC_SERVICE)), 246 LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); 247 } 248 249 @VisibleForTesting getInternalCallback()250 public BaseClientMonitor.Callback getInternalCallback() { 251 return mInternalCallback; 252 } 253 getTag()254 protected String getTag() { 255 return BASE_TAG + "/" + mBiometricTag; 256 } 257 startNextOperationIfIdle()258 protected void startNextOperationIfIdle() { 259 if (mCurrentOperation != null) { 260 Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation); 261 return; 262 } 263 if (mPendingOperations.isEmpty()) { 264 Slog.d(getTag(), "No operations, returning to idle"); 265 return; 266 } 267 268 mCurrentOperation = mPendingOperations.poll(); 269 Slog.d(getTag(), "[Polled] " + mCurrentOperation); 270 271 // If the operation at the front of the queue has been marked for cancellation, send 272 // ERROR_CANCELED. No need to start this client. 273 if (mCurrentOperation.isMarkedCanceling()) { 274 Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation); 275 mCurrentOperation.cancel(mHandler, mInternalCallback); 276 // Now we wait for the client to send its FinishCallback, which kicks off the next 277 // operation. 278 return; 279 } 280 281 if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) { 282 mGestureAvailabilityDispatcher.markSensorActive( 283 mCurrentOperation.getSensorId(), true /* active */); 284 } 285 286 // Not all operations start immediately. BiometricPrompt waits for its operation 287 // to arrive at the head of the queue, before pinging it to start. 288 final int cookie = mCurrentOperation.isReadyToStart(); 289 if (cookie == 0) { 290 if (!mCurrentOperation.start(mInternalCallback)) { 291 // Note down current length of queue 292 final int pendingOperationsLength = mPendingOperations.size(); 293 final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast(); 294 Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation 295 + ". Last pending operation: " + lastOperation); 296 297 // Then for each operation currently in the pending queue at the time of this 298 // failure, do the same as above. Otherwise, it's possible that something like 299 // setActiveUser fails, but then authenticate (for the wrong user) is invoked. 300 for (int i = 0; i < pendingOperationsLength; i++) { 301 final BiometricSchedulerOperation operation = mPendingOperations.pollFirst(); 302 if (operation != null) { 303 Slog.w(getTag(), "[Aborting Operation] " + operation); 304 operation.abort(); 305 } else { 306 Slog.e(getTag(), "Null operation, index: " + i 307 + ", expected length: " + pendingOperationsLength); 308 } 309 } 310 311 // It's possible that during cleanup a new set of operations came in. We can try to 312 // run these. A single request from the manager layer to the service layer may 313 // actually be multiple operations (i.e. updateActiveUser + authenticate). 314 mCurrentOperation = null; 315 startNextOperationIfIdle(); 316 } 317 } else { 318 try { 319 mBiometricService.onReadyForAuthentication(cookie); 320 } catch (RemoteException e) { 321 Slog.e(getTag(), "Remote exception when contacting BiometricService", e); 322 } 323 Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation); 324 } 325 } 326 327 /** 328 * Starts the {@link #mCurrentOperation} if 329 * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and 330 * 2) its cookie matches this cookie 331 * 332 * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which 333 * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g. 334 * the BiometricService client becomes the current client in the scheduler), the cookie is 335 * returned to BiometricService. Once BiometricService decides that authentication can start, 336 * it invokes this code path. 337 * 338 * @param cookie of the operation to be started 339 */ startPreparedClient(int cookie)340 public void startPreparedClient(int cookie) { 341 if (mCurrentOperation == null) { 342 Slog.e(getTag(), "Current operation is null"); 343 return; 344 } 345 346 if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) { 347 Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation); 348 } else { 349 Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation); 350 mCurrentOperation = null; 351 startNextOperationIfIdle(); 352 } 353 } 354 355 /** 356 * Adds a {@link BaseClientMonitor} to the pending queue 357 * 358 * @param clientMonitor operation to be scheduled 359 */ scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor)360 public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor) { 361 scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */); 362 } 363 364 /** 365 * Adds a {@link BaseClientMonitor} to the pending queue 366 * 367 * @param clientMonitor operation to be scheduled 368 * @param clientCallback optional callback, invoked when the client state changes. 369 */ scheduleClientMonitor(@onNull BaseClientMonitor clientMonitor, @Nullable BaseClientMonitor.Callback clientCallback)370 public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor, 371 @Nullable BaseClientMonitor.Callback clientCallback) { 372 // If the incoming operation should interrupt preceding clients, mark any interruptable 373 // pending clients as canceling. Once they reach the head of the queue, the scheduler will 374 // send ERROR_CANCELED and skip the operation. 375 if (clientMonitor.interruptsPrecedingClients()) { 376 for (BiometricSchedulerOperation operation : mPendingOperations) { 377 if (operation.markCanceling()) { 378 Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); 379 } 380 } 381 } 382 383 mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback)); 384 Slog.d(getTag(), "[Added] " + clientMonitor 385 + ", new queue size: " + mPendingOperations.size()); 386 387 // If the new operation should interrupt preceding clients, and if the current operation is 388 // cancellable, start the cancellation process. 389 if (clientMonitor.interruptsPrecedingClients() 390 && mCurrentOperation != null 391 && mCurrentOperation.isInterruptable() 392 && mCurrentOperation.isStarted()) { 393 Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); 394 mCurrentOperation.cancel(mHandler, mInternalCallback); 395 } else { 396 startNextOperationIfIdle(); 397 } 398 } 399 400 /** 401 * Requests to cancel enrollment. 402 * @param token from the caller, should match the token passed in when requesting enrollment 403 */ cancelEnrollment(IBinder token, long requestId)404 public void cancelEnrollment(IBinder token, long requestId) { 405 Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId); 406 407 if (mCurrentOperation != null 408 && canCancelEnrollOperation(mCurrentOperation, token, requestId)) { 409 Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation); 410 mCurrentOperation.cancel(mHandler, mInternalCallback); 411 } else { 412 for (BiometricSchedulerOperation operation : mPendingOperations) { 413 if (canCancelEnrollOperation(operation, token, requestId)) { 414 Slog.d(getTag(), "Cancelling pending enrollment op: " + operation); 415 operation.markCanceling(); 416 } 417 } 418 } 419 } 420 421 /** 422 * Requests to cancel authentication or detection. 423 * @param token from the caller, should match the token passed in when requesting authentication 424 * @param requestId the id returned when requesting authentication 425 */ cancelAuthenticationOrDetection(IBinder token, long requestId)426 public void cancelAuthenticationOrDetection(IBinder token, long requestId) { 427 Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId); 428 429 if (mCurrentOperation != null 430 && canCancelAuthOperation(mCurrentOperation, token, requestId)) { 431 Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation); 432 mCurrentOperation.cancel(mHandler, mInternalCallback); 433 } else { 434 for (BiometricSchedulerOperation operation : mPendingOperations) { 435 if (canCancelAuthOperation(operation, token, requestId)) { 436 Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation); 437 operation.markCanceling(); 438 } 439 } 440 } 441 } 442 canCancelEnrollOperation(BiometricSchedulerOperation operation, IBinder token, long requestId)443 private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation, 444 IBinder token, long requestId) { 445 return operation.isEnrollOperation() 446 && operation.isMatchingToken(token) 447 && operation.isMatchingRequestId(requestId); 448 } 449 canCancelAuthOperation(BiometricSchedulerOperation operation, IBinder token, long requestId)450 private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation, 451 IBinder token, long requestId) { 452 // TODO: restrict callers that can cancel without requestId (negative value)? 453 return operation.isAuthenticationOrDetectionOperation() 454 && operation.isMatchingToken(token) 455 && operation.isMatchingRequestId(requestId); 456 } 457 458 /** 459 * @return the current operation 460 */ getCurrentClient()461 public BaseClientMonitor getCurrentClient() { 462 return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null; 463 } 464 getCurrentPendingCount()465 public int getCurrentPendingCount() { 466 return mPendingOperations.size(); 467 } 468 recordCrashState()469 public void recordCrashState() { 470 if (mCrashStates.size() >= CrashState.NUM_ENTRIES) { 471 mCrashStates.removeFirst(); 472 } 473 final SimpleDateFormat dateFormat = 474 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); 475 final String timestamp = dateFormat.format(new Date(System.currentTimeMillis())); 476 final List<String> pendingOperations = new ArrayList<>(); 477 for (BiometricSchedulerOperation operation : mPendingOperations) { 478 pendingOperations.add(operation.toString()); 479 } 480 481 final CrashState crashState = new CrashState(timestamp, 482 mCurrentOperation != null ? mCurrentOperation.toString() : null, 483 pendingOperations); 484 mCrashStates.add(crashState); 485 Slog.e(getTag(), "Recorded crash state: " + crashState.toString()); 486 } 487 dump(PrintWriter pw)488 public void dump(PrintWriter pw) { 489 pw.println("Dump of BiometricScheduler " + getTag()); 490 pw.println("Type: " + mSensorType); 491 pw.println("Current operation: " + mCurrentOperation); 492 pw.println("Pending operations: " + mPendingOperations.size()); 493 for (BiometricSchedulerOperation operation : mPendingOperations) { 494 pw.println("Pending operation: " + operation); 495 } 496 for (CrashState crashState : mCrashStates) { 497 pw.println("Crash State " + crashState); 498 } 499 } 500 dumpProtoState(boolean clearSchedulerBuffer)501 public byte[] dumpProtoState(boolean clearSchedulerBuffer) { 502 final ProtoOutputStream proto = new ProtoOutputStream(); 503 proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null 504 ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE); 505 proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled); 506 507 if (!mRecentOperations.isEmpty()) { 508 for (int i = 0; i < mRecentOperations.size(); i++) { 509 proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i)); 510 } 511 } else { 512 // TODO:(b/178828362) Unsure why protobuf has a problem decoding when an empty list 513 // is returned. So, let's just add a no-op for this case. 514 proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, BiometricsProto.CM_NONE); 515 } 516 proto.flush(); 517 518 if (clearSchedulerBuffer) { 519 mRecentOperations.clear(); 520 } 521 return proto.getBytes(); 522 } 523 524 /** 525 * Clears the scheduler of anything work-related. This should be used for example when the 526 * HAL dies. 527 */ reset()528 public void reset() { 529 Slog.d(getTag(), "Resetting scheduler"); 530 mPendingOperations.clear(); 531 mCurrentOperation = null; 532 } 533 } 534