1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.biometrics;
18 
19 import static android.Manifest.permission.USE_BIOMETRIC;
20 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
21 
22 import android.annotation.CallbackExecutor;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.app.KeyguardManager;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.os.Binder;
30 import android.os.Bundle;
31 import android.os.CancellationSignal;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.security.identity.IdentityCredential;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.internal.R;
40 
41 import java.security.Signature;
42 import java.util.concurrent.Executor;
43 
44 import javax.crypto.Cipher;
45 import javax.crypto.Mac;
46 
47 /**
48  * A class that manages a system-provided biometric dialog.
49  */
50 public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
51 
52     private static final String TAG = "BiometricPrompt";
53 
54     /**
55      * @hide
56      */
57     public static final String KEY_TITLE = "title";
58     /**
59      * @hide
60      */
61     public static final String KEY_USE_DEFAULT_TITLE = "use_default_title";
62     /**
63      * @hide
64      */
65     public static final String KEY_SUBTITLE = "subtitle";
66     /**
67      * @hide
68      */
69     public static final String KEY_DESCRIPTION = "description";
70     /**
71      * @hide
72      */
73     public static final String KEY_POSITIVE_TEXT = "positive_text";
74     /**
75      * @hide
76      */
77     public static final String KEY_NEGATIVE_TEXT = "negative_text";
78     /**
79      * @hide
80      */
81     public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
82     /**
83      * @hide
84      */
85     public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential";
86     /**
87      * @hide
88      */
89     public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL
90             = "from_confirm_device_credential";
91 
92     /**
93      * Error/help message will show for this amount of time.
94      * For error messages, the dialog will also be dismissed after this amount of time.
95      * Error messages will be propagated back to the application via AuthenticationCallback
96      * after this amount of time.
97      * @hide
98      */
99     public static final int HIDE_DIALOG_DELAY = 2000; // ms
100 
101     /**
102      * @hide
103      */
104     public static final int DISMISSED_REASON_POSITIVE = 1;
105 
106     /**
107      * @hide
108      */
109     public static final int DISMISSED_REASON_NEGATIVE = 2;
110 
111     /**
112      * @hide
113      */
114     public static final int DISMISSED_REASON_USER_CANCEL = 3;
115 
116     private static class ButtonInfo {
117         Executor executor;
118         DialogInterface.OnClickListener listener;
ButtonInfo(Executor ex, DialogInterface.OnClickListener l)119         ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
120             executor = ex;
121             listener = l;
122         }
123     }
124 
125     /**
126      * A builder that collects arguments to be shown on the system-provided biometric dialog.
127      **/
128     public static class Builder {
129         private final Bundle mBundle;
130         private ButtonInfo mPositiveButtonInfo;
131         private ButtonInfo mNegativeButtonInfo;
132         private Context mContext;
133 
134         /**
135          * Creates a builder for a biometric dialog.
136          * @param context
137          */
Builder(Context context)138         public Builder(Context context) {
139             mBundle = new Bundle();
140             mContext = context;
141         }
142 
143         /**
144          * Required: Set the title to display.
145          * @param title
146          * @return
147          */
setTitle(@onNull CharSequence title)148         @NonNull public Builder setTitle(@NonNull CharSequence title) {
149             mBundle.putCharSequence(KEY_TITLE, title);
150             return this;
151         }
152 
153         /**
154          * For internal use currently. Only takes effect if title is null/empty. Shows a default
155          * modality-specific title.
156          * @hide
157          */
158         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
setUseDefaultTitle()159         @NonNull public Builder setUseDefaultTitle() {
160             mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true);
161             return this;
162         }
163 
164         /**
165          * Optional: Set the subtitle to display.
166          * @param subtitle
167          * @return
168          */
setSubtitle(@onNull CharSequence subtitle)169         @NonNull public Builder setSubtitle(@NonNull CharSequence subtitle) {
170             mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
171             return this;
172         }
173 
174         /**
175          * Optional: Set the description to display.
176          * @param description
177          * @return
178          */
setDescription(@onNull CharSequence description)179         @NonNull public Builder setDescription(@NonNull CharSequence description) {
180             mBundle.putCharSequence(KEY_DESCRIPTION, description);
181             return this;
182         }
183 
184         /**
185          * Optional: Set the text for the positive button. If not set, the positive button
186          * will not show.
187          * @param text
188          * @return
189          * @hide
190          */
setPositiveButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)191         @NonNull public Builder setPositiveButton(@NonNull CharSequence text,
192                 @NonNull @CallbackExecutor Executor executor,
193                 @NonNull DialogInterface.OnClickListener listener) {
194             if (TextUtils.isEmpty(text)) {
195                 throw new IllegalArgumentException("Text must be set and non-empty");
196             }
197             if (executor == null) {
198                 throw new IllegalArgumentException("Executor must not be null");
199             }
200             if (listener == null) {
201                 throw new IllegalArgumentException("Listener must not be null");
202             }
203             mBundle.putCharSequence(KEY_POSITIVE_TEXT, text);
204             mPositiveButtonInfo = new ButtonInfo(executor, listener);
205             return this;
206         }
207 
208         /**
209          * Required: Set the text for the negative button. This would typically be used as a
210          * "Cancel" button, but may be also used to show an alternative method for authentication,
211          * such as screen that asks for a backup password.
212          *
213          * Note that this should not be set if {@link #setDeviceCredentialAllowed(boolean)}(boolean)
214          * is set to true.
215          *
216          * @param text
217          * @return
218          */
setNegativeButton(@onNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener)219         @NonNull public Builder setNegativeButton(@NonNull CharSequence text,
220                 @NonNull @CallbackExecutor Executor executor,
221                 @NonNull DialogInterface.OnClickListener listener) {
222             if (TextUtils.isEmpty(text)) {
223                 throw new IllegalArgumentException("Text must be set and non-empty");
224             }
225             if (executor == null) {
226                 throw new IllegalArgumentException("Executor must not be null");
227             }
228             if (listener == null) {
229                 throw new IllegalArgumentException("Listener must not be null");
230             }
231             mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
232             mNegativeButtonInfo = new ButtonInfo(executor, listener);
233             return this;
234         }
235 
236         /**
237          * Optional: A hint to the system to require user confirmation after a biometric has been
238          * authenticated. For example, implicit modalities like Face and Iris authentication are
239          * passive, meaning they don't require an explicit user action to complete. When set to
240          * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
241          * will require confirmation by default.
242          *
243          * A typical use case for not requiring confirmation would be for low-risk transactions,
244          * such as re-authenticating a recently authenticated application. A typical use case for
245          * requiring confirmation would be for authorizing a purchase.
246          *
247          * Note that this is a hint to the system. The system may choose to ignore the flag. For
248          * example, if the user disables implicit authentication in Settings, or if it does not
249          * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
250          * requiring confirmation.
251          *
252          * @param requireConfirmation
253          */
setConfirmationRequired(boolean requireConfirmation)254         @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) {
255             mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
256             return this;
257         }
258 
259         /**
260          * The user will first be prompted to authenticate with biometrics, but also given the
261          * option to authenticate with their device PIN, pattern, or password. Developers should
262          * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device
263          * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be
264          * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}.
265          * Defaults to false.
266          *
267          * Note that {@link #setNegativeButton(CharSequence, Executor,
268          * DialogInterface.OnClickListener)} should not be set if this is set to true.
269          *
270          * @param allowed When true, the prompt will fall back to ask for the user's device
271          *               credentials (PIN, pattern, or password).
272          * @return
273          */
setDeviceCredentialAllowed(boolean allowed)274         @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) {
275             mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, allowed);
276             return this;
277         }
278 
279         /**
280          * TODO(123378871): Remove when moved.
281          * @return
282          * @hide
283          */
284         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
setFromConfirmDeviceCredential()285         @NonNull public Builder setFromConfirmDeviceCredential() {
286             mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true);
287             return this;
288         }
289 
290         /**
291          * Creates a {@link BiometricPrompt}.
292          * @return a {@link BiometricPrompt}
293          * @throws IllegalArgumentException if any of the required fields are not set.
294          */
build()295         @NonNull public BiometricPrompt build() {
296             final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
297             final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
298             final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
299             final boolean enableFallback = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
300 
301             if (TextUtils.isEmpty(title) && !useDefaultTitle) {
302                 throw new IllegalArgumentException("Title must be set and non-empty");
303             } else if (TextUtils.isEmpty(negative) && !enableFallback) {
304                 throw new IllegalArgumentException("Negative text must be set and non-empty");
305             } else if (!TextUtils.isEmpty(negative) && enableFallback) {
306                 throw new IllegalArgumentException("Can't have both negative button behavior"
307                         + " and device credential enabled");
308             }
309             return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
310         }
311     }
312 
313     private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
314         @Override
onCancel()315         public void onCancel() {
316             cancelAuthentication();
317         }
318     }
319 
320     private final IBinder mToken = new Binder();
321     private final Context mContext;
322     private final IBiometricService mService;
323     private final Bundle mBundle;
324     private final ButtonInfo mPositiveButtonInfo;
325     private final ButtonInfo mNegativeButtonInfo;
326 
327     private CryptoObject mCryptoObject;
328     private Executor mExecutor;
329     private AuthenticationCallback mAuthenticationCallback;
330 
331     private final IBiometricServiceReceiver mBiometricServiceReceiver =
332             new IBiometricServiceReceiver.Stub() {
333 
334         @Override
335         public void onAuthenticationSucceeded() throws RemoteException {
336             mExecutor.execute(() -> {
337                 final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
338                 mAuthenticationCallback.onAuthenticationSucceeded(result);
339             });
340         }
341 
342         @Override
343         public void onAuthenticationFailed() throws RemoteException {
344             mExecutor.execute(() -> {
345                 mAuthenticationCallback.onAuthenticationFailed();
346             });
347         }
348 
349         @Override
350         public void onError(int error, String message) throws RemoteException {
351             mExecutor.execute(() -> {
352                 mAuthenticationCallback.onAuthenticationError(error, message);
353             });
354         }
355 
356         @Override
357         public void onAcquired(int acquireInfo, String message) throws RemoteException {
358             mExecutor.execute(() -> {
359                 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
360             });
361         }
362 
363         @Override
364         public void onDialogDismissed(int reason) throws RemoteException {
365             // Check the reason and invoke OnClickListener(s) if necessary
366             if (reason == DISMISSED_REASON_POSITIVE) {
367                 mPositiveButtonInfo.executor.execute(() -> {
368                     mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
369                 });
370             } else if (reason == DISMISSED_REASON_NEGATIVE) {
371                 mNegativeButtonInfo.executor.execute(() -> {
372                     mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
373                 });
374             }
375         }
376     };
377 
BiometricPrompt(Context context, Bundle bundle, ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo)378     private BiometricPrompt(Context context, Bundle bundle,
379             ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
380         mContext = context;
381         mBundle = bundle;
382         mPositiveButtonInfo = positiveButtonInfo;
383         mNegativeButtonInfo = negativeButtonInfo;
384         mService = IBiometricService.Stub.asInterface(
385                 ServiceManager.getService(Context.BIOMETRIC_SERVICE));
386     }
387 
388     /**
389      * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework
390      * supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
391      */
392     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
CryptoObject(@onNull Signature signature)393         public CryptoObject(@NonNull Signature signature) {
394             super(signature);
395         }
396 
CryptoObject(@onNull Cipher cipher)397         public CryptoObject(@NonNull Cipher cipher) {
398             super(cipher);
399         }
400 
CryptoObject(@onNull Mac mac)401         public CryptoObject(@NonNull Mac mac) {
402             super(mac);
403         }
404 
CryptoObject(@onNull IdentityCredential credential)405         public CryptoObject(@NonNull IdentityCredential credential) {
406             super(credential);
407         }
408 
409         /**
410          * Get {@link Signature} object.
411          * @return {@link Signature} object or null if this doesn't contain one.
412          */
getSignature()413         public Signature getSignature() {
414             return super.getSignature();
415         }
416 
417         /**
418          * Get {@link Cipher} object.
419          * @return {@link Cipher} object or null if this doesn't contain one.
420          */
getCipher()421         public Cipher getCipher() {
422             return super.getCipher();
423         }
424 
425         /**
426          * Get {@link Mac} object.
427          * @return {@link Mac} object or null if this doesn't contain one.
428          */
getMac()429         public Mac getMac() {
430             return super.getMac();
431         }
432 
433         /**
434          * Get {@link IdentityCredential} object.
435          * @return {@link IdentityCredential} object or null if this doesn't contain one.
436          */
getIdentityCredential()437         public @Nullable IdentityCredential getIdentityCredential() {
438             return super.getIdentityCredential();
439         }
440     }
441 
442     /**
443      * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
444      * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
445      * AuthenticationCallback)}
446      */
447     public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
448         /**
449          * Authentication result
450          * @param crypto
451          * @hide
452          */
AuthenticationResult(CryptoObject crypto)453         public AuthenticationResult(CryptoObject crypto) {
454             // Identifier and userId is not used for BiometricPrompt.
455             super(crypto, null /* identifier */, 0 /* userId */);
456         }
457         /**
458          * Obtain the crypto object associated with this transaction
459          * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
460          * Executor, AuthenticationCallback)}
461          */
getCryptoObject()462         public CryptoObject getCryptoObject() {
463             return (CryptoObject) super.getCryptoObject();
464         }
465     }
466 
467     /**
468      * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal,
469      * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject,
470      * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation
471      * of this for listening to authentication events.
472      */
473     public abstract static class AuthenticationCallback extends
474             BiometricAuthenticator.AuthenticationCallback {
475         /**
476          * Called when an unrecoverable error has been encountered and the operation is complete.
477          * No further actions will be made on this object.
478          * @param errorCode An integer identifying the error message
479          * @param errString A human-readable error string that can be shown on an UI
480          */
481         @Override
onAuthenticationError(int errorCode, CharSequence errString)482         public void onAuthenticationError(int errorCode, CharSequence errString) {}
483 
484         /**
485          * Called when a recoverable error has been encountered during authentication. The help
486          * string is provided to give the user guidance for what went wrong, such as "Sensor dirty,
487          * please clean it."
488          * @param helpCode An integer identifying the error message
489          * @param helpString A human-readable string that can be shown on an UI
490          */
491         @Override
onAuthenticationHelp(int helpCode, CharSequence helpString)492         public void onAuthenticationHelp(int helpCode, CharSequence helpString) {}
493 
494         /**
495          * Called when a biometric is recognized.
496          * @param result An object containing authentication-related data
497          */
onAuthenticationSucceeded(AuthenticationResult result)498         public void onAuthenticationSucceeded(AuthenticationResult result) {}
499 
500         /**
501          * Called when a biometric is valid but not recognized.
502          */
503         @Override
onAuthenticationFailed()504         public void onAuthenticationFailed() {}
505 
506         /**
507          * Called when a biometric has been acquired, but hasn't been processed yet.
508          * @hide
509          */
510         @Override
onAuthenticationAcquired(int acquireInfo)511         public void onAuthenticationAcquired(int acquireInfo) {}
512     }
513 
514     /**
515      * Authenticates for the given user.
516      * @param cancel An object that can be used to cancel authentication
517      * @param executor An executor to handle callback events
518      * @param callback An object to receive authentication events
519      * @param userId The user to authenticate
520      * @hide
521      */
522     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
authenticateUser(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId, IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback)523     public void authenticateUser(@NonNull CancellationSignal cancel,
524             @NonNull @CallbackExecutor Executor executor,
525             @NonNull AuthenticationCallback callback,
526             int userId,
527             IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
528         if (cancel == null) {
529             throw new IllegalArgumentException("Must supply a cancellation signal");
530         }
531         if (executor == null) {
532             throw new IllegalArgumentException("Must supply an executor");
533         }
534         if (callback == null) {
535             throw new IllegalArgumentException("Must supply a callback");
536         }
537         authenticateInternal(null /* crypto */, cancel, executor, callback, userId,
538                 confirmDeviceCredentialCallback);
539     }
540 
541     /**
542      * This call warms up the biometric hardware, displays a system-provided dialog, and starts
543      * scanning for a biometric. It terminates when {@link
544      * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
545      * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user
546      * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This
547      * operation can be canceled by using the provided cancel object. The application will receive
548      * authentication errors through {@link AuthenticationCallback}, and button events through the
549      * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor,
550      * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object,
551      * and calling {@link BiometricPrompt#authenticate( CancellationSignal, Executor,
552      * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the
553      * previous client and start a new authentication. The interrupted client will receive a
554      * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
555      * CharSequence)}.
556      *
557      * Note: Applications generally should not cancel and start authentication in quick succession.
558      * For example, to properly handle authentication across configuration changes, it's recommended
559      * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
560      * application will not need to cancel/restart authentication during the configuration change.
561      *
562      * @throws IllegalArgumentException If any of the arguments are null
563      *
564      * @param crypto Object associated with the call
565      * @param cancel An object that can be used to cancel authentication
566      * @param executor An executor to handle callback events
567      * @param callback An object to receive authentication events
568      */
569     @RequiresPermission(USE_BIOMETRIC)
authenticate(@onNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)570     public void authenticate(@NonNull CryptoObject crypto,
571             @NonNull CancellationSignal cancel,
572             @NonNull @CallbackExecutor Executor executor,
573             @NonNull AuthenticationCallback callback) {
574         if (crypto == null) {
575             throw new IllegalArgumentException("Must supply a crypto object");
576         }
577         if (cancel == null) {
578             throw new IllegalArgumentException("Must supply a cancellation signal");
579         }
580         if (executor == null) {
581             throw new IllegalArgumentException("Must supply an executor");
582         }
583         if (callback == null) {
584             throw new IllegalArgumentException("Must supply a callback");
585         }
586         if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
587             throw new IllegalArgumentException("Device credential not supported with crypto");
588         }
589         authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(),
590                 null /* confirmDeviceCredentialCallback */);
591     }
592 
593     /**
594      * This call warms up the biometric hardware, displays a system-provided dialog, and starts
595      * scanning for a biometric. It terminates when {@link
596      * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
597      * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when
598      * the user dismisses the system-provided dialog.  This operation can be canceled by using the
599      * provided cancel object. The application will receive authentication errors through {@link
600      * AuthenticationCallback}, and button events through the corresponding callback set in {@link
601      * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.  It is
602      * safe to reuse the {@link BiometricPrompt} object, and calling {@link
603      * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while
604      * an existing authentication attempt is occurring will stop the previous client and start a new
605      * authentication. The interrupted client will receive a cancelled notification through {@link
606      * AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
607      *
608      * Note: Applications generally should not cancel and start authentication in quick succession.
609      * For example, to properly handle authentication across configuration changes, it's recommended
610      * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
611      * application will not need to cancel/restart authentication during the configuration change.
612      *
613      * @throws IllegalArgumentException If any of the arguments are null
614      *
615      * @param cancel An object that can be used to cancel authentication
616      * @param executor An executor to handle callback events
617      * @param callback An object to receive authentication events
618      */
619     @RequiresPermission(USE_BIOMETRIC)
authenticate(@onNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback)620     public void authenticate(@NonNull CancellationSignal cancel,
621             @NonNull @CallbackExecutor Executor executor,
622             @NonNull AuthenticationCallback callback) {
623         if (cancel == null) {
624             throw new IllegalArgumentException("Must supply a cancellation signal");
625         }
626         if (executor == null) {
627             throw new IllegalArgumentException("Must supply an executor");
628         }
629         if (callback == null) {
630             throw new IllegalArgumentException("Must supply a callback");
631         }
632         authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(),
633                 null /* confirmDeviceCredentialCallback */);
634     }
635 
cancelAuthentication()636     private void cancelAuthentication() {
637         if (mService != null) {
638             try {
639                 mService.cancelAuthentication(mToken, mContext.getOpPackageName());
640             } catch (RemoteException e) {
641                 Log.e(TAG, "Unable to cancel authentication", e);
642             }
643         }
644     }
645 
authenticateInternal(@ullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId, IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback)646     private void authenticateInternal(@Nullable CryptoObject crypto,
647             @NonNull CancellationSignal cancel,
648             @NonNull @CallbackExecutor Executor executor,
649             @NonNull AuthenticationCallback callback,
650             int userId,
651             IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
652         try {
653             if (cancel.isCanceled()) {
654                 Log.w(TAG, "Authentication already canceled");
655                 return;
656             } else {
657                 cancel.setOnCancelListener(new OnAuthenticationCancelListener());
658             }
659 
660             mCryptoObject = crypto;
661             mExecutor = executor;
662             mAuthenticationCallback = callback;
663             final long sessionId = crypto != null ? crypto.getOpId() : 0;
664             if (BiometricManager.hasBiometrics(mContext)) {
665                 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
666                         mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback);
667             } else {
668                 mExecutor.execute(() -> {
669                     callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
670                             mContext.getString(R.string.biometric_error_hw_unavailable));
671                 });
672             }
673         } catch (RemoteException e) {
674             Log.e(TAG, "Remote exception while authenticating", e);
675             mExecutor.execute(() -> {
676                 callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
677                         mContext.getString(R.string.biometric_error_hw_unavailable));
678             });
679         }
680     }
681 }
682