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