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.SET_BIOMETRIC_DIALOG_ADVANCED;
20 import static android.Manifest.permission.TEST_BIOMETRIC;
21 import static android.Manifest.permission.USE_BIOMETRIC;
22 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
23 import static android.hardware.biometrics.BiometricManager.Authenticators;
24 import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
25 import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
26 import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
27 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
28 
29 import android.annotation.CallbackExecutor;
30 import android.annotation.DrawableRes;
31 import android.annotation.FlaggedApi;
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.annotation.RequiresPermission;
36 import android.annotation.TestApi;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.DialogInterface;
40 import android.graphics.Bitmap;
41 import android.graphics.Canvas;
42 import android.graphics.drawable.BitmapDrawable;
43 import android.graphics.drawable.Drawable;
44 import android.hardware.face.FaceManager;
45 import android.hardware.fingerprint.FingerprintManager;
46 import android.os.Binder;
47 import android.os.CancellationSignal;
48 import android.os.IBinder;
49 import android.os.Parcel;
50 import android.os.RemoteException;
51 import android.os.ServiceManager;
52 import android.security.identity.IdentityCredential;
53 import android.security.identity.PresentationSession;
54 import android.security.keystore.KeyProperties;
55 import android.text.TextUtils;
56 import android.util.Log;
57 
58 import com.android.internal.R;
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.util.FrameworkStatsLog;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.security.Signature;
65 import java.util.List;
66 import java.util.concurrent.Executor;
67 
68 import javax.crypto.Cipher;
69 import javax.crypto.KeyAgreement;
70 import javax.crypto.Mac;
71 
72 /**
73  * A class that manages a system-provided biometric dialog.
74  */
75 public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
76 
77     private static final String TAG = "BiometricPrompt";
78     @VisibleForTesting
79     static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
80 
81     /**
82      * Error/help message will show for this amount of time.
83      * For error messages, the dialog will also be dismissed after this amount of time.
84      * Error messages will be propagated back to the application via AuthenticationCallback
85      * after this amount of time.
86      * @hide
87      */
88     public static final int HIDE_DIALOG_DELAY = 2000; // ms
89 
90     /**
91      * @hide
92      */
93     public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1;
94 
95     /**
96      * Dialog is done animating away after user clicked on the button set via
97      * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor,
98      * DialogInterface.OnClickListener)}.
99      * @hide
100      */
101     public static final int DISMISSED_REASON_NEGATIVE = 2;
102 
103     /**
104      * @hide
105      */
106     public static final int DISMISSED_REASON_USER_CANCEL = 3;
107 
108     /**
109      * Authenticated, confirmation not required. Dialog animated away.
110      * @hide
111      */
112     public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4;
113 
114     /**
115      * Error message shown on SystemUI. When BiometricService receives this, the UI is already
116      * gone.
117      * @hide
118      */
119     public static final int DISMISSED_REASON_ERROR = 5;
120 
121     /**
122      * Dialog dismissal requested by BiometricService.
123      * @hide
124      */
125     public static final int DISMISSED_REASON_SERVER_REQUESTED = 6;
126 
127     /**
128      * @hide
129      */
130     public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7;
131 
132     /**
133      * Dialog is done animating away after user clicked on the button set via
134      * {@link PromptContentViewWithMoreOptionsButton.Builder#setMoreOptionsButtonListener(Executor,
135      * DialogInterface.OnClickListener)} )}.
136      *
137      * @hide
138      */
139     public static final int DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS = 8;
140 
141     /**
142      * @hide
143      */
144     @IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED,
145             DISMISSED_REASON_NEGATIVE,
146             DISMISSED_REASON_USER_CANCEL,
147             DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
148             DISMISSED_REASON_ERROR,
149             DISMISSED_REASON_SERVER_REQUESTED,
150             DISMISSED_REASON_CREDENTIAL_CONFIRMED,
151             DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS})
152     @Retention(RetentionPolicy.SOURCE)
153     public @interface DismissedReason {}
154 
155     static class ButtonInfo {
156         Executor executor;
157         DialogInterface.OnClickListener listener;
ButtonInfo(Executor ex, DialogInterface.OnClickListener l)158         ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
159             executor = ex;
160             listener = l;
161         }
162     }
163 
164     /**
165      * A builder that collects arguments to be shown on the system-provided biometric dialog.
166      */
167     public static class Builder {
168         private PromptInfo mPromptInfo;
169         private ButtonInfo mNegativeButtonInfo;
170         private ButtonInfo mContentViewMoreOptionsButtonInfo;
171         private Context mContext;
172         private IAuthService mService;
173 
174         // LINT.IfChange
175         /**
176          * Creates a builder for a {@link BiometricPrompt} dialog.
177          * @param context The {@link Context} that will be used to build the prompt.
178          */
Builder(Context context)179         public Builder(Context context) {
180             mPromptInfo = new PromptInfo();
181             mContext = context;
182         }
183 
184         /**
185          * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
186          *
187          * <p> Note that using this method is not recommended in most scenarios because the calling
188          * application's icon will be used by default. Setting the logo is intended for large
189          * bundled applications that perform a wide range of functions and need to show distinct
190          * icons for each function.
191          *
192          * @param logoRes A drawable resource of the logo that will be shown on the prompt.
193          * @return This builder.
194          */
195         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
196         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
197         @NonNull
setLogoRes(@rawableRes int logoRes)198         public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
199             if (mPromptInfo.getLogoBitmap() != null) {
200                 throw new IllegalStateException(
201                         "Exclusively one of logo resource or logo bitmap can be set");
202             }
203             if (logoRes != 0) {
204                 mPromptInfo.setLogo(logoRes,
205                         convertDrawableToBitmap(mContext.getDrawable(logoRes)));
206             }
207             return this;
208         }
209 
210         /**
211          * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
212          *
213          * <p> Note that using this method is not recommended in most scenarios because the calling
214          * application's icon will be used by default. Setting the logo is intended for large
215          * bundled applications that perform a wide range of functions and need to show distinct
216          * icons for each function.
217          *
218          * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
219          * @return This builder.
220          */
221         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
222         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
223         @NonNull
setLogoBitmap(@onNull Bitmap logoBitmap)224         public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
225             if (mPromptInfo.getLogoRes() != 0) {
226                 throw new IllegalStateException(
227                         "Exclusively one of logo resource or logo bitmap can be set");
228             }
229             mPromptInfo.setLogo(0, logoBitmap);
230             return this;
231         }
232 
233         /**
234          * Optional: Sets logo description text that will be shown on the prompt.
235          *
236          * <p> Note that using this method is not recommended in most scenarios because the calling
237          * application's name will be used by default. Setting the logo description is intended for
238          * large bundled applications that perform a wide range of functions and need to show
239          * distinct description for each function.
240          *
241          * @param logoDescription The logo description text that will be shown on the prompt.
242          * @return This builder.
243          * @throws IllegalArgumentException If logo description is null.
244          */
245         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
246         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
247         @NonNull
setLogoDescription(@onNull String logoDescription)248         public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
249             if (logoDescription == null || logoDescription.isEmpty()) {
250                 throw new IllegalArgumentException("Logo description passed in can not be null");
251             }
252             if (logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
253                 Log.w(TAG,
254                         "Logo description passed in exceeds" + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER
255                                 + " character number and may be truncated.");
256             }
257             mPromptInfo.setLogoDescription(logoDescription);
258             return this;
259         }
260 
261         /**
262          * Required: Sets the title that will be shown on the prompt.
263          * @param title The title to display.
264          * @return This builder.
265          */
266         @NonNull
setTitle(@onNull CharSequence title)267         public Builder setTitle(@NonNull CharSequence title) {
268             mPromptInfo.setTitle(title);
269             return this;
270         }
271 
272         /**
273          * Shows a default, modality-specific title for the prompt if the title would otherwise be
274          * null or empty. Currently for internal use only.
275          * @return This builder.
276          * @hide
277          */
278         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
279         @NonNull
setUseDefaultTitle()280         public Builder setUseDefaultTitle() {
281             mPromptInfo.setUseDefaultTitle(true);
282             return this;
283         }
284 
285         /**
286          * Optional: Sets a subtitle that will be shown on the prompt.
287          * @param subtitle The subtitle to display.
288          * @return This builder.
289          */
290         @NonNull
setSubtitle(@onNull CharSequence subtitle)291         public Builder setSubtitle(@NonNull CharSequence subtitle) {
292             mPromptInfo.setSubtitle(subtitle);
293             return this;
294         }
295 
296         /**
297          * Shows a default subtitle for the prompt if the subtitle would otherwise be
298          * null or empty. Currently for internal use only.
299          * @return This builder.
300          * @hide
301          */
302         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
303         @NonNull
setUseDefaultSubtitle()304         public Builder setUseDefaultSubtitle() {
305             mPromptInfo.setUseDefaultSubtitle(true);
306             return this;
307         }
308 
309         /**
310          * Optional: Sets a description that will be shown on the prompt.
311          *
312          * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
313          * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
314          * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
315          * called.
316          *
317          * @param description The description to display.
318          * @return This builder.
319          */
320         @NonNull
setDescription(@onNull CharSequence description)321         public Builder setDescription(@NonNull CharSequence description) {
322             mPromptInfo.setDescription(description);
323             return this;
324         }
325 
326         /**
327          * Optional: Sets application customized content view that will be shown on the prompt.
328          *
329          * <p> Note that the description set by {@link Builder#setDescription(CharSequence)} will be
330          * overridden by {@link Builder#setContentView(PromptContentView)}. The view provided to
331          * {@link Builder#setContentView(PromptContentView)} will be used if both methods are
332          * called.
333          *
334          * @param view The customized view information.
335          * @return This builder.
336          */
337         @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
338         @NonNull
setContentView(@onNull PromptContentView view)339         public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
340             mPromptInfo.setContentView(view);
341 
342             if (mPromptInfo.isContentViewMoreOptionsButtonUsed()) {
343                 mContentViewMoreOptionsButtonInfo =
344                         ((PromptContentViewWithMoreOptionsButton) view).getButtonInfo();
345             }
346 
347             return this;
348         }
349 
350         /**
351          * @param service
352          * @return This builder.
353          * @hide
354          */
355         @RequiresPermission(TEST_BIOMETRIC)
356         @NonNull
setService(@onNull IAuthService service)357         public Builder setService(@NonNull IAuthService service) {
358             mService = service;
359             return this;
360         }
361 
362         /**
363          * Sets an optional title, subtitle, and/or description that will override other text when
364          * the user is authenticating with PIN/pattern/password. Currently for internal use only.
365          * @return This builder.
366          * @hide
367          */
368         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
369         @NonNull
setTextForDeviceCredential( @ullable CharSequence title, @Nullable CharSequence subtitle, @Nullable CharSequence description)370         public Builder setTextForDeviceCredential(
371                 @Nullable CharSequence title,
372                 @Nullable CharSequence subtitle,
373                 @Nullable CharSequence description) {
374             if (title != null) {
375                 mPromptInfo.setDeviceCredentialTitle(title);
376             }
377             if (subtitle != null) {
378                 mPromptInfo.setDeviceCredentialSubtitle(subtitle);
379             }
380             if (description != null) {
381                 mPromptInfo.setDeviceCredentialDescription(description);
382             }
383             return this;
384         }
385 
386         /**
387          * Required: Sets the text, executor, and click listener for the negative button on the
388          * prompt. This is typically a cancel button, but may be also used to show an alternative
389          * method for authentication, such as a screen that asks for a backup password.
390          *
391          * <p>Note that this setting is not required, and in fact is explicitly disallowed, if
392          * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or
393          * {@link #setDeviceCredentialAllowed(boolean)}.
394          *
395          * @param text Text to be shown on the negative button for the prompt.
396          * @param executor Executor that will be used to run the on click callback.
397          * @param listener Listener containing a callback to be run when the button is pressed.
398          * @return This builder.
399          */
400         @NonNull
setNegativeButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)401         public Builder setNegativeButton(@NonNull CharSequence text,
402                 @NonNull @CallbackExecutor Executor executor,
403                 @NonNull DialogInterface.OnClickListener listener) {
404             if (TextUtils.isEmpty(text)) {
405                 throw new IllegalArgumentException("Text must be set and non-empty");
406             }
407             if (executor == null) {
408                 throw new IllegalArgumentException("Executor must not be null");
409             }
410             if (listener == null) {
411                 throw new IllegalArgumentException("Listener must not be null");
412             }
413             mPromptInfo.setNegativeButtonText(text);
414             mNegativeButtonInfo = new ButtonInfo(executor, listener);
415             return this;
416         }
417 
418         /**
419          * Optional: Sets a hint to the system for whether to require user confirmation after
420          * authentication. For example, implicit modalities like face and iris are passive, meaning
421          * they don't require an explicit user action to complete authentication. If set to true,
422          * these modalities should require the user to take some action (e.g. press a button)
423          * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is
424          * called. Defaults to true.
425          *
426          * <p>A typical use case for not requiring confirmation would be for low-risk transactions,
427          * such as re-authenticating a recently authenticated application. A typical use case for
428          * requiring confirmation would be for authorizing a purchase.
429          *
430          * <p>Note that this just passes a hint to the system, which the system may then ignore. For
431          * example, a value of false may be ignored if the user has disabled implicit authentication
432          * in Settings, or if it does not apply to a particular modality (e.g. fingerprint).
433          *
434          * @param requireConfirmation true if explicit user confirmation should be required, or
435          *                            false otherwise.
436          * @return This builder.
437          */
438         @NonNull
setConfirmationRequired(boolean requireConfirmation)439         public Builder setConfirmationRequired(boolean requireConfirmation) {
440             mPromptInfo.setConfirmationRequested(requireConfirmation);
441             return this;
442         }
443 
444         /**
445          * Optional: If enabled, the user will be given the option to authenticate with their device
446          * PIN, pattern, or password. Developers should first check {@link
447          * BiometricManager#canAuthenticate(int)} for {@link Authenticators#DEVICE_CREDENTIAL}
448          * before enabling. If the device is not secured with a credential,
449          * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} will be invoked
450          * with {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}. Defaults to false.
451          *
452          * <p>Note that enabling this option replaces the negative button on the prompt with one
453          * that allows the user to authenticate with their device credential, making it an error to
454          * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
455          *
456          * @param allowed true if the prompt should fall back to asking for the user's device
457          *                credential (PIN/pattern/password), or false otherwise.
458          * @return This builder.
459          *
460          * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}.
461          */
462         @Deprecated
463         @NonNull
setDeviceCredentialAllowed(boolean allowed)464         public Builder setDeviceCredentialAllowed(boolean allowed) {
465             mPromptInfo.setDeviceCredentialAllowed(allowed);
466             return this;
467         }
468 
469         /**
470          * Optional: Specifies the type(s) of authenticators that may be invoked by
471          * {@link BiometricPrompt} to authenticate the user. Available authenticator types are
472          * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to:
473          * <ul>
474          *     <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li>
475          *     <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li>
476          * </ul>
477          *
478          * <p>If this method is used and no authenticator of any of the specified types is available
479          * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will
480          * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}
481          * will be invoked with an appropriate error code.
482          *
483          * <p>This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and
484          * overrides the latter if both are used. Using this method to enable device credential
485          * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative
486          * button on the prompt, making it an error to also call
487          * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
488          *
489          * <p>If unlocking cryptographic operation(s), it is the application's responsibility to
490          * request authentication with the proper set of authenticators (e.g. match the
491          * authenticators specified during key generation).
492          *
493          * @see android.security.keystore.KeyGenParameterSpec.Builder
494          * @see KeyProperties#AUTH_BIOMETRIC_STRONG
495          * @see KeyProperties#AUTH_DEVICE_CREDENTIAL
496          *
497          * @param authenticators A bit field representing all valid authenticator types that may be
498          *                       invoked by the prompt.
499          * @return This builder.
500          */
501         @NonNull
setAllowedAuthenticators(@uthenticators.Types int authenticators)502         public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) {
503             mPromptInfo.setAuthenticators(authenticators);
504             return this;
505         }
506 
507         /**
508          * If non-empty, requests authentication to be performed only if the sensor is contained
509          * within the list. Note that the actual sensor presented to the user/test will meet all
510          * constraints specified within this builder. For example, on a device with the below
511          * configuration:
512          *
513          * SensorId: 1, Strength: BIOMETRIC_STRONG
514          * SensorId: 2, Strength: BIOMETRIC_WEAK
515          *
516          * If authentication is invoked with setAllowedAuthenticators(BIOMETRIC_STRONG) and
517          * setAllowedSensorIds(2), then no sensor will be eligible for authentication.
518          *
519          * @see {@link BiometricManager#getSensorProperties()}
520          *
521          * @param sensorIds Sensor IDs to constrain this authentication to.
522          * @return This builder
523          * @hide
524          */
525         @TestApi
526         @NonNull
527         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setAllowedSensorIds(@onNull List<Integer> sensorIds)528         public Builder setAllowedSensorIds(@NonNull List<Integer> sensorIds) {
529             mPromptInfo.setAllowedSensorIds(sensorIds);
530             return this;
531         }
532 
533         /**
534          * @param allow If true, allows authentication when the calling package is not in the
535          *              foreground. This is set to false by default.
536          * @return This builder
537          * @hide
538          */
539         @TestApi
540         @NonNull
541         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setAllowBackgroundAuthentication(boolean allow)542         public Builder setAllowBackgroundAuthentication(boolean allow) {
543             mPromptInfo.setAllowBackgroundAuthentication(allow);
544             return this;
545         }
546 
547         /**
548          * Remove {@link Builder#setAllowBackgroundAuthentication(boolean)} once
549          * FLAG_ALLOW_PRIVATE_PROFILE is enabled.
550          *
551          * @param allow If true, allows authentication when the calling package is not in the
552          *              foreground. This is set to false by default.
553          * @param useParentProfileForDeviceCredential If true, uses parent profile for device
554          *                                            credential IME request
555          * @return This builder
556          * @hide
557          */
558         @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
559         @TestApi
560         @NonNull
561         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setAllowBackgroundAuthentication(boolean allow, boolean useParentProfileForDeviceCredential)562         public Builder setAllowBackgroundAuthentication(boolean allow,
563                 boolean useParentProfileForDeviceCredential) {
564             mPromptInfo.setAllowBackgroundAuthentication(allow);
565             mPromptInfo.setUseParentProfileForDeviceCredential(useParentProfileForDeviceCredential);
566             return this;
567         }
568 
569         /**
570          * If set check the Device Policy Manager for disabled biometrics.
571          *
572          * @param checkDevicePolicyManager
573          * @return This builder.
574          * @hide
575          */
576         @NonNull
577         @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager)578         public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
579             mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
580             return this;
581         }
582 
583         /**
584          * If set, receive internal events via {@link AuthenticationCallback#onSystemEvent(int)}
585          * @param set
586          * @return This builder.
587          * @hide
588          */
589         @NonNull
590         @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
setReceiveSystemEvents(boolean set)591         public Builder setReceiveSystemEvents(boolean set) {
592             mPromptInfo.setReceiveSystemEvents(set);
593             return this;
594         }
595 
596         /**
597          * Flag to decide if authentication should ignore enrollment state.
598          * Defaults to false (not ignoring enrollment state)
599          * @param ignoreEnrollmentState
600          * @return This builder.
601          * @hide
602          */
603         @NonNull
604         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setIgnoreEnrollmentState(boolean ignoreEnrollmentState)605         public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
606             mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
607             return this;
608         }
609 
610         /**
611          * Set if BiometricPrompt is being used by the legacy fingerprint manager API.
612          * @param sensorId sensor id
613          * @return This builder.
614          * @hide
615          */
616         @NonNull
617         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setIsForLegacyFingerprintManager(int sensorId)618         public Builder setIsForLegacyFingerprintManager(int sensorId) {
619             mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
620             return this;
621         }
622 
623         /**
624          * Set if emergency call button should show, for example if biometrics are
625          * required to access the dialer app
626          * @param showEmergencyCallButton if true, shows emergency call button
627          * @return This builder.
628          * @hide
629          */
630         @NonNull
631         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setShowEmergencyCallButton(boolean showEmergencyCallButton)632         public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) {
633             mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton);
634             return this;
635         }
636 
637         /**
638          * Set caller's component name for getting logo icon/description. This should only be used
639          * by ConfirmDeviceCredentialActivity, see b/337082634 for more context.
640          *
641          * @param componentNameForConfirmDeviceCredentialActivity set the component name for
642          *                                                        ConfirmDeviceCredentialActivity.
643          * @return This builder.
644          * @hide
645          */
646         @NonNull
647         @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
setComponentNameForConfirmDeviceCredentialActivity( ComponentName componentNameForConfirmDeviceCredentialActivity)648         public Builder setComponentNameForConfirmDeviceCredentialActivity(
649                 ComponentName componentNameForConfirmDeviceCredentialActivity) {
650             mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity(
651                     componentNameForConfirmDeviceCredentialActivity);
652             return this;
653         }
654 
655         // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
656 
657         /**
658          * Creates a {@link BiometricPrompt}.
659          *
660          * @return An instance of {@link BiometricPrompt}.
661          *
662          * @throws IllegalArgumentException If any required fields are unset, or if given any
663          * invalid combination of field values.
664          */
665         @NonNull
build()666         public BiometricPrompt build() {
667             final CharSequence title = mPromptInfo.getTitle();
668             final CharSequence negative = mPromptInfo.getNegativeButtonText();
669             final boolean useDefaultTitle = mPromptInfo.isUseDefaultTitle();
670             final boolean deviceCredentialAllowed = mPromptInfo.isDeviceCredentialAllowed();
671             final @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators();
672             final boolean willShowDeviceCredentialButton = deviceCredentialAllowed
673                     || isCredentialAllowed(authenticators);
674 
675             if (TextUtils.isEmpty(title) && !useDefaultTitle) {
676                 throw new IllegalArgumentException("Title must be set and non-empty");
677             } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) {
678                 throw new IllegalArgumentException("Negative text must be set and non-empty");
679             } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) {
680                 throw new IllegalArgumentException("Can't have both negative button behavior"
681                         + " and device credential enabled");
682             }
683             mService = (mService == null) ? IAuthService.Stub.asInterface(
684                     ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
685             return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo,
686                     mContentViewMoreOptionsButtonInfo, mService);
687         }
688     }
689 
690     private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
691         private final long mAuthRequestId;
692 
OnAuthenticationCancelListener(long id)693         OnAuthenticationCancelListener(long id) {
694             mAuthRequestId = id;
695         }
696 
697         @Override
onCancel()698         public void onCancel() {
699             if (!mIsPromptShowing) {
700                 Log.w(TAG, "BP is not showing");
701                 return;
702             }
703             Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
704             cancelAuthentication(mAuthRequestId);
705         }
706     }
707 
708     private final IBinder mToken = new Binder();
709     private final Context mContext;
710     private final IAuthService mService;
711     private final PromptInfo mPromptInfo;
712     private final ButtonInfo mNegativeButtonInfo;
713     private final ButtonInfo mContentViewMoreOptionsButtonInfo;
714 
715     private CryptoObject mCryptoObject;
716     private Executor mExecutor;
717     private AuthenticationCallback mAuthenticationCallback;
718 
719     private final IBiometricServiceReceiver mBiometricServiceReceiver =
720             new IBiometricServiceReceiver.Stub() {
721 
722         @Override
723         public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) {
724             mExecutor.execute(() -> {
725                 final AuthenticationResult result =
726                         new AuthenticationResult(mCryptoObject, authenticationType);
727                 mAuthenticationCallback.onAuthenticationSucceeded(result);
728                 mIsPromptShowing = false;
729             });
730         }
731 
732         @Override
733         public void onAuthenticationFailed() {
734             mExecutor.execute(() -> {
735                 mAuthenticationCallback.onAuthenticationFailed();
736             });
737         }
738 
739         @Override
740         public void onError(@BiometricAuthenticator.Modality int modality, int error,
741                 int vendorCode) {
742 
743             String errorMessage = null;
744             switch (modality) {
745                 case TYPE_FACE:
746                     errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
747                     break;
748 
749                 case TYPE_FINGERPRINT:
750                     errorMessage = FingerprintManager.getErrorString(mContext, error, vendorCode);
751                     break;
752             }
753 
754             // Look for generic errors, as it may be a combination of modalities, or no modality
755             // (e.g. attempted biometric authentication without biometric sensors).
756             if (errorMessage == null) {
757                 switch (error) {
758                     case BIOMETRIC_ERROR_CANCELED:
759                         errorMessage = mContext.getString(R.string.biometric_error_canceled);
760                         break;
761                     case BIOMETRIC_ERROR_USER_CANCELED:
762                         errorMessage = mContext.getString(R.string.biometric_error_user_canceled);
763                         break;
764                     case BIOMETRIC_ERROR_HW_NOT_PRESENT:
765                         errorMessage = mContext.getString(R.string.biometric_error_hw_unavailable);
766                         break;
767                     case BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL:
768                         errorMessage = mContext.getString(
769                                 R.string.biometric_error_device_not_secured);
770                         break;
771                     default:
772                         Log.e(TAG, "Unknown error, modality: " + modality
773                                 + " error: " + error
774                                 + " vendorCode: " + vendorCode);
775                         errorMessage = mContext.getString(R.string.biometric_error_generic);
776                         break;
777                 }
778             }
779 
780             final String stringToSend = errorMessage;
781             mExecutor.execute(() -> {
782                 mAuthenticationCallback.onAuthenticationError(error, stringToSend);
783                 mIsPromptShowing = false;
784             });
785         }
786 
787         @Override
788         public void onAcquired(int acquireInfo, String message) {
789             mExecutor.execute(() -> {
790                 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
791             });
792         }
793 
794         @Override
795         public void onDialogDismissed(int reason) {
796             // Check the reason and invoke OnClickListener(s) if necessary
797             if (reason == DISMISSED_REASON_NEGATIVE) {
798                 mNegativeButtonInfo.executor.execute(() -> {
799                     mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
800                     mIsPromptShowing = false;
801                 });
802             } else if (reason == DISMISSED_REASON_CONTENT_VIEW_MORE_OPTIONS) {
803                 if (mContentViewMoreOptionsButtonInfo != null) {
804                     mContentViewMoreOptionsButtonInfo.executor.execute(() -> {
805                         mContentViewMoreOptionsButtonInfo.listener.onClick(null,
806                                 DialogInterface.BUTTON_NEGATIVE);
807                     });
808                 }
809             } else {
810                 mIsPromptShowing = false;
811                 Log.e(TAG, "Unknown reason: " + reason);
812             }
813         }
814 
815         @Override
816         public void onSystemEvent(int event) {
817             mExecutor.execute(() -> {
818                 mAuthenticationCallback.onSystemEvent(event);
819             });
820         }
821     };
822 
823     private boolean mIsPromptShowing;
824 
BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo, ButtonInfo contentViewMoreOptionsButtonInfo, IAuthService service)825     private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
826             ButtonInfo contentViewMoreOptionsButtonInfo, IAuthService service) {
827         mContext = context;
828         mPromptInfo = promptInfo;
829         mNegativeButtonInfo = negativeButtonInfo;
830         mContentViewMoreOptionsButtonInfo = contentViewMoreOptionsButtonInfo;
831         mService = service;
832         mIsPromptShowing = false;
833     }
834 
835     /**
836      * Gets the drawable resource of the logo for the prompt, as set by
837      * {@link Builder#setLogoRes(int)}. Currently for system applications use only.
838      *
839      * @return The drawable resource of the logo, or 0 if the prompt has no logo resource set.
840      */
841     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
842     @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
843     @DrawableRes
getLogoRes()844     public int getLogoRes() {
845         return mPromptInfo.getLogoRes();
846     }
847 
848     /**
849      * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}.
850      * Currently for system applications use only.
851      *
852      * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
853      */
854     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
855     @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
856     @Nullable
getLogoBitmap()857     public Bitmap getLogoBitmap() {
858         return mPromptInfo.getLogoBitmap();
859     }
860 
861     /**
862      * Gets the logo description for the prompt, as set by
863      * {@link Builder#setLogoDescription(String)}.
864      * Currently for system applications use only.
865      *
866      * @return The logo description of the prompt, or null if the prompt has no logo description
867      * set.
868      */
869     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
870     @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
871     @Nullable
getLogoDescription()872     public String getLogoDescription() {
873         return mPromptInfo.getLogoDescription();
874     }
875 
876     /**
877      * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
878      * @return The title of the prompt, which is guaranteed to be non-null.
879      */
880     @NonNull
getTitle()881     public CharSequence getTitle() {
882         return mPromptInfo.getTitle();
883     }
884 
885     /**
886      * Whether to use a default modality-specific title. For internal use only.
887      * @return See {@link Builder#setUseDefaultTitle()}.
888      * @hide
889      */
890     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
shouldUseDefaultTitle()891     public boolean shouldUseDefaultTitle() {
892         return mPromptInfo.isUseDefaultTitle();
893     }
894 
895     /**
896      * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}.
897      * @return The subtitle for the prompt, or null if the prompt has no subtitle.
898      */
899     @Nullable
getSubtitle()900     public CharSequence getSubtitle() {
901         return mPromptInfo.getSubtitle();
902     }
903 
904     /**
905      * Whether to use a default subtitle. For internal use only.
906      * @return See {@link Builder#setUseDefaultSubtitle()}.
907      * @hide
908      */
909     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
shouldUseDefaultSubtitle()910     public boolean shouldUseDefaultSubtitle() {
911         return mPromptInfo.isUseDefaultSubtitle();
912     }
913 
914     /**
915      * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}.
916      * @return The description for the prompt, or null if the prompt has no description.
917      */
918     @Nullable
getDescription()919     public CharSequence getDescription() {
920         return mPromptInfo.getDescription();
921     }
922 
923     /**
924      * Gets the content view for the prompt, as set by
925      * {@link Builder#setContentView(PromptContentView)}.
926      *
927      * @return The content view for the prompt, or null if the prompt has no content view.
928      */
929     @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
930     @Nullable
getContentView()931     public PromptContentView getContentView() {
932         return mPromptInfo.getContentView();
933     }
934 
935     /**
936      * Gets the negative button text for the prompt, as set by
937      * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
938      * @return The negative button text for the prompt, or null if no negative button text was set.
939      */
940     @Nullable
getNegativeButtonText()941     public CharSequence getNegativeButtonText() {
942         return mPromptInfo.getNegativeButtonText();
943     }
944 
945     /**
946      * Determines if explicit user confirmation is required by the prompt, as set by
947      * {@link Builder#setConfirmationRequired(boolean)}.
948      *
949      * @return true if explicit user confirmation is required, or false otherwise.
950      */
isConfirmationRequired()951     public boolean isConfirmationRequired() {
952         return mPromptInfo.isConfirmationRequested();
953     }
954 
955     /**
956      * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the
957      * user, as set by {@link Builder#setAllowedAuthenticators(int)}.
958      *
959      * @return A bit field representing the type(s) of authenticators that may be invoked by the
960      * prompt (as defined by {@link Authenticators}), or 0 if this field was not set.
961      */
962     @Nullable
getAllowedAuthenticators()963     public int getAllowedAuthenticators() {
964         return mPromptInfo.getAuthenticators();
965     }
966 
967     /**
968      * @return The values set by {@link Builder#setAllowedSensorIds(List)}
969      * @hide
970      */
971     @TestApi
972     @NonNull
getAllowedSensorIds()973     public List<Integer> getAllowedSensorIds() {
974         return mPromptInfo.getAllowedSensorIds();
975     }
976 
977     /**
978      * @return The value set by {@link Builder#setAllowBackgroundAuthentication(boolean)}
979      * @hide
980      */
981     @TestApi
isAllowBackgroundAuthentication()982     public boolean isAllowBackgroundAuthentication() {
983         return mPromptInfo.isAllowBackgroundAuthentication();
984     }
985 
986     /**
987      * A wrapper class for the cryptographic operations supported by BiometricPrompt.
988      *
989      * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
990      * {@link IdentityCredential}, {@link PresentationSession} and {@link KeyAgreement}.
991      *
992      * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and
993      * time-based. This is specified during key creation via the timeout parameter of the
994      * {@code setUserAuthenticationParameters(int, int)} method of {@link
995      * android.security.keystore.KeyGenParameterSpec.Builder}.
996      *
997      * <p>CryptoObjects are used to unlock auth-per-use keys via
998      * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor,
999      * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration
1000      * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard).
1001      * If a time-based key is not available for use (i.e. none of the allowed authenticators have
1002      * been unlocked recently), applications can prompt the user to authenticate via
1003      * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)}
1004      *
1005      * @see Builder#setAllowedAuthenticators(int)
1006      */
1007     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
1008         /**
1009          * Create from a {@link Signature} object.
1010          *
1011          * @param signature a {@link Signature} object.
1012          */
CryptoObject(@onNull Signature signature)1013         public CryptoObject(@NonNull Signature signature) {
1014             super(signature);
1015         }
1016 
1017         /**
1018          * Create from a {@link Cipher} object.
1019          *
1020          * @param cipher a {@link Cipher} object.
1021          */
CryptoObject(@onNull Cipher cipher)1022         public CryptoObject(@NonNull Cipher cipher) {
1023             super(cipher);
1024         }
1025 
1026         /**
1027          * Create from a {@link Mac} object.
1028          *
1029          * @param mac a {@link Mac} object.
1030          */
CryptoObject(@onNull Mac mac)1031         public CryptoObject(@NonNull Mac mac) {
1032             super(mac);
1033         }
1034 
1035         /**
1036          * Create from a {@link IdentityCredential} object.
1037          *
1038          * @param credential a {@link IdentityCredential} object.
1039          * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
1040          */
1041         @Deprecated
CryptoObject(@onNull IdentityCredential credential)1042         public CryptoObject(@NonNull IdentityCredential credential) {
1043             super(credential);
1044         }
1045 
1046         /**
1047          * Create from a {@link PresentationSession} object.
1048          *
1049          * @param session a {@link PresentationSession} object.
1050          */
CryptoObject(@onNull PresentationSession session)1051         public CryptoObject(@NonNull PresentationSession session) {
1052             super(session);
1053         }
1054 
1055         /**
1056          * Create from a {@link KeyAgreement} object.
1057          *
1058          * @param keyAgreement a {@link KeyAgreement} object.
1059          */
1060         @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
CryptoObject(@onNull KeyAgreement keyAgreement)1061         public CryptoObject(@NonNull KeyAgreement keyAgreement) {
1062             super(keyAgreement);
1063         }
1064 
1065         /**
1066          * Create from an operation handle.
1067          * @see CryptoObject#getOperationHandle()
1068          *
1069          * @param operationHandle the operation handle associated with this object.
1070          */
1071         @FlaggedApi(FLAG_GET_OP_ID_CRYPTO_OBJECT)
CryptoObject(long operationHandle)1072         public CryptoObject(long operationHandle) {
1073             super(operationHandle);
1074         }
1075 
1076         /**
1077          * Get {@link Signature} object.
1078          * @return {@link Signature} object or null if this doesn't contain one.
1079          */
getSignature()1080         public @Nullable Signature getSignature() {
1081             return super.getSignature();
1082         }
1083 
1084         /**
1085          * Get {@link Cipher} object.
1086          * @return {@link Cipher} object or null if this doesn't contain one.
1087          */
getCipher()1088         public @Nullable Cipher getCipher() {
1089             return super.getCipher();
1090         }
1091 
1092         /**
1093          * Get {@link Mac} object.
1094          * @return {@link Mac} object or null if this doesn't contain one.
1095          */
getMac()1096         public @Nullable Mac getMac() {
1097             return super.getMac();
1098         }
1099 
1100         /**
1101          * Get {@link IdentityCredential} object.
1102          * @return {@link IdentityCredential} object or null if this doesn't contain one.
1103          * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
1104          */
1105         @Deprecated
getIdentityCredential()1106         public @Nullable IdentityCredential getIdentityCredential() {
1107             return super.getIdentityCredential();
1108         }
1109 
1110         /**
1111          * Get {@link PresentationSession} object.
1112          * @return {@link PresentationSession} object or null if this doesn't contain one.
1113          */
getPresentationSession()1114         public @Nullable PresentationSession getPresentationSession() {
1115             return super.getPresentationSession();
1116         }
1117 
1118         /**
1119          * Get {@link KeyAgreement} object.
1120          * @return {@link KeyAgreement} object or null if this doesn't contain one.
1121          */
1122         @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
getKeyAgreement()1123         public @Nullable KeyAgreement getKeyAgreement() {
1124             return super.getKeyAgreement();
1125         }
1126 
1127         /**
1128          * Returns the {@code operationHandle} associated with this object or 0 if none.
1129          * The {@code operationHandle} is the underlying identifier associated with
1130          * the {@code CryptoObject}.
1131          *
1132          * <p> The {@code operationHandle} can be used to reconstruct a {@code CryptoObject}
1133          * instance. This is useful for any cross-process communication as the {@code CryptoObject}
1134          * class is not {@link android.os.Parcelable}. Hence, if the {@code CryptoObject} is
1135          * constructed in one process, and needs to be propagated to another process,
1136          * before calling the
1137          * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor,
1138          * AuthenticationCallback)} API in the second process, the recommendation is to retrieve the
1139          * {@code operationHandle} using this API, and then reconstruct the
1140          * {@code CryptoObject}using the constructor that takes in an {@code operationHandle}, and
1141          * pass that in to the {@code authenticate} API mentioned above.
1142          */
1143         @FlaggedApi(FLAG_GET_OP_ID_CRYPTO_OBJECT)
getOperationHandle()1144         public long getOperationHandle() {
1145             return super.getOpId();
1146         }
1147     }
1148 
1149     /**
1150      * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
1151      * entering their device PIN, pattern, or password.
1152      */
1153     public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
1154 
1155     /**
1156      * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
1157      * presenting some form of biometric (e.g. fingerprint or face).
1158      */
1159     public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
1160 
1161     /**
1162      * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
1163      * @hide
1164      */
1165     @Retention(RetentionPolicy.SOURCE)
1166     @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
1167     public @interface AuthenticationResultType {
1168     }
1169 
1170     /**
1171      * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
1172      * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
1173      * AuthenticationCallback)}.
1174      */
1175     public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
1176         /**
1177          * Authentication result
1178          * @param crypto
1179          * @param authenticationType
1180          * @hide
1181          */
AuthenticationResult(CryptoObject crypto, @AuthenticationResultType int authenticationType)1182         public AuthenticationResult(CryptoObject crypto,
1183                 @AuthenticationResultType int authenticationType) {
1184             // Identifier and userId is not used for BiometricPrompt.
1185             super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
1186         }
1187 
1188         /**
1189          * Provides the crypto object associated with this transaction.
1190          * @return The crypto object provided to {@link #authenticate(CryptoObject,
1191          * CancellationSignal, Executor, AuthenticationCallback)}
1192          */
getCryptoObject()1193         public CryptoObject getCryptoObject() {
1194             return (CryptoObject) super.getCryptoObject();
1195         }
1196 
1197         /**
1198          * Provides the type of authentication (e.g. device credential or biometric) that was
1199          * requested from and successfully provided by the user.
1200          *
1201          * @return An integer value representing the authentication method used.
1202          */
getAuthenticationType()1203         public @AuthenticationResultType int getAuthenticationType() {
1204             return super.getAuthenticationType();
1205         }
1206     }
1207 
1208     /**
1209      * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal,
1210      * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject,
1211      * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation
1212      * of this for listening to authentication events.
1213      */
1214     public abstract static class AuthenticationCallback extends
1215             BiometricAuthenticator.AuthenticationCallback {
1216         /**
1217          * Called when an unrecoverable error has been encountered and the operation is complete.
1218          * No further actions will be made on this object.
1219          * @param errorCode An integer identifying the error message
1220          * @param errString A human-readable error string that can be shown on an UI
1221          */
1222         @Override
onAuthenticationError(int errorCode, CharSequence errString)1223         public void onAuthenticationError(int errorCode, CharSequence errString) {}
1224 
1225         /**
1226          * Called when a recoverable error has been encountered during authentication. The help
1227          * string is provided to give the user guidance for what went wrong, such as "Sensor dirty,
1228          * please clean it."
1229          * @param helpCode An integer identifying the error message
1230          * @param helpString A human-readable string that can be shown on an UI
1231          */
1232         @Override
onAuthenticationHelp(int helpCode, CharSequence helpString)1233         public void onAuthenticationHelp(int helpCode, CharSequence helpString) {}
1234 
1235         /**
1236          * Called when a biometric is recognized.
1237          * @param result An object containing authentication-related data
1238          */
onAuthenticationSucceeded(AuthenticationResult result)1239         public void onAuthenticationSucceeded(AuthenticationResult result) {}
1240 
1241         /**
1242          * Called when a biometric is valid but not recognized.
1243          */
1244         @Override
onAuthenticationFailed()1245         public void onAuthenticationFailed() {}
1246 
1247         /**
1248          * Called when a biometric has been acquired, but hasn't been processed yet.
1249          * @hide
1250          */
1251         @Override
onAuthenticationAcquired(int acquireInfo)1252         public void onAuthenticationAcquired(int acquireInfo) {}
1253 
1254         /**
1255          * Receiver for internal system events. See {@link Builder#setReceiveSystemEvents(boolean)}
1256          * @hide
1257          */
onSystemEvent(int event)1258         public void onSystemEvent(int event) {}
1259     }
1260 
1261     /**
1262      * Authenticates for the given user.
1263      *
1264      * @param cancel An object that can be used to cancel authentication
1265      * @param executor An executor to handle callback events
1266      * @param callback An object to receive authentication events
1267      * @param userId The user to authenticate
1268      *
1269      * @hide
1270      */
1271     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
authenticateUser(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)1272     public void authenticateUser(@NonNull CancellationSignal cancel,
1273             @NonNull @CallbackExecutor Executor executor,
1274             @NonNull AuthenticationCallback callback,
1275             int userId) {
1276         if (cancel == null) {
1277             throw new IllegalArgumentException("Must supply a cancellation signal");
1278         }
1279         if (executor == null) {
1280             throw new IllegalArgumentException("Must supply an executor");
1281         }
1282         if (callback == null) {
1283             throw new IllegalArgumentException("Must supply a callback");
1284         }
1285 
1286         authenticateInternal(0 /* operationId */, cancel, executor, callback, userId);
1287     }
1288 
1289     /**
1290      * Authenticates for the given keystore operation.
1291      *
1292      * @param cancel An object that can be used to cancel authentication
1293      * @param executor An executor to handle callback events
1294      * @param callback An object to receive authentication events
1295      * @param operationId The keystore operation associated with authentication
1296      *
1297      * @return A requestId that can be used to cancel this operation.
1298      *
1299      * @hide
1300      */
1301     @RequiresPermission(USE_BIOMETRIC)
authenticateForOperation( @onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, long operationId)1302     public long authenticateForOperation(
1303             @NonNull CancellationSignal cancel,
1304             @NonNull @CallbackExecutor Executor executor,
1305             @NonNull AuthenticationCallback callback,
1306             long operationId) {
1307         if (cancel == null) {
1308             throw new IllegalArgumentException("Must supply a cancellation signal");
1309         }
1310         if (executor == null) {
1311             throw new IllegalArgumentException("Must supply an executor");
1312         }
1313         if (callback == null) {
1314             throw new IllegalArgumentException("Must supply a callback");
1315         }
1316 
1317         return authenticateInternal(operationId, cancel, executor, callback, mContext.getUserId());
1318     }
1319 
1320     /**
1321      * This call warms up the biometric hardware, displays a system-provided dialog, and starts
1322      * scanning for a biometric. It terminates when {@link
1323      * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
1324      * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user
1325      * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This
1326      * operation can be canceled by using the provided cancel object. The application will receive
1327      * authentication errors through {@link AuthenticationCallback}, and button events through the
1328      * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor,
1329      * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object,
1330      * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
1331      * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the
1332      * previous client and start a new authentication. The interrupted client will receive a
1333      * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
1334      * CharSequence)}.
1335      *
1336      * <p>Note: Applications generally should not cancel and start authentication in quick
1337      * succession. For example, to properly handle authentication across configuration changes, it's
1338      * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
1339      * the application will not need to cancel/restart authentication during the configuration
1340      * change.
1341      *
1342      * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements
1343      * for <strong>Strong</strong> are permitted to integrate with Keystore to perform related
1344      * cryptographic operations. Therefore, it is an error to call this method after explicitly
1345      * calling {@link Builder#setAllowedAuthenticators(int)} with any biometric strength other than
1346      * {@link Authenticators#BIOMETRIC_STRONG}.
1347      *
1348      * @throws IllegalArgumentException If any argument is null, or if the allowed biometric
1349      * authenticator strength is explicitly set to {@link Authenticators#BIOMETRIC_WEAK}. Prior to
1350      * {@link android.os.Build.VERSION_CODES#R}, this exception is also thrown if
1351      * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true.
1352      *
1353      * @param crypto A cryptographic operation to be unlocked after successful authentication.
1354      * @param cancel An object that can be used to cancel authentication.
1355      * @param executor An executor to handle callback events.
1356      * @param callback An object to receive authentication events.
1357      */
1358     @RequiresPermission(USE_BIOMETRIC)
authenticate(@onNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)1359     public void authenticate(@NonNull CryptoObject crypto,
1360             @NonNull CancellationSignal cancel,
1361             @NonNull @CallbackExecutor Executor executor,
1362             @NonNull AuthenticationCallback callback) {
1363 
1364         FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED,
1365                 true /* isCrypto */,
1366                 mPromptInfo.isConfirmationRequested(),
1367                 mPromptInfo.isDeviceCredentialAllowed(),
1368                 mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET,
1369                 mPromptInfo.getAuthenticators());
1370 
1371         if (crypto == null) {
1372             throw new IllegalArgumentException("Must supply a crypto object");
1373         }
1374         if (cancel == null) {
1375             throw new IllegalArgumentException("Must supply a cancellation signal");
1376         }
1377         if (executor == null) {
1378             throw new IllegalArgumentException("Must supply an executor");
1379         }
1380         if (callback == null) {
1381             throw new IllegalArgumentException("Must supply a callback");
1382         }
1383 
1384         // Disallow explicitly setting any non-Strong biometric authenticator types.
1385         @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators();
1386         if (authenticators == Authenticators.EMPTY_SET) {
1387             authenticators = Authenticators.BIOMETRIC_STRONG;
1388         }
1389         final int biometricStrength = authenticators & Authenticators.BIOMETRIC_WEAK;
1390         if ((biometricStrength & ~Authenticators.BIOMETRIC_STRONG) != 0) {
1391             throw new IllegalArgumentException("Only Strong biometrics supported with crypto");
1392         }
1393 
1394         authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
1395     }
1396 
1397     /**
1398      * This call warms up the biometric hardware, displays a system-provided dialog, and starts
1399      * scanning for a biometric. It terminates when {@link
1400      * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
1401      * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when
1402      * the user dismisses the system-provided dialog.  This operation can be canceled by using the
1403      * provided cancel object. The application will receive authentication errors through {@link
1404      * AuthenticationCallback}, and button events through the corresponding callback set in {@link
1405      * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.  It is
1406      * safe to reuse the {@link BiometricPrompt} object, and calling {@link
1407      * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while
1408      * an existing authentication attempt is occurring will stop the previous client and start a new
1409      * authentication. The interrupted client will receive a cancelled notification through {@link
1410      * AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
1411      *
1412      * <p>Note: Applications generally should not cancel and start authentication in quick
1413      * succession. For example, to properly handle authentication across configuration changes, it's
1414      * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
1415      * the application will not need to cancel/restart authentication during the configuration
1416      * change.
1417      *
1418      * @throws IllegalArgumentException If any of the arguments are null.
1419      *
1420      * @param cancel An object that can be used to cancel authentication.
1421      * @param executor An executor to handle callback events.
1422      * @param callback An object to receive authentication events.
1423      */
1424     @RequiresPermission(USE_BIOMETRIC)
authenticate(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)1425     public void authenticate(@NonNull CancellationSignal cancel,
1426             @NonNull @CallbackExecutor Executor executor,
1427             @NonNull AuthenticationCallback callback) {
1428 
1429         FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED,
1430                 false /* isCrypto */,
1431                 mPromptInfo.isConfirmationRequested(),
1432                 mPromptInfo.isDeviceCredentialAllowed(),
1433                 mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET,
1434                 mPromptInfo.getAuthenticators());
1435 
1436         if (cancel == null) {
1437             throw new IllegalArgumentException("Must supply a cancellation signal");
1438         }
1439         if (executor == null) {
1440             throw new IllegalArgumentException("Must supply an executor");
1441         }
1442         if (callback == null) {
1443             throw new IllegalArgumentException("Must supply a callback");
1444         }
1445         authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
1446     }
1447 
cancelAuthentication(long requestId)1448     private void cancelAuthentication(long requestId) {
1449         if (mService != null) {
1450             try {
1451                 mService.cancelAuthentication(mToken, mContext.getPackageName(), requestId);
1452             } catch (RemoteException e) {
1453                 Log.e(TAG, "Unable to cancel authentication", e);
1454             }
1455         }
1456     }
1457 
authenticateInternal( @ullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)1458     private void authenticateInternal(
1459             @Nullable CryptoObject crypto,
1460             @NonNull CancellationSignal cancel,
1461             @NonNull @CallbackExecutor Executor executor,
1462             @NonNull AuthenticationCallback callback,
1463             int userId) {
1464 
1465         mCryptoObject = crypto;
1466         final long operationId = crypto != null ? crypto.getOpId() : 0L;
1467         authenticateInternal(operationId, cancel, executor, callback, userId);
1468     }
1469 
authenticateInternal( long operationId, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId)1470     private long authenticateInternal(
1471             long operationId,
1472             @NonNull CancellationSignal cancel,
1473             @NonNull @CallbackExecutor Executor executor,
1474             @NonNull AuthenticationCallback callback,
1475             int userId) {
1476         // Ensure we don't return the wrong crypto object as an auth result.
1477         if (mCryptoObject != null && mCryptoObject.getOpId() != operationId) {
1478             Log.w(TAG, "CryptoObject operation ID does not match argument; setting field to null");
1479             mCryptoObject = null;
1480         }
1481 
1482         try {
1483             if (cancel.isCanceled()) {
1484                 Log.w(TAG, "Authentication already canceled");
1485                 return -1;
1486             }
1487 
1488             mExecutor = executor;
1489             mAuthenticationCallback = callback;
1490             if (mIsPromptShowing) {
1491                 final String stringToSend = mContext.getString(R.string.biometric_error_canceled);
1492                 mExecutor.execute(() -> {
1493                     mAuthenticationCallback.onAuthenticationError(BIOMETRIC_ERROR_CANCELED,
1494                             stringToSend);
1495                 });
1496                 return -1;
1497             }
1498 
1499             final PromptInfo promptInfo;
1500             if (operationId != 0L) {
1501                 // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth.
1502                 // Note that we use a new PromptInfo here so as to not overwrite the application's
1503                 // preference, since it is possible that the same prompt configuration be used
1504                 // without a crypto object later.
1505                 Parcel parcel = Parcel.obtain();
1506                 mPromptInfo.writeToParcel(parcel, 0 /* flags */);
1507                 parcel.setDataPosition(0);
1508                 promptInfo = new PromptInfo(parcel);
1509                 if (promptInfo.getAuthenticators() == Authenticators.EMPTY_SET) {
1510                     promptInfo.setAuthenticators(Authenticators.BIOMETRIC_STRONG);
1511                 }
1512             } else {
1513                 promptInfo = mPromptInfo;
1514             }
1515 
1516             final long authId = mService.authenticate(mToken, operationId, userId,
1517                     mBiometricServiceReceiver, mContext.getPackageName(), promptInfo);
1518             cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
1519             mIsPromptShowing = true;
1520 
1521             return authId;
1522         } catch (RemoteException e) {
1523             Log.e(TAG, "Remote exception while authenticating", e);
1524             mExecutor.execute(() -> callback.onAuthenticationError(
1525                     BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
1526                     mContext.getString(R.string.biometric_error_hw_unavailable)));
1527             return -1;
1528         }
1529     }
1530 
isCredentialAllowed(@uthenticators.Types int allowedAuthenticators)1531     private static boolean isCredentialAllowed(@Authenticators.Types int allowedAuthenticators) {
1532         return (allowedAuthenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
1533     }
1534 
1535     /** Converts {@code drawable} to a {@link Bitmap}. */
convertDrawableToBitmap(Drawable drawable)1536     private static Bitmap convertDrawableToBitmap(Drawable drawable) {
1537         if (drawable == null) {
1538             return null;
1539         }
1540 
1541         if (drawable instanceof BitmapDrawable) {
1542             return ((BitmapDrawable) drawable).getBitmap();
1543         }
1544 
1545         Bitmap bitmap;
1546         if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
1547             bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
1548             // Single color bitmap will be created of 1x1 pixel
1549         } else {
1550             bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
1551                     drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
1552         }
1553 
1554         final Canvas canvas = new Canvas(bitmap);
1555         drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
1556         drawable.draw(canvas);
1557         return bitmap;
1558     }
1559 }
1560