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