1 /* 2 * Copyright (C) 2018 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.systemui.biometrics; 18 19 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; 20 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.ActivityManager; 25 import android.app.ActivityTaskManager; 26 import android.app.TaskStackListener; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.graphics.PointF; 34 import android.hardware.SensorPrivacyManager; 35 import android.hardware.biometrics.BiometricAuthenticator.Modality; 36 import android.hardware.biometrics.BiometricConstants; 37 import android.hardware.biometrics.BiometricManager.Authenticators; 38 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; 39 import android.hardware.biometrics.BiometricPrompt; 40 import android.hardware.biometrics.IBiometricSysuiReceiver; 41 import android.hardware.biometrics.PromptInfo; 42 import android.hardware.display.DisplayManager; 43 import android.hardware.face.FaceManager; 44 import android.hardware.face.FaceSensorPropertiesInternal; 45 import android.hardware.fingerprint.FingerprintManager; 46 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 47 import android.hardware.fingerprint.FingerprintStateListener; 48 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; 49 import android.hardware.fingerprint.IUdfpsHbmListener; 50 import android.os.Bundle; 51 import android.os.Handler; 52 import android.os.RemoteException; 53 import android.util.Log; 54 import android.util.SparseBooleanArray; 55 import android.view.MotionEvent; 56 import android.view.WindowManager; 57 58 import com.android.internal.R; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.os.SomeArgs; 61 import com.android.systemui.SystemUI; 62 import com.android.systemui.assist.ui.DisplayUtils; 63 import com.android.systemui.dagger.SysUISingleton; 64 import com.android.systemui.dagger.qualifiers.Main; 65 import com.android.systemui.doze.DozeReceiver; 66 import com.android.systemui.statusbar.CommandQueue; 67 import com.android.systemui.util.concurrency.Execution; 68 69 import java.util.ArrayList; 70 import java.util.Arrays; 71 import java.util.HashSet; 72 import java.util.List; 73 import java.util.Set; 74 75 import javax.inject.Inject; 76 import javax.inject.Provider; 77 78 import kotlin.Unit; 79 80 /** 81 * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the 82 * appropriate biometric UI (e.g. BiometricDialogView). 83 * 84 * Also coordinates biometric-related things, such as UDFPS, with 85 * {@link com.android.keyguard.KeyguardUpdateMonitor} 86 */ 87 @SysUISingleton 88 public class AuthController extends SystemUI implements CommandQueue.Callbacks, 89 AuthDialogCallback, DozeReceiver { 90 91 private static final String TAG = "AuthController"; 92 private static final boolean DEBUG = true; 93 private static final int SENSOR_PRIVACY_DELAY = 500; 94 95 private final Handler mHandler; 96 private final Execution mExecution; 97 private final CommandQueue mCommandQueue; 98 private final ActivityTaskManager mActivityTaskManager; 99 @Nullable 100 private final FingerprintManager mFingerprintManager; 101 @Nullable 102 private final FaceManager mFaceManager; 103 private final Provider<UdfpsController> mUdfpsControllerFactory; 104 private final Provider<SidefpsController> mSidefpsControllerFactory; 105 @Nullable 106 private final PointF mFaceAuthSensorLocation; 107 @Nullable 108 private PointF mFingerprintLocation; 109 private final Set<Callback> mCallbacks = new HashSet<>(); 110 111 // TODO: These should just be saved from onSaveState 112 private SomeArgs mCurrentDialogArgs; 113 @VisibleForTesting 114 AuthDialog mCurrentDialog; 115 116 @NonNull private final WindowManager mWindowManager; 117 @Nullable private UdfpsController mUdfpsController; 118 @Nullable private IUdfpsHbmListener mUdfpsHbmListener; 119 @Nullable private SidefpsController mSidefpsController; 120 @VisibleForTesting 121 TaskStackListener mTaskStackListener; 122 @VisibleForTesting 123 IBiometricSysuiReceiver mReceiver; 124 @VisibleForTesting 125 @NonNull final BiometricDisplayListener mOrientationListener; 126 @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps; 127 @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps; 128 @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps; 129 @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; 130 131 @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; 132 private SensorPrivacyManager mSensorPrivacyManager; 133 134 private class BiometricTaskStackListener extends TaskStackListener { 135 @Override onTaskStackChanged()136 public void onTaskStackChanged() { 137 mHandler.post(AuthController.this::handleTaskStackChanged); 138 } 139 } 140 141 private final IFingerprintAuthenticatorsRegisteredCallback 142 mFingerprintAuthenticatorsRegisteredCallback = 143 new IFingerprintAuthenticatorsRegisteredCallback.Stub() { 144 @Override 145 public void onAllAuthenticatorsRegistered( 146 List<FingerprintSensorPropertiesInternal> sensors) { 147 mHandler.post(() -> handleAllAuthenticatorsRegistered(sensors)); 148 } 149 }; 150 151 private final FingerprintStateListener mFingerprintStateListener = 152 new FingerprintStateListener() { 153 @Override 154 public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { 155 mHandler.post( 156 () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments)); 157 } 158 }; 159 160 @VisibleForTesting 161 final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 162 @Override 163 public void onReceive(Context context, Intent intent) { 164 if (mCurrentDialog != null 165 && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { 166 Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received"); 167 mCurrentDialog.dismissWithoutCallback(true /* animate */); 168 mCurrentDialog = null; 169 mOrientationListener.disable(); 170 171 try { 172 if (mReceiver != null) { 173 mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, 174 null /* credentialAttestation */); 175 mReceiver = null; 176 } 177 } catch (RemoteException e) { 178 Log.e(TAG, "Remote exception", e); 179 } 180 } 181 } 182 }; 183 handleTaskStackChanged()184 private void handleTaskStackChanged() { 185 mExecution.assertIsMainThread(); 186 if (mCurrentDialog != null) { 187 try { 188 final String clientPackage = mCurrentDialog.getOpPackageName(); 189 Log.w(TAG, "Task stack changed, current client: " + clientPackage); 190 final List<ActivityManager.RunningTaskInfo> runningTasks = 191 mActivityTaskManager.getTasks(1); 192 if (!runningTasks.isEmpty()) { 193 final String topPackage = runningTasks.get(0).topActivity.getPackageName(); 194 if (!topPackage.contentEquals(clientPackage) 195 && !Utils.isSystem(mContext, clientPackage)) { 196 Log.w(TAG, "Evicting client due to: " + topPackage); 197 mCurrentDialog.dismissWithoutCallback(true /* animate */); 198 mCurrentDialog = null; 199 mOrientationListener.disable(); 200 201 if (mReceiver != null) { 202 mReceiver.onDialogDismissed( 203 BiometricPrompt.DISMISSED_REASON_USER_CANCEL, 204 null /* credentialAttestation */); 205 mReceiver = null; 206 } 207 } 208 } 209 } catch (RemoteException e) { 210 Log.e(TAG, "Remote exception", e); 211 } 212 } 213 } 214 handleAllAuthenticatorsRegistered( List<FingerprintSensorPropertiesInternal> sensors)215 private void handleAllAuthenticatorsRegistered( 216 List<FingerprintSensorPropertiesInternal> sensors) { 217 mExecution.assertIsMainThread(); 218 if (DEBUG) { 219 Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString( 220 sensors.toArray())); 221 } 222 mFpProps = sensors; 223 List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>(); 224 List<FingerprintSensorPropertiesInternal> sidefpsProps = new ArrayList<>(); 225 for (FingerprintSensorPropertiesInternal props : mFpProps) { 226 if (props.isAnyUdfpsType()) { 227 udfpsProps.add(props); 228 } 229 if (props.isAnySidefpsType()) { 230 sidefpsProps.add(props); 231 } 232 } 233 mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; 234 if (mUdfpsProps != null) { 235 mUdfpsController = mUdfpsControllerFactory.get(); 236 } 237 mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; 238 if (mSidefpsProps != null) { 239 mSidefpsController = mSidefpsControllerFactory.get(); 240 } 241 for (Callback cb : mCallbacks) { 242 cb.onAllAuthenticatorsRegistered(); 243 } 244 mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener); 245 } 246 handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments)247 private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) { 248 mExecution.assertIsMainThread(); 249 Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId 250 + ", hasEnrollments: " + hasEnrollments); 251 if (mUdfpsProps == null) { 252 Log.d(TAG, "handleEnrollmentsChanged, mUdfpsProps is null"); 253 } else { 254 for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { 255 if (prop.sensorId == sensorId) { 256 mUdfpsEnrolledForUser.put(userId, hasEnrollments); 257 } 258 } 259 } 260 for (Callback cb : mCallbacks) { 261 cb.onEnrollmentsChanged(); 262 } 263 } 264 265 /** 266 * Adds a callback. See {@link Callback}. 267 */ addCallback(@onNull Callback callback)268 public void addCallback(@NonNull Callback callback) { 269 mCallbacks.add(callback); 270 } 271 272 /** 273 * Removes a callback. See {@link Callback}. 274 */ removeCallback(@onNull Callback callback)275 public void removeCallback(@NonNull Callback callback) { 276 mCallbacks.remove(callback); 277 } 278 279 @Override dozeTimeTick()280 public void dozeTimeTick() { 281 if (mUdfpsController != null) { 282 mUdfpsController.dozeTimeTick(); 283 } 284 } 285 286 @Override onTryAgainPressed()287 public void onTryAgainPressed() { 288 if (mReceiver == null) { 289 Log.e(TAG, "onTryAgainPressed: Receiver is null"); 290 return; 291 } 292 try { 293 mReceiver.onTryAgainPressed(); 294 } catch (RemoteException e) { 295 Log.e(TAG, "RemoteException when handling try again", e); 296 } 297 } 298 299 @Override onDeviceCredentialPressed()300 public void onDeviceCredentialPressed() { 301 if (mReceiver == null) { 302 Log.e(TAG, "onDeviceCredentialPressed: Receiver is null"); 303 return; 304 } 305 try { 306 mReceiver.onDeviceCredentialPressed(); 307 } catch (RemoteException e) { 308 Log.e(TAG, "RemoteException when handling credential button", e); 309 } 310 } 311 312 @Override onSystemEvent(int event)313 public void onSystemEvent(int event) { 314 if (mReceiver == null) { 315 Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null"); 316 return; 317 } 318 try { 319 mReceiver.onSystemEvent(event); 320 } catch (RemoteException e) { 321 Log.e(TAG, "RemoteException when sending system event", e); 322 } 323 } 324 325 @Override onDialogAnimatedIn()326 public void onDialogAnimatedIn() { 327 if (mReceiver == null) { 328 Log.e(TAG, "onDialogAnimatedIn: Receiver is null"); 329 return; 330 } 331 332 try { 333 mReceiver.onDialogAnimatedIn(); 334 } catch (RemoteException e) { 335 Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); 336 } 337 } 338 339 @Override onStartFingerprintNow()340 public void onStartFingerprintNow() { 341 if (mReceiver == null) { 342 Log.e(TAG, "onStartUdfpsNow: Receiver is null"); 343 return; 344 } 345 346 try { 347 mReceiver.onStartFingerprintNow(); 348 } catch (RemoteException e) { 349 Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); 350 } 351 } 352 353 @Override onDismissed(@ismissedReason int reason, @Nullable byte[] credentialAttestation)354 public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { 355 switch (reason) { 356 case AuthDialogCallback.DISMISSED_USER_CANCELED: 357 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL, 358 credentialAttestation); 359 break; 360 361 case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE: 362 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE, 363 credentialAttestation); 364 break; 365 366 case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE: 367 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, 368 credentialAttestation); 369 break; 370 371 case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED: 372 sendResultAndCleanUp( 373 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, 374 credentialAttestation); 375 break; 376 377 case AuthDialogCallback.DISMISSED_ERROR: 378 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR, 379 credentialAttestation); 380 break; 381 382 case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER: 383 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED, 384 credentialAttestation); 385 break; 386 387 case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED: 388 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED, 389 credentialAttestation); 390 break; 391 392 default: 393 Log.e(TAG, "Unhandled reason: " + reason); 394 break; 395 } 396 } 397 398 /** 399 * @return where the UDFPS exists on the screen in pixels in portrait mode. 400 */ getUdfpsSensorLocation()401 @Nullable public PointF getUdfpsSensorLocation() { 402 if (mUdfpsController == null) { 403 return null; 404 } 405 return new PointF(mUdfpsController.getSensorLocation().centerX(), 406 mUdfpsController.getSensorLocation().centerY()); 407 } 408 409 /** 410 * @return where the fingerprint sensor exists in pixels in portrait mode. devices without an 411 * overridden value will use the default value even if they don't have a fingerprint sensor 412 */ getFingerprintSensorLocation()413 @Nullable public PointF getFingerprintSensorLocation() { 414 if (getUdfpsSensorLocation() != null) { 415 return getUdfpsSensorLocation(); 416 } 417 return mFingerprintLocation; 418 } 419 420 /** 421 * @return where the face authentication sensor exists relative to the screen in pixels in 422 * portrait mode. 423 */ getFaceAuthSensorLocation()424 @Nullable public PointF getFaceAuthSensorLocation() { 425 if (mFaceProps == null || mFaceAuthSensorLocation == null) { 426 return null; 427 } 428 return new PointF(mFaceAuthSensorLocation.x, mFaceAuthSensorLocation.y); 429 } 430 431 /** 432 * Requests fingerprint scan. 433 * 434 * @param screenX X position of long press 435 * @param screenY Y position of long press 436 * @param major length of the major axis. See {@link MotionEvent#AXIS_TOOL_MAJOR}. 437 * @param minor length of the minor axis. See {@link MotionEvent#AXIS_TOOL_MINOR}. 438 */ onAodInterrupt(int screenX, int screenY, float major, float minor)439 public void onAodInterrupt(int screenX, int screenY, float major, float minor) { 440 if (mUdfpsController == null) { 441 return; 442 } 443 mUdfpsController.onAodInterrupt(screenX, screenY, major, minor); 444 } 445 446 /** 447 * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps 448 * sensor area even if the user hasn't explicitly lifted their finger yet. 449 */ onCancelUdfps()450 public void onCancelUdfps() { 451 if (mUdfpsController == null) { 452 return; 453 } 454 mUdfpsController.onCancelUdfps(); 455 } 456 sendResultAndCleanUp(@ismissedReason int reason, @Nullable byte[] credentialAttestation)457 private void sendResultAndCleanUp(@DismissedReason int reason, 458 @Nullable byte[] credentialAttestation) { 459 if (mReceiver == null) { 460 Log.e(TAG, "sendResultAndCleanUp: Receiver is null"); 461 return; 462 } 463 try { 464 mReceiver.onDialogDismissed(reason, credentialAttestation); 465 } catch (RemoteException e) { 466 Log.w(TAG, "Remote exception", e); 467 } 468 onDialogDismissed(reason); 469 } 470 471 @Inject AuthController(Context context, Execution execution, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, @NonNull WindowManager windowManager, @Nullable FingerprintManager fingerprintManager, @Nullable FaceManager faceManager, Provider<UdfpsController> udfpsControllerFactory, Provider<SidefpsController> sidefpsControllerFactory, @NonNull DisplayManager displayManager, @Main Handler handler)472 public AuthController(Context context, 473 Execution execution, 474 CommandQueue commandQueue, 475 ActivityTaskManager activityTaskManager, 476 @NonNull WindowManager windowManager, 477 @Nullable FingerprintManager fingerprintManager, 478 @Nullable FaceManager faceManager, 479 Provider<UdfpsController> udfpsControllerFactory, 480 Provider<SidefpsController> sidefpsControllerFactory, 481 @NonNull DisplayManager displayManager, 482 @Main Handler handler) { 483 super(context); 484 mExecution = execution; 485 mHandler = handler; 486 mCommandQueue = commandQueue; 487 mActivityTaskManager = activityTaskManager; 488 mFingerprintManager = fingerprintManager; 489 mFaceManager = faceManager; 490 mUdfpsControllerFactory = udfpsControllerFactory; 491 mSidefpsControllerFactory = sidefpsControllerFactory; 492 mWindowManager = windowManager; 493 mUdfpsEnrolledForUser = new SparseBooleanArray(); 494 mOrientationListener = new BiometricDisplayListener( 495 context, 496 displayManager, 497 mHandler, 498 BiometricDisplayListener.SensorType.Generic.INSTANCE, 499 () -> { 500 onOrientationChanged(); 501 return Unit.INSTANCE; 502 }); 503 504 mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; 505 506 int[] faceAuthLocation = context.getResources().getIntArray( 507 com.android.systemui.R.array.config_face_auth_props); 508 if (faceAuthLocation == null || faceAuthLocation.length < 2) { 509 mFaceAuthSensorLocation = null; 510 } else { 511 mFaceAuthSensorLocation = new PointF( 512 (float) faceAuthLocation[0], 513 (float) faceAuthLocation[1]); 514 } 515 516 updateFingerprintLocation(); 517 518 IntentFilter filter = new IntentFilter(); 519 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 520 521 context.registerReceiver(mBroadcastReceiver, filter); 522 mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); 523 } 524 updateFingerprintLocation()525 private void updateFingerprintLocation() { 526 int xLocation = DisplayUtils.getWidth(mContext) / 2; 527 try { 528 xLocation = mContext.getResources().getDimensionPixelSize( 529 com.android.systemui.R.dimen 530 .physical_fingerprint_sensor_center_screen_location_x); 531 } catch (Resources.NotFoundException e) { 532 } 533 int yLocation = mContext.getResources().getDimensionPixelSize( 534 com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y); 535 mFingerprintLocation = new PointF( 536 xLocation, 537 yLocation); 538 } 539 540 @SuppressWarnings("deprecation") 541 @Override start()542 public void start() { 543 mCommandQueue.addCallback(this); 544 545 if (mFingerprintManager != null) { 546 mFingerprintManager.addAuthenticatorsRegisteredCallback( 547 mFingerprintAuthenticatorsRegisteredCallback); 548 } 549 550 mTaskStackListener = new BiometricTaskStackListener(); 551 mActivityTaskManager.registerTaskStackListener(mTaskStackListener); 552 } 553 554 /** 555 * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}. 556 * 557 * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by 558 * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}. 559 */ 560 @Override setUdfpsHbmListener(IUdfpsHbmListener listener)561 public void setUdfpsHbmListener(IUdfpsHbmListener listener) { 562 mUdfpsHbmListener = listener; 563 } 564 565 /** 566 * @return IUdfpsHbmListener that can be set by DisplayModeDirector. 567 */ getUdfpsHbmListener()568 @Nullable public IUdfpsHbmListener getUdfpsHbmListener() { 569 return mUdfpsHbmListener; 570 } 571 572 @Override showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, long operationId, String opPackageName, long requestId, @BiometricMultiSensorMode int multiSensorConfig)573 public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, 574 int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, 575 int userId, long operationId, String opPackageName, long requestId, 576 @BiometricMultiSensorMode int multiSensorConfig) { 577 @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); 578 579 if (DEBUG) { 580 StringBuilder ids = new StringBuilder(); 581 for (int sensorId : sensorIds) { 582 ids.append(sensorId).append(" "); 583 } 584 Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators 585 + ", sensorIds: " + ids.toString() 586 + ", credentialAllowed: " + credentialAllowed 587 + ", requireConfirmation: " + requireConfirmation 588 + ", operationId: " + operationId 589 + ", requestId: " + requestId 590 + ", multiSensorConfig: " + multiSensorConfig); 591 } 592 SomeArgs args = SomeArgs.obtain(); 593 args.arg1 = promptInfo; 594 args.arg2 = receiver; 595 args.arg3 = sensorIds; 596 args.arg4 = credentialAllowed; 597 args.arg5 = requireConfirmation; 598 args.argi1 = userId; 599 args.arg6 = opPackageName; 600 args.arg7 = operationId; 601 args.arg8 = requestId; 602 args.argi2 = multiSensorConfig; 603 604 boolean skipAnimation = false; 605 if (mCurrentDialog != null) { 606 Log.w(TAG, "mCurrentDialog: " + mCurrentDialog); 607 skipAnimation = true; 608 } 609 610 showDialog(args, skipAnimation, null /* savedState */); 611 } 612 613 /** 614 * Only called via BiometricService for the biometric prompt. Will not be called for 615 * authentication directly requested through FingerprintManager. For 616 * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}. 617 */ 618 @Override onBiometricAuthenticated()619 public void onBiometricAuthenticated() { 620 if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: "); 621 622 if (mCurrentDialog != null) { 623 mCurrentDialog.onAuthenticationSucceeded(); 624 } else { 625 Log.w(TAG, "onBiometricAuthenticated callback but dialog gone"); 626 } 627 } 628 629 @Override onBiometricHelp(@odality int modality, String message)630 public void onBiometricHelp(@Modality int modality, String message) { 631 if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); 632 633 if (mCurrentDialog != null) { 634 mCurrentDialog.onHelp(modality, message); 635 } else { 636 Log.w(TAG, "onBiometricHelp callback but dialog gone"); 637 } 638 } 639 640 @Nullable getUdfpsProps()641 public List<FingerprintSensorPropertiesInternal> getUdfpsProps() { 642 return mUdfpsProps; 643 } 644 getErrorString(@odality int modality, int error, int vendorCode)645 private String getErrorString(@Modality int modality, int error, int vendorCode) { 646 switch (modality) { 647 case TYPE_FACE: 648 return FaceManager.getErrorString(mContext, error, vendorCode); 649 650 case TYPE_FINGERPRINT: 651 return FingerprintManager.getErrorString(mContext, error, vendorCode); 652 653 default: 654 return ""; 655 } 656 } 657 658 /** 659 * Only called via BiometricService for the biometric prompt. Will not be called for 660 * authentication directly requested through FingerprintManager. For 661 * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}. 662 */ 663 @Override onBiometricError(@odality int modality, int error, int vendorCode)664 public void onBiometricError(@Modality int modality, int error, int vendorCode) { 665 if (DEBUG) { 666 Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode)); 667 } 668 669 final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) 670 || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); 671 672 boolean isCameraPrivacyEnabled = false; 673 if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE 674 && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, 675 mCurrentDialogArgs.argi1 /* userId */)) { 676 isCameraPrivacyEnabled = true; 677 } 678 // TODO(b/141025588): Create separate methods for handling hard and soft errors. 679 final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED 680 || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT 681 || isCameraPrivacyEnabled); 682 if (mCurrentDialog != null) { 683 if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { 684 if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); 685 mCurrentDialog.animateToCredentialUI(); 686 } else if (isSoftError) { 687 final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED) 688 ? mContext.getString(R.string.biometric_not_recognized) 689 : getErrorString(modality, error, vendorCode); 690 if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); 691 // The camera privacy error can return before the prompt initializes its state, 692 // causing the prompt to appear to endlessly authenticate. Add a small delay 693 // to stop this. 694 if (isCameraPrivacyEnabled) { 695 mHandler.postDelayed(() -> { 696 mCurrentDialog.onAuthenticationFailed(modality, 697 mContext.getString(R.string.face_sensor_privacy_enabled)); 698 }, SENSOR_PRIVACY_DELAY); 699 } else { 700 mCurrentDialog.onAuthenticationFailed(modality, errorMessage); 701 } 702 } else { 703 final String errorMessage = getErrorString(modality, error, vendorCode); 704 if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); 705 mCurrentDialog.onError(modality, errorMessage); 706 } 707 708 } else { 709 Log.w(TAG, "onBiometricError callback but dialog is gone"); 710 } 711 712 onCancelUdfps(); 713 } 714 715 @Override hideAuthenticationDialog()716 public void hideAuthenticationDialog() { 717 if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog); 718 719 if (mCurrentDialog == null) { 720 // Could be possible if the caller canceled authentication after credential success 721 // but before the client was notified. 722 if (DEBUG) Log.d(TAG, "dialog already gone"); 723 return; 724 } 725 726 mCurrentDialog.dismissFromSystemServer(); 727 728 // BiometricService will have already sent the callback to the client in this case. 729 // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done. 730 mCurrentDialog = null; 731 mOrientationListener.disable(); 732 } 733 734 /** 735 * Whether the user's finger is currently on udfps attempting to authenticate. 736 */ isUdfpsFingerDown()737 public boolean isUdfpsFingerDown() { 738 if (mUdfpsController == null) { 739 return false; 740 } 741 742 return mUdfpsController.isFingerDown(); 743 } 744 745 /** 746 * Whether the passed userId has enrolled face auth. 747 */ isFaceAuthEnrolled(int userId)748 public boolean isFaceAuthEnrolled(int userId) { 749 if (mFaceProps == null) { 750 return false; 751 } 752 753 return mFaceManager.hasEnrolledTemplates(userId); 754 } 755 756 /** 757 * Whether the passed userId has enrolled UDFPS. 758 */ isUdfpsEnrolled(int userId)759 public boolean isUdfpsEnrolled(int userId) { 760 if (mUdfpsController == null) { 761 return false; 762 } 763 764 return mUdfpsEnrolledForUser.get(userId); 765 } 766 showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState)767 private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { 768 mCurrentDialogArgs = args; 769 770 final PromptInfo promptInfo = (PromptInfo) args.arg1; 771 final int[] sensorIds = (int[]) args.arg3; 772 final boolean credentialAllowed = (boolean) args.arg4; 773 final boolean requireConfirmation = (boolean) args.arg5; 774 final int userId = args.argi1; 775 final String opPackageName = (String) args.arg6; 776 final long operationId = (long) args.arg7; 777 final long requestId = (long) args.arg8; 778 final @BiometricMultiSensorMode int multiSensorConfig = args.argi2; 779 780 // Create a new dialog but do not replace the current one yet. 781 final AuthDialog newDialog = buildDialog( 782 promptInfo, 783 requireConfirmation, 784 userId, 785 sensorIds, 786 credentialAllowed, 787 opPackageName, 788 skipAnimation, 789 operationId, 790 requestId, 791 multiSensorConfig); 792 793 if (newDialog == null) { 794 Log.e(TAG, "Unsupported type configuration"); 795 return; 796 } 797 798 if (DEBUG) { 799 Log.d(TAG, "userId: " + userId 800 + " savedState: " + savedState 801 + " mCurrentDialog: " + mCurrentDialog 802 + " newDialog: " + newDialog); 803 } 804 805 if (mCurrentDialog != null) { 806 // If somehow we're asked to show a dialog, the old one doesn't need to be animated 807 // away. This can happen if the app cancels and re-starts auth during configuration 808 // change. This is ugly because we also have to do things on onConfigurationChanged 809 // here. 810 mCurrentDialog.dismissWithoutCallback(false /* animate */); 811 } 812 813 mReceiver = (IBiometricSysuiReceiver) args.arg2; 814 mCurrentDialog = newDialog; 815 mCurrentDialog.show(mWindowManager, savedState); 816 mOrientationListener.enable(); 817 } 818 onDialogDismissed(@ismissedReason int reason)819 private void onDialogDismissed(@DismissedReason int reason) { 820 if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason); 821 if (mCurrentDialog == null) { 822 Log.w(TAG, "Dialog already dismissed"); 823 } 824 mReceiver = null; 825 mCurrentDialog = null; 826 mOrientationListener.disable(); 827 } 828 829 @Override onConfigurationChanged(Configuration newConfig)830 protected void onConfigurationChanged(Configuration newConfig) { 831 super.onConfigurationChanged(newConfig); 832 updateFingerprintLocation(); 833 834 // Save the state of the current dialog (buttons showing, etc) 835 if (mCurrentDialog != null) { 836 final Bundle savedState = new Bundle(); 837 mCurrentDialog.onSaveState(savedState); 838 mCurrentDialog.dismissWithoutCallback(false /* animate */); 839 mCurrentDialog = null; 840 mOrientationListener.disable(); 841 842 // Only show the dialog if necessary. If it was animating out, the dialog is supposed 843 // to send its pending callback immediately. 844 if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE) 845 != AuthContainerView.STATE_ANIMATING_OUT) { 846 final boolean credentialShowing = 847 savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING); 848 if (credentialShowing) { 849 // There may be a cleaner way to do this, rather than altering the current 850 // authentication's parameters. This gets the job done and should be clear 851 // enough for now. 852 PromptInfo promptInfo = (PromptInfo) mCurrentDialogArgs.arg1; 853 promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL); 854 } 855 856 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); 857 } 858 } 859 } 860 onOrientationChanged()861 private void onOrientationChanged() { 862 updateFingerprintLocation(); 863 if (mCurrentDialog != null) { 864 mCurrentDialog.onOrientationChanged(); 865 } 866 } 867 buildDialog(PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig)868 protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation, 869 int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, 870 boolean skipIntro, long operationId, long requestId, 871 @BiometricMultiSensorMode int multiSensorConfig) { 872 return new AuthContainerView.Builder(mContext) 873 .setCallback(this) 874 .setPromptInfo(promptInfo) 875 .setRequireConfirmation(requireConfirmation) 876 .setUserId(userId) 877 .setOpPackageName(opPackageName) 878 .setSkipIntro(skipIntro) 879 .setOperationId(operationId) 880 .setRequestId(requestId) 881 .setMultiSensorConfig(multiSensorConfig) 882 .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); 883 } 884 885 /** 886 * AuthController callback used to receive signal for when biometric authenticators are 887 * registered. 888 */ 889 public interface Callback { 890 /** 891 * Called when authenticators are registered. If authenticators are already 892 * registered before this call, this callback will never be triggered. 893 */ onAllAuthenticatorsRegistered()894 void onAllAuthenticatorsRegistered(); 895 896 /** 897 * Called when UDFPS enrollments have changed. This is called after boot and on changes to 898 * enrollment. 899 */ onEnrollmentsChanged()900 void onEnrollmentsChanged(); 901 } 902 } 903