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