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