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 android.hardware.biometrics; 18 19 import static android.Manifest.permission.USE_BIOMETRIC; 20 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; 21 22 import android.annotation.CallbackExecutor; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.app.KeyguardManager; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.CancellationSignal; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.text.TextUtils; 36 import android.util.Log; 37 38 import com.android.internal.R; 39 40 import java.security.Signature; 41 import java.util.concurrent.Executor; 42 43 import javax.crypto.Cipher; 44 import javax.crypto.Mac; 45 46 /** 47 * A class that manages a system-provided biometric dialog. 48 */ 49 public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants { 50 51 private static final String TAG = "BiometricPrompt"; 52 53 /** 54 * @hide 55 */ 56 public static final String KEY_TITLE = "title"; 57 /** 58 * @hide 59 */ 60 public static final String KEY_USE_DEFAULT_TITLE = "use_default_title"; 61 /** 62 * @hide 63 */ 64 public static final String KEY_SUBTITLE = "subtitle"; 65 /** 66 * @hide 67 */ 68 public static final String KEY_DESCRIPTION = "description"; 69 /** 70 * @hide 71 */ 72 public static final String KEY_POSITIVE_TEXT = "positive_text"; 73 /** 74 * @hide 75 */ 76 public static final String KEY_NEGATIVE_TEXT = "negative_text"; 77 /** 78 * @hide 79 */ 80 public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; 81 /** 82 * @hide 83 */ 84 public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential"; 85 /** 86 * @hide 87 */ 88 public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL 89 = "from_confirm_device_credential"; 90 91 /** 92 * Error/help message will show for this amount of time. 93 * For error messages, the dialog will also be dismissed after this amount of time. 94 * Error messages will be propagated back to the application via AuthenticationCallback 95 * after this amount of time. 96 * @hide 97 */ 98 public static final int HIDE_DIALOG_DELAY = 2000; // ms 99 100 /** 101 * @hide 102 */ 103 public static final int DISMISSED_REASON_POSITIVE = 1; 104 105 /** 106 * @hide 107 */ 108 public static final int DISMISSED_REASON_NEGATIVE = 2; 109 110 /** 111 * @hide 112 */ 113 public static final int DISMISSED_REASON_USER_CANCEL = 3; 114 115 private static class ButtonInfo { 116 Executor executor; 117 DialogInterface.OnClickListener listener; ButtonInfo(Executor ex, DialogInterface.OnClickListener l)118 ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { 119 executor = ex; 120 listener = l; 121 } 122 } 123 124 /** 125 * A builder that collects arguments to be shown on the system-provided biometric dialog. 126 **/ 127 public static class Builder { 128 private final Bundle mBundle; 129 private ButtonInfo mPositiveButtonInfo; 130 private ButtonInfo mNegativeButtonInfo; 131 private Context mContext; 132 133 /** 134 * Creates a builder for a biometric dialog. 135 * @param context 136 */ Builder(Context context)137 public Builder(Context context) { 138 mBundle = new Bundle(); 139 mContext = context; 140 } 141 142 /** 143 * Required: Set the title to display. 144 * @param title 145 * @return 146 */ setTitle(@onNull CharSequence title)147 @NonNull public Builder setTitle(@NonNull CharSequence title) { 148 mBundle.putCharSequence(KEY_TITLE, title); 149 return this; 150 } 151 152 /** 153 * For internal use currently. Only takes effect if title is null/empty. Shows a default 154 * modality-specific title. 155 * @hide 156 */ 157 @RequiresPermission(USE_BIOMETRIC_INTERNAL) setUseDefaultTitle()158 @NonNull public Builder setUseDefaultTitle() { 159 mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true); 160 return this; 161 } 162 163 /** 164 * Optional: Set the subtitle to display. 165 * @param subtitle 166 * @return 167 */ setSubtitle(@onNull CharSequence subtitle)168 @NonNull public Builder setSubtitle(@NonNull CharSequence subtitle) { 169 mBundle.putCharSequence(KEY_SUBTITLE, subtitle); 170 return this; 171 } 172 173 /** 174 * Optional: Set the description to display. 175 * @param description 176 * @return 177 */ setDescription(@onNull CharSequence description)178 @NonNull public Builder setDescription(@NonNull CharSequence description) { 179 mBundle.putCharSequence(KEY_DESCRIPTION, description); 180 return this; 181 } 182 183 /** 184 * Optional: Set the text for the positive button. If not set, the positive button 185 * will not show. 186 * @param text 187 * @return 188 * @hide 189 */ setPositiveButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)190 @NonNull public Builder setPositiveButton(@NonNull CharSequence text, 191 @NonNull @CallbackExecutor Executor executor, 192 @NonNull DialogInterface.OnClickListener listener) { 193 if (TextUtils.isEmpty(text)) { 194 throw new IllegalArgumentException("Text must be set and non-empty"); 195 } 196 if (executor == null) { 197 throw new IllegalArgumentException("Executor must not be null"); 198 } 199 if (listener == null) { 200 throw new IllegalArgumentException("Listener must not be null"); 201 } 202 mBundle.putCharSequence(KEY_POSITIVE_TEXT, text); 203 mPositiveButtonInfo = new ButtonInfo(executor, listener); 204 return this; 205 } 206 207 /** 208 * Required: Set the text for the negative button. This would typically be used as a 209 * "Cancel" button, but may be also used to show an alternative method for authentication, 210 * such as screen that asks for a backup password. 211 * 212 * Note that this should not be set if {@link #setDeviceCredentialAllowed(boolean)}(boolean) 213 * is set to true. 214 * 215 * @param text 216 * @return 217 */ setNegativeButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)218 @NonNull public Builder setNegativeButton(@NonNull CharSequence text, 219 @NonNull @CallbackExecutor Executor executor, 220 @NonNull DialogInterface.OnClickListener listener) { 221 if (TextUtils.isEmpty(text)) { 222 throw new IllegalArgumentException("Text must be set and non-empty"); 223 } 224 if (executor == null) { 225 throw new IllegalArgumentException("Executor must not be null"); 226 } 227 if (listener == null) { 228 throw new IllegalArgumentException("Listener must not be null"); 229 } 230 mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text); 231 mNegativeButtonInfo = new ButtonInfo(executor, listener); 232 return this; 233 } 234 235 /** 236 * Optional: A hint to the system to require user confirmation after a biometric has been 237 * authenticated. For example, implicit modalities like Face and Iris authentication are 238 * passive, meaning they don't require an explicit user action to complete. When set to 239 * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt 240 * will require confirmation by default. 241 * 242 * A typical use case for not requiring confirmation would be for low-risk transactions, 243 * such as re-authenticating a recently authenticated application. A typical use case for 244 * requiring confirmation would be for authorizing a purchase. 245 * 246 * Note that this is a hint to the system. The system may choose to ignore the flag. For 247 * example, if the user disables implicit authentication in Settings, or if it does not 248 * apply to a modality (e.g. Fingerprint). When ignored, the system will default to 249 * requiring confirmation. 250 * 251 * @param requireConfirmation 252 */ setConfirmationRequired(boolean requireConfirmation)253 @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) { 254 mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation); 255 return this; 256 } 257 258 /** 259 * The user will first be prompted to authenticate with biometrics, but also given the 260 * option to authenticate with their device PIN, pattern, or password. Developers should 261 * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device 262 * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be 263 * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}. 264 * Defaults to false. 265 * 266 * Note that {@link #setNegativeButton(CharSequence, Executor, 267 * DialogInterface.OnClickListener)} should not be set if this is set to true. 268 * 269 * @param allowed When true, the prompt will fall back to ask for the user's device 270 * credentials (PIN, pattern, or password). 271 * @return 272 */ setDeviceCredentialAllowed(boolean allowed)273 @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) { 274 mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, allowed); 275 return this; 276 } 277 278 /** 279 * TODO(123378871): Remove when moved. 280 * @return 281 * @hide 282 */ 283 @RequiresPermission(USE_BIOMETRIC_INTERNAL) setFromConfirmDeviceCredential()284 @NonNull public Builder setFromConfirmDeviceCredential() { 285 mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true); 286 return this; 287 } 288 289 /** 290 * Creates a {@link BiometricPrompt}. 291 * @return a {@link BiometricPrompt} 292 * @throws IllegalArgumentException if any of the required fields are not set. 293 */ build()294 @NonNull public BiometricPrompt build() { 295 final CharSequence title = mBundle.getCharSequence(KEY_TITLE); 296 final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); 297 final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE); 298 final boolean enableFallback = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL); 299 300 if (TextUtils.isEmpty(title) && !useDefaultTitle) { 301 throw new IllegalArgumentException("Title must be set and non-empty"); 302 } else if (TextUtils.isEmpty(negative) && !enableFallback) { 303 throw new IllegalArgumentException("Negative text must be set and non-empty"); 304 } else if (!TextUtils.isEmpty(negative) && enableFallback) { 305 throw new IllegalArgumentException("Can't have both negative button behavior" 306 + " and device credential enabled"); 307 } 308 return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); 309 } 310 } 311 312 private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener { 313 @Override onCancel()314 public void onCancel() { 315 cancelAuthentication(); 316 } 317 } 318 319 private final IBinder mToken = new Binder(); 320 private final Context mContext; 321 private final IBiometricService mService; 322 private final Bundle mBundle; 323 private final ButtonInfo mPositiveButtonInfo; 324 private final ButtonInfo mNegativeButtonInfo; 325 326 private CryptoObject mCryptoObject; 327 private Executor mExecutor; 328 private AuthenticationCallback mAuthenticationCallback; 329 330 private final IBiometricServiceReceiver mBiometricServiceReceiver = 331 new IBiometricServiceReceiver.Stub() { 332 333 @Override 334 public void onAuthenticationSucceeded() throws RemoteException { 335 mExecutor.execute(() -> { 336 final AuthenticationResult result = new AuthenticationResult(mCryptoObject); 337 mAuthenticationCallback.onAuthenticationSucceeded(result); 338 }); 339 } 340 341 @Override 342 public void onAuthenticationFailed() throws RemoteException { 343 mExecutor.execute(() -> { 344 mAuthenticationCallback.onAuthenticationFailed(); 345 }); 346 } 347 348 @Override 349 public void onError(int error, String message) throws RemoteException { 350 mExecutor.execute(() -> { 351 mAuthenticationCallback.onAuthenticationError(error, message); 352 }); 353 } 354 355 @Override 356 public void onAcquired(int acquireInfo, String message) throws RemoteException { 357 mExecutor.execute(() -> { 358 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); 359 }); 360 } 361 362 @Override 363 public void onDialogDismissed(int reason) throws RemoteException { 364 // Check the reason and invoke OnClickListener(s) if necessary 365 if (reason == DISMISSED_REASON_POSITIVE) { 366 mPositiveButtonInfo.executor.execute(() -> { 367 mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); 368 }); 369 } else if (reason == DISMISSED_REASON_NEGATIVE) { 370 mNegativeButtonInfo.executor.execute(() -> { 371 mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); 372 }); 373 } 374 } 375 }; 376 BiometricPrompt(Context context, Bundle bundle, ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo)377 private BiometricPrompt(Context context, Bundle bundle, 378 ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { 379 mContext = context; 380 mBundle = bundle; 381 mPositiveButtonInfo = positiveButtonInfo; 382 mNegativeButtonInfo = negativeButtonInfo; 383 mService = IBiometricService.Stub.asInterface( 384 ServiceManager.getService(Context.BIOMETRIC_SERVICE)); 385 } 386 387 /** 388 * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework 389 * supports {@link Signature}, {@link Cipher} and {@link Mac} objects. 390 */ 391 public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { CryptoObject(@onNull Signature signature)392 public CryptoObject(@NonNull Signature signature) { 393 super(signature); 394 } 395 CryptoObject(@onNull Cipher cipher)396 public CryptoObject(@NonNull Cipher cipher) { 397 super(cipher); 398 } 399 CryptoObject(@onNull Mac mac)400 public CryptoObject(@NonNull Mac mac) { 401 super(mac); 402 } 403 404 /** 405 * Get {@link Signature} object. 406 * @return {@link Signature} object or null if this doesn't contain one. 407 */ getSignature()408 public Signature getSignature() { 409 return super.getSignature(); 410 } 411 412 /** 413 * Get {@link Cipher} object. 414 * @return {@link Cipher} object or null if this doesn't contain one. 415 */ getCipher()416 public Cipher getCipher() { 417 return super.getCipher(); 418 } 419 420 /** 421 * Get {@link Mac} object. 422 * @return {@link Mac} object or null if this doesn't contain one. 423 */ getMac()424 public Mac getMac() { 425 return super.getMac(); 426 } 427 } 428 429 /** 430 * Container for callback data from {@link #authenticate( CancellationSignal, Executor, 431 * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, 432 * AuthenticationCallback)} 433 */ 434 public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { 435 /** 436 * Authentication result 437 * @param crypto 438 * @hide 439 */ AuthenticationResult(CryptoObject crypto)440 public AuthenticationResult(CryptoObject crypto) { 441 // Identifier and userId is not used for BiometricPrompt. 442 super(crypto, null /* identifier */, 0 /* userId */); 443 } 444 /** 445 * Obtain the crypto object associated with this transaction 446 * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, 447 * Executor, AuthenticationCallback)} 448 */ getCryptoObject()449 public CryptoObject getCryptoObject() { 450 return (CryptoObject) super.getCryptoObject(); 451 } 452 } 453 454 /** 455 * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal, 456 * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject, 457 * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation 458 * of this for listening to authentication events. 459 */ 460 public abstract static class AuthenticationCallback extends 461 BiometricAuthenticator.AuthenticationCallback { 462 /** 463 * Called when an unrecoverable error has been encountered and the operation is complete. 464 * No further actions will be made on this object. 465 * @param errorCode An integer identifying the error message 466 * @param errString A human-readable error string that can be shown on an UI 467 */ 468 @Override onAuthenticationError(int errorCode, CharSequence errString)469 public void onAuthenticationError(int errorCode, CharSequence errString) {} 470 471 /** 472 * Called when a recoverable error has been encountered during authentication. The help 473 * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, 474 * please clean it." 475 * @param helpCode An integer identifying the error message 476 * @param helpString A human-readable string that can be shown on an UI 477 */ 478 @Override onAuthenticationHelp(int helpCode, CharSequence helpString)479 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} 480 481 /** 482 * Called when a biometric is recognized. 483 * @param result An object containing authentication-related data 484 */ onAuthenticationSucceeded(AuthenticationResult result)485 public void onAuthenticationSucceeded(AuthenticationResult result) {} 486 487 /** 488 * Called when a biometric is valid but not recognized. 489 */ 490 @Override onAuthenticationFailed()491 public void onAuthenticationFailed() {} 492 493 /** 494 * Called when a biometric has been acquired, but hasn't been processed yet. 495 * @hide 496 */ 497 @Override onAuthenticationAcquired(int acquireInfo)498 public void onAuthenticationAcquired(int acquireInfo) {} 499 } 500 501 /** 502 * Authenticates for the given user. 503 * @param cancel An object that can be used to cancel authentication 504 * @param executor An executor to handle callback events 505 * @param callback An object to receive authentication events 506 * @param userId The user to authenticate 507 * @hide 508 */ 509 @RequiresPermission(USE_BIOMETRIC_INTERNAL) authenticateUser(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId, IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback)510 public void authenticateUser(@NonNull CancellationSignal cancel, 511 @NonNull @CallbackExecutor Executor executor, 512 @NonNull AuthenticationCallback callback, 513 int userId, 514 IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { 515 if (cancel == null) { 516 throw new IllegalArgumentException("Must supply a cancellation signal"); 517 } 518 if (executor == null) { 519 throw new IllegalArgumentException("Must supply an executor"); 520 } 521 if (callback == null) { 522 throw new IllegalArgumentException("Must supply a callback"); 523 } 524 authenticateInternal(null /* crypto */, cancel, executor, callback, userId, 525 confirmDeviceCredentialCallback); 526 } 527 528 /** 529 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 530 * scanning for a biometric. It terminates when {@link 531 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 532 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user 533 * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This 534 * operation can be canceled by using the provided cancel object. The application will receive 535 * authentication errors through {@link AuthenticationCallback}, and button events through the 536 * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor, 537 * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object, 538 * and calling {@link BiometricPrompt#authenticate( CancellationSignal, Executor, 539 * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the 540 * previous client and start a new authentication. The interrupted client will receive a 541 * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, 542 * CharSequence)}. 543 * 544 * Note: Applications generally should not cancel and start authentication in quick succession. 545 * For example, to properly handle authentication across configuration changes, it's recommended 546 * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the 547 * application will not need to cancel/restart authentication during the configuration change. 548 * 549 * @throws IllegalArgumentException If any of the arguments are null 550 * 551 * @param crypto Object associated with the call 552 * @param cancel An object that can be used to cancel authentication 553 * @param executor An executor to handle callback events 554 * @param callback An object to receive authentication events 555 */ 556 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)557 public void authenticate(@NonNull CryptoObject crypto, 558 @NonNull CancellationSignal cancel, 559 @NonNull @CallbackExecutor Executor executor, 560 @NonNull AuthenticationCallback callback) { 561 if (crypto == null) { 562 throw new IllegalArgumentException("Must supply a crypto object"); 563 } 564 if (cancel == null) { 565 throw new IllegalArgumentException("Must supply a cancellation signal"); 566 } 567 if (executor == null) { 568 throw new IllegalArgumentException("Must supply an executor"); 569 } 570 if (callback == null) { 571 throw new IllegalArgumentException("Must supply a callback"); 572 } 573 if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) { 574 throw new IllegalArgumentException("Device credential not supported with crypto"); 575 } 576 authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(), 577 null /* confirmDeviceCredentialCallback */); 578 } 579 580 /** 581 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 582 * scanning for a biometric. It terminates when {@link 583 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 584 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when 585 * the user dismisses the system-provided dialog. This operation can be canceled by using the 586 * provided cancel object. The application will receive authentication errors through {@link 587 * AuthenticationCallback}, and button events through the corresponding callback set in {@link 588 * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is 589 * safe to reuse the {@link BiometricPrompt} object, and calling {@link 590 * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while 591 * an existing authentication attempt is occurring will stop the previous client and start a new 592 * authentication. The interrupted client will receive a cancelled notification through {@link 593 * AuthenticationCallback#onAuthenticationError(int, CharSequence)}. 594 * 595 * Note: Applications generally should not cancel and start authentication in quick succession. 596 * For example, to properly handle authentication across configuration changes, it's recommended 597 * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the 598 * application will not need to cancel/restart authentication during the configuration change. 599 * 600 * @throws IllegalArgumentException If any of the arguments are null 601 * 602 * @param cancel An object that can be used to cancel authentication 603 * @param executor An executor to handle callback events 604 * @param callback An object to receive authentication events 605 */ 606 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)607 public void authenticate(@NonNull CancellationSignal cancel, 608 @NonNull @CallbackExecutor Executor executor, 609 @NonNull AuthenticationCallback callback) { 610 if (cancel == null) { 611 throw new IllegalArgumentException("Must supply a cancellation signal"); 612 } 613 if (executor == null) { 614 throw new IllegalArgumentException("Must supply an executor"); 615 } 616 if (callback == null) { 617 throw new IllegalArgumentException("Must supply a callback"); 618 } 619 authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(), 620 null /* confirmDeviceCredentialCallback */); 621 } 622 cancelAuthentication()623 private void cancelAuthentication() { 624 if (mService != null) { 625 try { 626 mService.cancelAuthentication(mToken, mContext.getOpPackageName()); 627 } catch (RemoteException e) { 628 Log.e(TAG, "Unable to cancel authentication", e); 629 } 630 } 631 } 632 authenticateInternal(@ullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId, IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback)633 private void authenticateInternal(@Nullable CryptoObject crypto, 634 @NonNull CancellationSignal cancel, 635 @NonNull @CallbackExecutor Executor executor, 636 @NonNull AuthenticationCallback callback, 637 int userId, 638 IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { 639 try { 640 if (cancel.isCanceled()) { 641 Log.w(TAG, "Authentication already canceled"); 642 return; 643 } else { 644 cancel.setOnCancelListener(new OnAuthenticationCancelListener()); 645 } 646 647 mCryptoObject = crypto; 648 mExecutor = executor; 649 mAuthenticationCallback = callback; 650 final long sessionId = crypto != null ? crypto.getOpId() : 0; 651 if (BiometricManager.hasBiometrics(mContext)) { 652 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, 653 mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback); 654 } else { 655 mExecutor.execute(() -> { 656 callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, 657 mContext.getString(R.string.biometric_error_hw_unavailable)); 658 }); 659 } 660 } catch (RemoteException e) { 661 Log.e(TAG, "Remote exception while authenticating", e); 662 mExecutor.execute(() -> { 663 callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, 664 mContext.getString(R.string.biometric_error_hw_unavailable)); 665 }); 666 } 667 } 668 } 669