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