• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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