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.TEST_BIOMETRIC; 20 import static android.Manifest.permission.USE_BIOMETRIC; 21 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; 22 import static android.hardware.biometrics.BiometricManager.Authenticators; 23 24 import android.annotation.CallbackExecutor; 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.TestApi; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.hardware.face.FaceManager; 33 import android.hardware.fingerprint.FingerprintManager; 34 import android.os.Binder; 35 import android.os.CancellationSignal; 36 import android.os.IBinder; 37 import android.os.Parcel; 38 import android.os.RemoteException; 39 import android.os.ServiceManager; 40 import android.security.identity.IdentityCredential; 41 import android.security.identity.PresentationSession; 42 import android.security.keystore.KeyProperties; 43 import android.text.TextUtils; 44 import android.util.Log; 45 46 import com.android.internal.R; 47 import com.android.internal.util.FrameworkStatsLog; 48 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.security.Signature; 52 import java.util.List; 53 import java.util.concurrent.Executor; 54 55 import javax.crypto.Cipher; 56 import javax.crypto.Mac; 57 58 /** 59 * A class that manages a system-provided biometric dialog. 60 */ 61 public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants { 62 63 private static final String TAG = "BiometricPrompt"; 64 65 /** 66 * Error/help message will show for this amount of time. 67 * For error messages, the dialog will also be dismissed after this amount of time. 68 * Error messages will be propagated back to the application via AuthenticationCallback 69 * after this amount of time. 70 * @hide 71 */ 72 public static final int HIDE_DIALOG_DELAY = 2000; // ms 73 74 /** 75 * @hide 76 */ 77 public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1; 78 79 /** 80 * Dialog is done animating away after user clicked on the button set via 81 * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor, 82 * DialogInterface.OnClickListener)}. 83 * @hide 84 */ 85 public static final int DISMISSED_REASON_NEGATIVE = 2; 86 87 /** 88 * @hide 89 */ 90 public static final int DISMISSED_REASON_USER_CANCEL = 3; 91 92 /** 93 * Authenticated, confirmation not required. Dialog animated away. 94 * @hide 95 */ 96 public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4; 97 98 /** 99 * Error message shown on SystemUI. When BiometricService receives this, the UI is already 100 * gone. 101 * @hide 102 */ 103 public static final int DISMISSED_REASON_ERROR = 5; 104 105 /** 106 * Dialog dismissal requested by BiometricService. 107 * @hide 108 */ 109 public static final int DISMISSED_REASON_SERVER_REQUESTED = 6; 110 111 /** 112 * @hide 113 */ 114 public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7; 115 116 /** 117 * @hide 118 */ 119 @IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED, 120 DISMISSED_REASON_NEGATIVE, 121 DISMISSED_REASON_USER_CANCEL, 122 DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, 123 DISMISSED_REASON_ERROR, 124 DISMISSED_REASON_SERVER_REQUESTED, 125 DISMISSED_REASON_CREDENTIAL_CONFIRMED}) 126 @Retention(RetentionPolicy.SOURCE) 127 public @interface DismissedReason {} 128 129 private static class ButtonInfo { 130 Executor executor; 131 DialogInterface.OnClickListener listener; ButtonInfo(Executor ex, DialogInterface.OnClickListener l)132 ButtonInfo(Executor ex, DialogInterface.OnClickListener l) { 133 executor = ex; 134 listener = l; 135 } 136 } 137 138 /** 139 * A builder that collects arguments to be shown on the system-provided biometric dialog. 140 */ 141 public static class Builder { 142 private PromptInfo mPromptInfo; 143 private ButtonInfo mNegativeButtonInfo; 144 private Context mContext; 145 146 /** 147 * Creates a builder for a {@link BiometricPrompt} dialog. 148 * @param context The {@link Context} that will be used to build the prompt. 149 */ Builder(Context context)150 public Builder(Context context) { 151 mPromptInfo = new PromptInfo(); 152 mContext = context; 153 } 154 155 /** 156 * Required: Sets the title that will be shown on the prompt. 157 * @param title The title to display. 158 * @return This builder. 159 */ 160 @NonNull setTitle(@onNull CharSequence title)161 public Builder setTitle(@NonNull CharSequence title) { 162 mPromptInfo.setTitle(title); 163 return this; 164 } 165 166 /** 167 * Shows a default, modality-specific title for the prompt if the title would otherwise be 168 * null or empty. Currently for internal use only. 169 * @return This builder. 170 * @hide 171 */ 172 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 173 @NonNull setUseDefaultTitle()174 public Builder setUseDefaultTitle() { 175 mPromptInfo.setUseDefaultTitle(true); 176 return this; 177 } 178 179 /** 180 * Optional: Sets a subtitle that will be shown on the prompt. 181 * @param subtitle The subtitle to display. 182 * @return This builder. 183 */ 184 @NonNull setSubtitle(@onNull CharSequence subtitle)185 public Builder setSubtitle(@NonNull CharSequence subtitle) { 186 mPromptInfo.setSubtitle(subtitle); 187 return this; 188 } 189 190 /** 191 * Optional: Sets a description that will be shown on the prompt. 192 * @param description The description to display. 193 * @return This builder. 194 */ 195 @NonNull setDescription(@onNull CharSequence description)196 public Builder setDescription(@NonNull CharSequence description) { 197 mPromptInfo.setDescription(description); 198 return this; 199 } 200 201 /** 202 * Sets an optional title, subtitle, and/or description that will override other text when 203 * the user is authenticating with PIN/pattern/password. Currently for internal use only. 204 * @return This builder. 205 * @hide 206 */ 207 @RequiresPermission(USE_BIOMETRIC_INTERNAL) 208 @NonNull setTextForDeviceCredential( @ullable CharSequence title, @Nullable CharSequence subtitle, @Nullable CharSequence description)209 public Builder setTextForDeviceCredential( 210 @Nullable CharSequence title, 211 @Nullable CharSequence subtitle, 212 @Nullable CharSequence description) { 213 if (title != null) { 214 mPromptInfo.setDeviceCredentialTitle(title); 215 } 216 if (subtitle != null) { 217 mPromptInfo.setDeviceCredentialSubtitle(subtitle); 218 } 219 if (description != null) { 220 mPromptInfo.setDeviceCredentialDescription(description); 221 } 222 return this; 223 } 224 225 /** 226 * Required: Sets the text, executor, and click listener for the negative button on the 227 * prompt. This is typically a cancel button, but may be also used to show an alternative 228 * method for authentication, such as a screen that asks for a backup password. 229 * 230 * <p>Note that this setting is not required, and in fact is explicitly disallowed, if 231 * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or 232 * {@link #setDeviceCredentialAllowed(boolean)}. 233 * 234 * @param text Text to be shown on the negative button for the prompt. 235 * @param executor Executor that will be used to run the on click callback. 236 * @param listener Listener containing a callback to be run when the button is pressed. 237 * @return This builder. 238 */ 239 @NonNull setNegativeButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)240 public Builder setNegativeButton(@NonNull CharSequence text, 241 @NonNull @CallbackExecutor Executor executor, 242 @NonNull DialogInterface.OnClickListener listener) { 243 if (TextUtils.isEmpty(text)) { 244 throw new IllegalArgumentException("Text must be set and non-empty"); 245 } 246 if (executor == null) { 247 throw new IllegalArgumentException("Executor must not be null"); 248 } 249 if (listener == null) { 250 throw new IllegalArgumentException("Listener must not be null"); 251 } 252 mPromptInfo.setNegativeButtonText(text); 253 mNegativeButtonInfo = new ButtonInfo(executor, listener); 254 return this; 255 } 256 257 /** 258 * Optional: Sets a hint to the system for whether to require user confirmation after 259 * authentication. For example, implicit modalities like face and iris are passive, meaning 260 * they don't require an explicit user action to complete authentication. If set to true, 261 * these modalities should require the user to take some action (e.g. press a button) 262 * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is 263 * called. Defaults to true. 264 * 265 * <p>A typical use case for not requiring confirmation would be for low-risk transactions, 266 * such as re-authenticating a recently authenticated application. A typical use case for 267 * requiring confirmation would be for authorizing a purchase. 268 * 269 * <p>Note that this just passes a hint to the system, which the system may then ignore. For 270 * example, a value of false may be ignored if the user has disabled implicit authentication 271 * in Settings, or if it does not apply to a particular modality (e.g. fingerprint). 272 * 273 * @param requireConfirmation true if explicit user confirmation should be required, or 274 * false otherwise. 275 * @return This builder. 276 */ 277 @NonNull setConfirmationRequired(boolean requireConfirmation)278 public Builder setConfirmationRequired(boolean requireConfirmation) { 279 mPromptInfo.setConfirmationRequested(requireConfirmation); 280 return this; 281 } 282 283 /** 284 * Optional: If enabled, the user will be given the option to authenticate with their device 285 * PIN, pattern, or password. Developers should first check {@link 286 * BiometricManager#canAuthenticate(int)} for {@link Authenticators#DEVICE_CREDENTIAL} 287 * before enabling. If the device is not secured with a credential, 288 * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} will be invoked 289 * with {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}. Defaults to false. 290 * 291 * <p>Note that enabling this option replaces the negative button on the prompt with one 292 * that allows the user to authenticate with their device credential, making it an error to 293 * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 294 * 295 * @param allowed true if the prompt should fall back to asking for the user's device 296 * credential (PIN/pattern/password), or false otherwise. 297 * @return This builder. 298 * 299 * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}. 300 */ 301 @Deprecated 302 @NonNull setDeviceCredentialAllowed(boolean allowed)303 public Builder setDeviceCredentialAllowed(boolean allowed) { 304 mPromptInfo.setDeviceCredentialAllowed(allowed); 305 return this; 306 } 307 308 /** 309 * Optional: Specifies the type(s) of authenticators that may be invoked by 310 * {@link BiometricPrompt} to authenticate the user. Available authenticator types are 311 * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to: 312 * <ul> 313 * <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li> 314 * <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li> 315 * </ul> 316 * 317 * <p>If this method is used and no authenticator of any of the specified types is available 318 * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will 319 * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} 320 * will be invoked with an appropriate error code. 321 * 322 * <p>This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and 323 * overrides the latter if both are used. Using this method to enable device credential 324 * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative 325 * button on the prompt, making it an error to also call 326 * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 327 * 328 * <p>If unlocking cryptographic operation(s), it is the application's responsibility to 329 * request authentication with the proper set of authenticators (e.g. match the 330 * authenticators specified during key generation). 331 * 332 * @see android.security.keystore.KeyGenParameterSpec.Builder 333 * @see KeyProperties#AUTH_BIOMETRIC_STRONG 334 * @see KeyProperties#AUTH_DEVICE_CREDENTIAL 335 * 336 * @param authenticators A bit field representing all valid authenticator types that may be 337 * invoked by the prompt. 338 * @return This builder. 339 */ 340 @NonNull setAllowedAuthenticators(@uthenticators.Types int authenticators)341 public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) { 342 mPromptInfo.setAuthenticators(authenticators); 343 return this; 344 } 345 346 /** 347 * If non-empty, requests authentication to be performed only if the sensor is contained 348 * within the list. Note that the actual sensor presented to the user/test will meet all 349 * constraints specified within this builder. For example, on a device with the below 350 * configuration: 351 * 352 * SensorId: 1, Strength: BIOMETRIC_STRONG 353 * SensorId: 2, Strength: BIOMETRIC_WEAK 354 * 355 * If authentication is invoked with setAllowedAuthenticators(BIOMETRIC_STRONG) and 356 * setAllowedSensorIds(2), then no sensor will be eligible for authentication. 357 * 358 * @see {@link BiometricManager#getSensorProperties()} 359 * 360 * @param sensorIds Sensor IDs to constrain this authentication to. 361 * @return This builder 362 * @hide 363 */ 364 @TestApi 365 @NonNull 366 @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) setAllowedSensorIds(@onNull List<Integer> sensorIds)367 public Builder setAllowedSensorIds(@NonNull List<Integer> sensorIds) { 368 mPromptInfo.setAllowedSensorIds(sensorIds); 369 return this; 370 } 371 372 /** 373 * @param allow If true, allows authentication when the calling package is not in the 374 * foreground. This is set to false by default. 375 * @return This builder 376 * @hide 377 */ 378 @TestApi 379 @NonNull 380 @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) setAllowBackgroundAuthentication(boolean allow)381 public Builder setAllowBackgroundAuthentication(boolean allow) { 382 mPromptInfo.setAllowBackgroundAuthentication(allow); 383 return this; 384 } 385 386 /** 387 * If set check the Device Policy Manager for disabled biometrics. 388 * 389 * @param checkDevicePolicyManager 390 * @return This builder. 391 * @hide 392 */ 393 @NonNull setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager)394 public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) { 395 mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager); 396 return this; 397 } 398 399 /** 400 * If set, receive internal events via {@link AuthenticationCallback#onSystemEvent(int)} 401 * @param set 402 * @return This builder. 403 * @hide 404 */ 405 @NonNull setReceiveSystemEvents(boolean set)406 public Builder setReceiveSystemEvents(boolean set) { 407 mPromptInfo.setReceiveSystemEvents(set); 408 return this; 409 } 410 411 /** 412 * Flag to decide if authentication should ignore enrollment state. 413 * Defaults to false (not ignoring enrollment state) 414 * @param ignoreEnrollmentState 415 * @return This builder. 416 * @hide 417 */ 418 @NonNull setIgnoreEnrollmentState(boolean ignoreEnrollmentState)419 public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { 420 mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState); 421 return this; 422 } 423 424 /** 425 * Set if BiometricPrompt is being used by the legacy fingerprint manager API. 426 * @param sensorId sensor id 427 * @return This builder. 428 * @hide 429 */ 430 @NonNull setIsForLegacyFingerprintManager(int sensorId)431 public Builder setIsForLegacyFingerprintManager(int sensorId) { 432 mPromptInfo.setIsForLegacyFingerprintManager(sensorId); 433 return this; 434 } 435 436 /** 437 * Creates a {@link BiometricPrompt}. 438 * 439 * @return An instance of {@link BiometricPrompt}. 440 * 441 * @throws IllegalArgumentException If any required fields are unset, or if given any 442 * invalid combination of field values. 443 */ 444 @NonNull build()445 public BiometricPrompt build() { 446 final CharSequence title = mPromptInfo.getTitle(); 447 final CharSequence negative = mPromptInfo.getNegativeButtonText(); 448 final boolean useDefaultTitle = mPromptInfo.isUseDefaultTitle(); 449 final boolean deviceCredentialAllowed = mPromptInfo.isDeviceCredentialAllowed(); 450 final @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators(); 451 final boolean willShowDeviceCredentialButton = deviceCredentialAllowed 452 || isCredentialAllowed(authenticators); 453 454 if (TextUtils.isEmpty(title) && !useDefaultTitle) { 455 throw new IllegalArgumentException("Title must be set and non-empty"); 456 } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) { 457 throw new IllegalArgumentException("Negative text must be set and non-empty"); 458 } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) { 459 throw new IllegalArgumentException("Can't have both negative button behavior" 460 + " and device credential enabled"); 461 } 462 return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo); 463 } 464 } 465 466 private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener { 467 private final long mAuthRequestId; 468 OnAuthenticationCancelListener(long id)469 OnAuthenticationCancelListener(long id) { 470 mAuthRequestId = id; 471 } 472 473 @Override onCancel()474 public void onCancel() { 475 Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId); 476 cancelAuthentication(mAuthRequestId); 477 } 478 } 479 480 private final IBinder mToken = new Binder(); 481 private final Context mContext; 482 private final IAuthService mService; 483 private final PromptInfo mPromptInfo; 484 private final ButtonInfo mNegativeButtonInfo; 485 486 private CryptoObject mCryptoObject; 487 private Executor mExecutor; 488 private AuthenticationCallback mAuthenticationCallback; 489 490 private final IBiometricServiceReceiver mBiometricServiceReceiver = 491 new IBiometricServiceReceiver.Stub() { 492 493 @Override 494 public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) { 495 mExecutor.execute(() -> { 496 final AuthenticationResult result = 497 new AuthenticationResult(mCryptoObject, authenticationType); 498 mAuthenticationCallback.onAuthenticationSucceeded(result); 499 }); 500 } 501 502 @Override 503 public void onAuthenticationFailed() { 504 mExecutor.execute(() -> { 505 mAuthenticationCallback.onAuthenticationFailed(); 506 }); 507 } 508 509 @Override 510 public void onError(@BiometricAuthenticator.Modality int modality, int error, 511 int vendorCode) { 512 513 String errorMessage = null; 514 switch (modality) { 515 case TYPE_FACE: 516 errorMessage = FaceManager.getErrorString(mContext, error, vendorCode); 517 break; 518 519 case TYPE_FINGERPRINT: 520 errorMessage = FingerprintManager.getErrorString(mContext, error, vendorCode); 521 break; 522 } 523 524 // Look for generic errors, as it may be a combination of modalities, or no modality 525 // (e.g. attempted biometric authentication without biometric sensors). 526 if (errorMessage == null) { 527 switch (error) { 528 case BIOMETRIC_ERROR_CANCELED: 529 errorMessage = mContext.getString(R.string.biometric_error_canceled); 530 break; 531 case BIOMETRIC_ERROR_USER_CANCELED: 532 errorMessage = mContext.getString(R.string.biometric_error_user_canceled); 533 break; 534 case BIOMETRIC_ERROR_HW_NOT_PRESENT: 535 errorMessage = mContext.getString(R.string.biometric_error_hw_unavailable); 536 break; 537 case BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL: 538 errorMessage = mContext.getString( 539 R.string.biometric_error_device_not_secured); 540 break; 541 default: 542 Log.e(TAG, "Unknown error, modality: " + modality 543 + " error: " + error 544 + " vendorCode: " + vendorCode); 545 errorMessage = mContext.getString(R.string.biometric_error_generic); 546 break; 547 } 548 } 549 550 final String stringToSend = errorMessage; 551 mExecutor.execute(() -> { 552 mAuthenticationCallback.onAuthenticationError(error, stringToSend); 553 }); 554 } 555 556 @Override 557 public void onAcquired(int acquireInfo, String message) { 558 mExecutor.execute(() -> { 559 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); 560 }); 561 } 562 563 @Override 564 public void onDialogDismissed(int reason) { 565 // Check the reason and invoke OnClickListener(s) if necessary 566 if (reason == DISMISSED_REASON_NEGATIVE) { 567 mNegativeButtonInfo.executor.execute(() -> { 568 mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); 569 }); 570 } else { 571 Log.e(TAG, "Unknown reason: " + reason); 572 } 573 } 574 575 @Override 576 public void onSystemEvent(int event) { 577 mExecutor.execute(() -> { 578 mAuthenticationCallback.onSystemEvent(event); 579 }); 580 } 581 }; 582 BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo)583 private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) { 584 mContext = context; 585 mPromptInfo = promptInfo; 586 mNegativeButtonInfo = negativeButtonInfo; 587 mService = IAuthService.Stub.asInterface( 588 ServiceManager.getService(Context.AUTH_SERVICE)); 589 } 590 591 /** 592 * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. 593 * @return The title of the prompt, which is guaranteed to be non-null. 594 */ 595 @NonNull getTitle()596 public CharSequence getTitle() { 597 return mPromptInfo.getTitle(); 598 } 599 600 /** 601 * Whether to use a default modality-specific title. For internal use only. 602 * @return See {@link Builder#setUseDefaultTitle()}. 603 * @hide 604 */ 605 @RequiresPermission(USE_BIOMETRIC_INTERNAL) shouldUseDefaultTitle()606 public boolean shouldUseDefaultTitle() { 607 return mPromptInfo.isUseDefaultTitle(); 608 } 609 610 /** 611 * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}. 612 * @return The subtitle for the prompt, or null if the prompt has no subtitle. 613 */ 614 @Nullable getSubtitle()615 public CharSequence getSubtitle() { 616 return mPromptInfo.getSubtitle(); 617 } 618 619 /** 620 * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}. 621 * @return The description for the prompt, or null if the prompt has no description. 622 */ 623 @Nullable getDescription()624 public CharSequence getDescription() { 625 return mPromptInfo.getDescription(); 626 } 627 628 /** 629 * Gets the negative button text for the prompt, as set by 630 * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. 631 * @return The negative button text for the prompt, or null if no negative button text was set. 632 */ 633 @Nullable getNegativeButtonText()634 public CharSequence getNegativeButtonText() { 635 return mPromptInfo.getNegativeButtonText(); 636 } 637 638 /** 639 * Determines if explicit user confirmation is required by the prompt, as set by 640 * {@link Builder#setConfirmationRequired(boolean)}. 641 * 642 * @return true if explicit user confirmation is required, or false otherwise. 643 */ isConfirmationRequired()644 public boolean isConfirmationRequired() { 645 return mPromptInfo.isConfirmationRequested(); 646 } 647 648 /** 649 * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the 650 * user, as set by {@link Builder#setAllowedAuthenticators(int)}. 651 * 652 * @return A bit field representing the type(s) of authenticators that may be invoked by the 653 * prompt (as defined by {@link Authenticators}), or 0 if this field was not set. 654 */ 655 @Nullable getAllowedAuthenticators()656 public int getAllowedAuthenticators() { 657 return mPromptInfo.getAuthenticators(); 658 } 659 660 /** 661 * @return The values set by {@link Builder#setAllowedSensorIds(List)} 662 * @hide 663 */ 664 @TestApi 665 @NonNull getAllowedSensorIds()666 public List<Integer> getAllowedSensorIds() { 667 return mPromptInfo.getAllowedSensorIds(); 668 } 669 670 /** 671 * @return The value set by {@link Builder#setAllowBackgroundAuthentication(boolean)} 672 * @hide 673 */ 674 @TestApi isAllowBackgroundAuthentication()675 public boolean isAllowBackgroundAuthentication() { 676 return mPromptInfo.isAllowBackgroundAuthentication(); 677 } 678 679 /** 680 * A wrapper class for the cryptographic operations supported by BiometricPrompt. 681 * 682 * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, 683 * {@link IdentityCredential}, and {@link PresentationSession}. 684 * 685 * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and 686 * time-based. This is specified during key creation via the timeout parameter of the 687 * {@code setUserAuthenticationParameters(int, int)} method of {@link 688 * android.security.keystore.KeyGenParameterSpec.Builder}. 689 * 690 * <p>CryptoObjects are used to unlock auth-per-use keys via 691 * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor, 692 * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration 693 * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard). 694 * If a time-based key is not available for use (i.e. none of the allowed authenticators have 695 * been unlocked recently), applications can prompt the user to authenticate via 696 * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} 697 * 698 * @see Builder#setAllowedAuthenticators(int) 699 */ 700 public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { CryptoObject(@onNull Signature signature)701 public CryptoObject(@NonNull Signature signature) { 702 super(signature); 703 } 704 CryptoObject(@onNull Cipher cipher)705 public CryptoObject(@NonNull Cipher cipher) { 706 super(cipher); 707 } 708 CryptoObject(@onNull Mac mac)709 public CryptoObject(@NonNull Mac mac) { 710 super(mac); 711 } 712 713 /** 714 * Create from a {@link IdentityCredential} object. 715 * 716 * @param credential a {@link IdentityCredential} object. 717 * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. 718 */ 719 @Deprecated CryptoObject(@onNull IdentityCredential credential)720 public CryptoObject(@NonNull IdentityCredential credential) { 721 super(credential); 722 } 723 CryptoObject(@onNull PresentationSession session)724 public CryptoObject(@NonNull PresentationSession session) { 725 super(session); 726 } 727 728 /** 729 * Get {@link Signature} object. 730 * @return {@link Signature} object or null if this doesn't contain one. 731 */ getSignature()732 public Signature getSignature() { 733 return super.getSignature(); 734 } 735 736 /** 737 * Get {@link Cipher} object. 738 * @return {@link Cipher} object or null if this doesn't contain one. 739 */ getCipher()740 public Cipher getCipher() { 741 return super.getCipher(); 742 } 743 744 /** 745 * Get {@link Mac} object. 746 * @return {@link Mac} object or null if this doesn't contain one. 747 */ getMac()748 public Mac getMac() { 749 return super.getMac(); 750 } 751 752 /** 753 * Get {@link IdentityCredential} object. 754 * @return {@link IdentityCredential} object or null if this doesn't contain one. 755 * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. 756 */ 757 @Deprecated getIdentityCredential()758 public @Nullable IdentityCredential getIdentityCredential() { 759 return super.getIdentityCredential(); 760 } 761 762 /** 763 * Get {@link PresentationSession} object. 764 * @return {@link PresentationSession} object or null if this doesn't contain one. 765 */ getPresentationSession()766 public @Nullable PresentationSession getPresentationSession() { 767 return super.getPresentationSession(); 768 } 769 } 770 771 /** 772 * Authentication type reported by {@link AuthenticationResult} when the user authenticated by 773 * entering their device PIN, pattern, or password. 774 */ 775 public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; 776 777 /** 778 * Authentication type reported by {@link AuthenticationResult} when the user authenticated by 779 * presenting some form of biometric (e.g. fingerprint or face). 780 */ 781 public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; 782 783 /** 784 * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. 785 * @hide 786 */ 787 @Retention(RetentionPolicy.SOURCE) 788 @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) 789 public @interface AuthenticationResultType { 790 } 791 792 /** 793 * Container for callback data from {@link #authenticate(CancellationSignal, Executor, 794 * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, 795 * AuthenticationCallback)}. 796 */ 797 public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { 798 /** 799 * Authentication result 800 * @param crypto 801 * @param authenticationType 802 * @hide 803 */ AuthenticationResult(CryptoObject crypto, @AuthenticationResultType int authenticationType)804 public AuthenticationResult(CryptoObject crypto, 805 @AuthenticationResultType int authenticationType) { 806 // Identifier and userId is not used for BiometricPrompt. 807 super(crypto, authenticationType, null /* identifier */, 0 /* userId */); 808 } 809 810 /** 811 * Provides the crypto object associated with this transaction. 812 * @return The crypto object provided to {@link #authenticate(CryptoObject, 813 * CancellationSignal, Executor, AuthenticationCallback)} 814 */ getCryptoObject()815 public CryptoObject getCryptoObject() { 816 return (CryptoObject) super.getCryptoObject(); 817 } 818 819 /** 820 * Provides the type of authentication (e.g. device credential or biometric) that was 821 * requested from and successfully provided by the user. 822 * 823 * @return An integer value representing the authentication method used. 824 */ getAuthenticationType()825 public @AuthenticationResultType int getAuthenticationType() { 826 return super.getAuthenticationType(); 827 } 828 } 829 830 /** 831 * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal, 832 * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject, 833 * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation 834 * of this for listening to authentication events. 835 */ 836 public abstract static class AuthenticationCallback extends 837 BiometricAuthenticator.AuthenticationCallback { 838 /** 839 * Called when an unrecoverable error has been encountered and the operation is complete. 840 * No further actions will be made on this object. 841 * @param errorCode An integer identifying the error message 842 * @param errString A human-readable error string that can be shown on an UI 843 */ 844 @Override onAuthenticationError(int errorCode, CharSequence errString)845 public void onAuthenticationError(int errorCode, CharSequence errString) {} 846 847 /** 848 * Called when a recoverable error has been encountered during authentication. The help 849 * string is provided to give the user guidance for what went wrong, such as "Sensor dirty, 850 * please clean it." 851 * @param helpCode An integer identifying the error message 852 * @param helpString A human-readable string that can be shown on an UI 853 */ 854 @Override onAuthenticationHelp(int helpCode, CharSequence helpString)855 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} 856 857 /** 858 * Called when a biometric is recognized. 859 * @param result An object containing authentication-related data 860 */ onAuthenticationSucceeded(AuthenticationResult result)861 public void onAuthenticationSucceeded(AuthenticationResult result) {} 862 863 /** 864 * Called when a biometric is valid but not recognized. 865 */ 866 @Override onAuthenticationFailed()867 public void onAuthenticationFailed() {} 868 869 /** 870 * Called when a biometric has been acquired, but hasn't been processed yet. 871 * @hide 872 */ 873 @Override onAuthenticationAcquired(int acquireInfo)874 public void onAuthenticationAcquired(int acquireInfo) {} 875 876 /** 877 * Receiver for internal system events. See {@link Builder#setReceiveSystemEvents(boolean)} 878 * @hide 879 */ onSystemEvent(int event)880 public void onSystemEvent(int event) {} 881 } 882 883 /** 884 * Authenticates for the given user. 885 * 886 * @param cancel An object that can be used to cancel authentication 887 * @param executor An executor to handle callback events 888 * @param callback An object to receive authentication events 889 * @param userId The user to authenticate 890 * 891 * @hide 892 */ 893 @RequiresPermission(USE_BIOMETRIC_INTERNAL) authenticateUser(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)894 public void authenticateUser(@NonNull CancellationSignal cancel, 895 @NonNull @CallbackExecutor Executor executor, 896 @NonNull AuthenticationCallback callback, 897 int userId) { 898 if (cancel == null) { 899 throw new IllegalArgumentException("Must supply a cancellation signal"); 900 } 901 if (executor == null) { 902 throw new IllegalArgumentException("Must supply an executor"); 903 } 904 if (callback == null) { 905 throw new IllegalArgumentException("Must supply a callback"); 906 } 907 908 authenticateInternal(0 /* operationId */, cancel, executor, callback, userId); 909 } 910 911 /** 912 * Authenticates for the given keystore operation. 913 * 914 * @param cancel An object that can be used to cancel authentication 915 * @param executor An executor to handle callback events 916 * @param callback An object to receive authentication events 917 * @param operationId The keystore operation associated with authentication 918 * 919 * @return A requestId that can be used to cancel this operation. 920 * 921 * @hide 922 */ 923 @RequiresPermission(USE_BIOMETRIC) authenticateForOperation( @onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, long operationId)924 public long authenticateForOperation( 925 @NonNull CancellationSignal cancel, 926 @NonNull @CallbackExecutor Executor executor, 927 @NonNull AuthenticationCallback callback, 928 long operationId) { 929 if (cancel == null) { 930 throw new IllegalArgumentException("Must supply a cancellation signal"); 931 } 932 if (executor == null) { 933 throw new IllegalArgumentException("Must supply an executor"); 934 } 935 if (callback == null) { 936 throw new IllegalArgumentException("Must supply a callback"); 937 } 938 939 return authenticateInternal(operationId, cancel, executor, callback, mContext.getUserId()); 940 } 941 942 /** 943 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 944 * scanning for a biometric. It terminates when {@link 945 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 946 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user 947 * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This 948 * operation can be canceled by using the provided cancel object. The application will receive 949 * authentication errors through {@link AuthenticationCallback}, and button events through the 950 * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor, 951 * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object, 952 * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor, 953 * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the 954 * previous client and start a new authentication. The interrupted client will receive a 955 * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, 956 * CharSequence)}. 957 * 958 * <p>Note: Applications generally should not cancel and start authentication in quick 959 * succession. For example, to properly handle authentication across configuration changes, it's 960 * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, 961 * the application will not need to cancel/restart authentication during the configuration 962 * change. 963 * 964 * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements 965 * for <strong>Strong</strong> are permitted to integrate with Keystore to perform related 966 * cryptographic operations. Therefore, it is an error to call this method after explicitly 967 * calling {@link Builder#setAllowedAuthenticators(int)} with any biometric strength other than 968 * {@link Authenticators#BIOMETRIC_STRONG}. 969 * 970 * @throws IllegalArgumentException If any argument is null, or if the allowed biometric 971 * authenticator strength is explicitly set to {@link Authenticators#BIOMETRIC_WEAK}. Prior to 972 * {@link android.os.Build.VERSION_CODES#R}, this exception is also thrown if 973 * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true. 974 * 975 * @param crypto A cryptographic operation to be unlocked after successful authentication. 976 * @param cancel An object that can be used to cancel authentication. 977 * @param executor An executor to handle callback events. 978 * @param callback An object to receive authentication events. 979 */ 980 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)981 public void authenticate(@NonNull CryptoObject crypto, 982 @NonNull CancellationSignal cancel, 983 @NonNull @CallbackExecutor Executor executor, 984 @NonNull AuthenticationCallback callback) { 985 986 FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED, 987 true /* isCrypto */, 988 mPromptInfo.isConfirmationRequested(), 989 mPromptInfo.isDeviceCredentialAllowed(), 990 mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET, 991 mPromptInfo.getAuthenticators()); 992 993 if (crypto == null) { 994 throw new IllegalArgumentException("Must supply a crypto object"); 995 } 996 if (cancel == null) { 997 throw new IllegalArgumentException("Must supply a cancellation signal"); 998 } 999 if (executor == null) { 1000 throw new IllegalArgumentException("Must supply an executor"); 1001 } 1002 if (callback == null) { 1003 throw new IllegalArgumentException("Must supply a callback"); 1004 } 1005 1006 // Disallow explicitly setting any non-Strong biometric authenticator types. 1007 @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators(); 1008 if (authenticators == Authenticators.EMPTY_SET) { 1009 authenticators = Authenticators.BIOMETRIC_STRONG; 1010 } 1011 final int biometricStrength = authenticators & Authenticators.BIOMETRIC_WEAK; 1012 if ((biometricStrength & ~Authenticators.BIOMETRIC_STRONG) != 0) { 1013 throw new IllegalArgumentException("Only Strong biometrics supported with crypto"); 1014 } 1015 1016 authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); 1017 } 1018 1019 /** 1020 * This call warms up the biometric hardware, displays a system-provided dialog, and starts 1021 * scanning for a biometric. It terminates when {@link 1022 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link 1023 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when 1024 * the user dismisses the system-provided dialog. This operation can be canceled by using the 1025 * provided cancel object. The application will receive authentication errors through {@link 1026 * AuthenticationCallback}, and button events through the corresponding callback set in {@link 1027 * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is 1028 * safe to reuse the {@link BiometricPrompt} object, and calling {@link 1029 * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while 1030 * an existing authentication attempt is occurring will stop the previous client and start a new 1031 * authentication. The interrupted client will receive a cancelled notification through {@link 1032 * AuthenticationCallback#onAuthenticationError(int, CharSequence)}. 1033 * 1034 * <p>Note: Applications generally should not cancel and start authentication in quick 1035 * succession. For example, to properly handle authentication across configuration changes, it's 1036 * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, 1037 * the application will not need to cancel/restart authentication during the configuration 1038 * change. 1039 * 1040 * @throws IllegalArgumentException If any of the arguments are null. 1041 * 1042 * @param cancel An object that can be used to cancel authentication. 1043 * @param executor An executor to handle callback events. 1044 * @param callback An object to receive authentication events. 1045 */ 1046 @RequiresPermission(USE_BIOMETRIC) authenticate(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)1047 public void authenticate(@NonNull CancellationSignal cancel, 1048 @NonNull @CallbackExecutor Executor executor, 1049 @NonNull AuthenticationCallback callback) { 1050 1051 FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED, 1052 false /* isCrypto */, 1053 mPromptInfo.isConfirmationRequested(), 1054 mPromptInfo.isDeviceCredentialAllowed(), 1055 mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET, 1056 mPromptInfo.getAuthenticators()); 1057 1058 if (cancel == null) { 1059 throw new IllegalArgumentException("Must supply a cancellation signal"); 1060 } 1061 if (executor == null) { 1062 throw new IllegalArgumentException("Must supply an executor"); 1063 } 1064 if (callback == null) { 1065 throw new IllegalArgumentException("Must supply a callback"); 1066 } 1067 authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); 1068 } 1069 cancelAuthentication(long requestId)1070 private void cancelAuthentication(long requestId) { 1071 if (mService != null) { 1072 try { 1073 mService.cancelAuthentication(mToken, mContext.getPackageName(), requestId); 1074 } catch (RemoteException e) { 1075 Log.e(TAG, "Unable to cancel authentication", e); 1076 } 1077 } 1078 } 1079 authenticateInternal( @ullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)1080 private void authenticateInternal( 1081 @Nullable CryptoObject crypto, 1082 @NonNull CancellationSignal cancel, 1083 @NonNull @CallbackExecutor Executor executor, 1084 @NonNull AuthenticationCallback callback, 1085 int userId) { 1086 1087 mCryptoObject = crypto; 1088 final long operationId = crypto != null ? crypto.getOpId() : 0L; 1089 authenticateInternal(operationId, cancel, executor, callback, userId); 1090 } 1091 authenticateInternal( long operationId, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)1092 private long authenticateInternal( 1093 long operationId, 1094 @NonNull CancellationSignal cancel, 1095 @NonNull @CallbackExecutor Executor executor, 1096 @NonNull AuthenticationCallback callback, 1097 int userId) { 1098 1099 // Ensure we don't return the wrong crypto object as an auth result. 1100 if (mCryptoObject != null && mCryptoObject.getOpId() != operationId) { 1101 Log.w(TAG, "CryptoObject operation ID does not match argument; setting field to null"); 1102 mCryptoObject = null; 1103 } 1104 1105 try { 1106 if (cancel.isCanceled()) { 1107 Log.w(TAG, "Authentication already canceled"); 1108 return -1; 1109 } 1110 1111 mExecutor = executor; 1112 mAuthenticationCallback = callback; 1113 1114 final PromptInfo promptInfo; 1115 if (operationId != 0L) { 1116 // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth. 1117 // Note that we use a new PromptInfo here so as to not overwrite the application's 1118 // preference, since it is possible that the same prompt configuration be used 1119 // without a crypto object later. 1120 Parcel parcel = Parcel.obtain(); 1121 mPromptInfo.writeToParcel(parcel, 0 /* flags */); 1122 parcel.setDataPosition(0); 1123 promptInfo = new PromptInfo(parcel); 1124 if (promptInfo.getAuthenticators() == Authenticators.EMPTY_SET) { 1125 promptInfo.setAuthenticators(Authenticators.BIOMETRIC_STRONG); 1126 } 1127 } else { 1128 promptInfo = mPromptInfo; 1129 } 1130 1131 final long authId = mService.authenticate(mToken, operationId, userId, 1132 mBiometricServiceReceiver, mContext.getPackageName(), promptInfo); 1133 cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); 1134 return authId; 1135 } catch (RemoteException e) { 1136 Log.e(TAG, "Remote exception while authenticating", e); 1137 mExecutor.execute(() -> callback.onAuthenticationError( 1138 BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, 1139 mContext.getString(R.string.biometric_error_hw_unavailable))); 1140 return -1; 1141 } 1142 } 1143 isCredentialAllowed(@uthenticators.Types int allowedAuthenticators)1144 private static boolean isCredentialAllowed(@Authenticators.Types int allowedAuthenticators) { 1145 return (allowedAuthenticators & Authenticators.DEVICE_CREDENTIAL) != 0; 1146 } 1147 } 1148