1 /**
2  * Copyright (C) 2014 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.fingerprint;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
20 import static android.Manifest.permission.MANAGE_FINGERPRINT;
21 import static android.Manifest.permission.USE_BIOMETRIC;
22 import static android.Manifest.permission.USE_FINGERPRINT;
23 
24 import android.annotation.CallbackExecutor;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.RequiresFeature;
28 import android.annotation.RequiresPermission;
29 import android.annotation.SystemService;
30 import android.app.ActivityManager;
31 import android.content.Context;
32 import android.content.pm.PackageManager;
33 import android.hardware.biometrics.BiometricAuthenticator;
34 import android.hardware.biometrics.BiometricFingerprintConstants;
35 import android.hardware.biometrics.BiometricPrompt;
36 import android.hardware.biometrics.IBiometricPromptReceiver;
37 import android.os.Binder;
38 import android.os.Bundle;
39 import android.os.CancellationSignal;
40 import android.os.CancellationSignal.OnCancelListener;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.IRemoteCallback;
44 import android.os.Looper;
45 import android.os.PowerManager;
46 import android.os.RemoteException;
47 import android.os.UserHandle;
48 import android.util.Slog;
49 
50 import java.security.Signature;
51 import java.util.List;
52 import java.util.concurrent.Executor;
53 
54 import javax.crypto.Cipher;
55 import javax.crypto.Mac;
56 
57 /**
58  * A class that coordinates access to the fingerprint hardware.
59  * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
60  * authentication. In a world where devices may have different types of biometric authentication,
61  * it's much more realistic to have a system-provided authentication dialog since the method may
62  * vary by vendor/device.
63  */
64 @Deprecated
65 @SystemService(Context.FINGERPRINT_SERVICE)
66 @RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
67 public class FingerprintManager implements BiometricFingerprintConstants {
68     private static final String TAG = "FingerprintManager";
69     private static final boolean DEBUG = true;
70     private static final int MSG_ENROLL_RESULT = 100;
71     private static final int MSG_ACQUIRED = 101;
72     private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
73     private static final int MSG_AUTHENTICATION_FAILED = 103;
74     private static final int MSG_ERROR = 104;
75     private static final int MSG_REMOVED = 105;
76     private static final int MSG_ENUMERATED = 106;
77 
78     private IFingerprintService mService;
79     private Context mContext;
80     private IBinder mToken = new Binder();
81     private BiometricAuthenticator.AuthenticationCallback mAuthenticationCallback;
82     private EnrollmentCallback mEnrollmentCallback;
83     private RemovalCallback mRemovalCallback;
84     private EnumerateCallback mEnumerateCallback;
85     private android.hardware.biometrics.CryptoObject mCryptoObject;
86     private Fingerprint mRemovalFingerprint;
87     private Handler mHandler;
88     private Executor mExecutor;
89 
90     private class OnEnrollCancelListener implements OnCancelListener {
91         @Override
onCancel()92         public void onCancel() {
93             cancelEnrollment();
94         }
95     }
96 
97     private class OnAuthenticationCancelListener implements OnCancelListener {
98         private android.hardware.biometrics.CryptoObject mCrypto;
99 
OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto)100         public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
101             mCrypto = crypto;
102         }
103 
104         @Override
onCancel()105         public void onCancel() {
106             cancelAuthentication(mCrypto);
107         }
108     }
109 
110     /**
111      * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
112      * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
113      * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
114      */
115     @Deprecated
116     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
CryptoObject(@onNull Signature signature)117         public CryptoObject(@NonNull Signature signature) {
118             super(signature);
119         }
120 
CryptoObject(@onNull Cipher cipher)121         public CryptoObject(@NonNull Cipher cipher) {
122             super(cipher);
123         }
124 
CryptoObject(@onNull Mac mac)125         public CryptoObject(@NonNull Mac mac) {
126             super(mac);
127         }
128 
129         /**
130          * Get {@link Signature} object.
131          * @return {@link Signature} object or null if this doesn't contain one.
132          */
getSignature()133         public Signature getSignature() {
134             return super.getSignature();
135         }
136 
137         /**
138          * Get {@link Cipher} object.
139          * @return {@link Cipher} object or null if this doesn't contain one.
140          */
getCipher()141         public Cipher getCipher() {
142             return super.getCipher();
143         }
144 
145         /**
146          * Get {@link Mac} object.
147          * @return {@link Mac} object or null if this doesn't contain one.
148          */
getMac()149         public Mac getMac() {
150             return super.getMac();
151         }
152     }
153 
154     /**
155      * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
156      *     CancellationSignal, int, AuthenticationCallback, Handler)}.
157      * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
158      */
159     @Deprecated
160     public static class AuthenticationResult {
161         private Fingerprint mFingerprint;
162         private CryptoObject mCryptoObject;
163         private int mUserId;
164 
165         /**
166          * Authentication result
167          *
168          * @param crypto the crypto object
169          * @param fingerprint the recognized fingerprint data, if allowed.
170          * @hide
171          */
AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId)172         public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
173             mCryptoObject = crypto;
174             mFingerprint = fingerprint;
175             mUserId = userId;
176         }
177 
178         /**
179          * Obtain the crypto object associated with this transaction
180          * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject,
181          *     CancellationSignal, int, AuthenticationCallback, Handler)}.
182          */
getCryptoObject()183         public CryptoObject getCryptoObject() { return mCryptoObject; }
184 
185         /**
186          * Obtain the Fingerprint associated with this operation. Applications are strongly
187          * discouraged from associating specific fingers with specific applications or operations.
188          *
189          * @hide
190          */
getFingerprint()191         public Fingerprint getFingerprint() { return mFingerprint; }
192 
193         /**
194          * Obtain the userId for which this fingerprint was authenticated.
195          * @hide
196          */
getUserId()197         public int getUserId() { return mUserId; }
198     };
199 
200     /**
201      * Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject,
202      * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
203      * FingerprintManager#authenticate(CryptoObject, CancellationSignal,
204      * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
205      * fingerprint events.
206      * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
207      */
208     @Deprecated
209     public static abstract class AuthenticationCallback
210             extends BiometricAuthenticator.AuthenticationCallback {
211         /**
212          * Called when an unrecoverable error has been encountered and the operation is complete.
213          * No further callbacks will be made on this object.
214          * @param errorCode An integer identifying the error message
215          * @param errString A human-readable error string that can be shown in UI
216          */
217         @Override
onAuthenticationError(int errorCode, CharSequence errString)218         public void onAuthenticationError(int errorCode, CharSequence errString) { }
219 
220         /**
221          * Called when a recoverable error has been encountered during authentication. The help
222          * string is provided to give the user guidance for what went wrong, such as
223          * "Sensor dirty, please clean it."
224          * @param helpCode An integer identifying the error message
225          * @param helpString A human-readable string that can be shown in UI
226          */
227         @Override
onAuthenticationHelp(int helpCode, CharSequence helpString)228         public void onAuthenticationHelp(int helpCode, CharSequence helpString) { }
229 
230         /**
231          * Called when a fingerprint is recognized.
232          * @param result An object containing authentication-related data
233          */
onAuthenticationSucceeded(AuthenticationResult result)234         public void onAuthenticationSucceeded(AuthenticationResult result) { }
235 
236         /**
237          * Called when a fingerprint is valid but not recognized.
238          */
239         @Override
onAuthenticationFailed()240         public void onAuthenticationFailed() { }
241 
242         /**
243          * Called when a fingerprint image has been acquired, but wasn't processed yet.
244          *
245          * @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants
246          * @hide
247          */
248         @Override
onAuthenticationAcquired(int acquireInfo)249         public void onAuthenticationAcquired(int acquireInfo) {}
250 
251         /**
252          * @hide
253          * @param result
254          */
255         @Override
onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result)256         public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) {
257             onAuthenticationSucceeded(new AuthenticationResult(
258                     (CryptoObject) result.getCryptoObject(),
259                     (Fingerprint) result.getId(), result.getUserId()));
260         }
261     };
262 
263     /**
264      * Callback structure provided to {@link FingerprintManager#enroll(long, EnrollmentCallback,
265      * CancellationSignal, int). Users of {@link #FingerprintManager()}
266      * must provide an implementation of this to {@link FingerprintManager#enroll(long,
267      * CancellationSignal, int, EnrollmentCallback) for listening to fingerprint events.
268      *
269      * @hide
270      */
271     public static abstract class EnrollmentCallback {
272         /**
273          * Called when an unrecoverable error has been encountered and the operation is complete.
274          * No further callbacks will be made on this object.
275          * @param errMsgId An integer identifying the error message
276          * @param errString A human-readable error string that can be shown in UI
277          */
onEnrollmentError(int errMsgId, CharSequence errString)278         public void onEnrollmentError(int errMsgId, CharSequence errString) { }
279 
280         /**
281          * Called when a recoverable error has been encountered during enrollment. The help
282          * string is provided to give the user guidance for what went wrong, such as
283          * "Sensor dirty, please clean it" or what they need to do next, such as
284          * "Touch sensor again."
285          * @param helpMsgId An integer identifying the error message
286          * @param helpString A human-readable string that can be shown in UI
287          */
onEnrollmentHelp(int helpMsgId, CharSequence helpString)288         public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { }
289 
290         /**
291          * Called as each enrollment step progresses. Enrollment is considered complete when
292          * remaining reaches 0. This function will not be called if enrollment fails. See
293          * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
294          * @param remaining The number of remaining steps
295          */
onEnrollmentProgress(int remaining)296         public void onEnrollmentProgress(int remaining) { }
297     };
298 
299     /**
300      * Callback structure provided to {@link #remove}. Users of {@link FingerprintManager} may
301      * optionally provide an implementation of this to
302      * {@link #remove(Fingerprint, int, RemovalCallback)} for listening to fingerprint template
303      * removal events.
304      *
305      * @hide
306      */
307     public static abstract class RemovalCallback {
308         /**
309          * Called when the given fingerprint can't be removed.
310          * @param fp The fingerprint that the call attempted to remove
311          * @param errMsgId An associated error message id
312          * @param errString An error message indicating why the fingerprint id can't be removed
313          */
onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString)314         public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { }
315 
316         /**
317          * Called when a given fingerprint is successfully removed.
318          * @param fp The fingerprint template that was removed.
319          * @param remaining The number of fingerprints yet to be removed in this operation. If
320          *         {@link #remove} is called on one fingerprint, this should be 0. If
321          *         {@link #remove} is called on a group, this should be the number of remaining
322          *         fingerprints in the group, and 0 after the last fingerprint is removed.
323          */
onRemovalSucceeded(Fingerprint fp, int remaining)324         public void onRemovalSucceeded(Fingerprint fp, int remaining) { }
325     };
326 
327     /**
328      * Callback structure provided to {@link FingerprintManager#enumerate(int). Users of
329      * {@link #FingerprintManager()} may optionally provide an implementation of this to
330      * {@link FingerprintManager#enumerate(int, int, EnumerateCallback)} for listening to
331      * fingerprint template removal events.
332      *
333      * @hide
334      */
335     public static abstract class EnumerateCallback {
336         /**
337          * Called when the given fingerprint can't be removed.
338          * @param errMsgId An associated error message id
339          * @param errString An error message indicating why the fingerprint id can't be removed
340          */
onEnumerateError(int errMsgId, CharSequence errString)341         public void onEnumerateError(int errMsgId, CharSequence errString) { }
342 
343         /**
344          * Called when a given fingerprint is successfully removed.
345          * @param fingerprint the fingerprint template that was removed.
346          */
onEnumerate(Fingerprint fingerprint)347         public void onEnumerate(Fingerprint fingerprint) { }
348     };
349 
350     /**
351      * @hide
352      */
353     public static abstract class LockoutResetCallback {
354 
355         /**
356          * Called when lockout period expired and clients are allowed to listen for fingerprint
357          * again.
358          */
onLockoutReset()359         public void onLockoutReset() { }
360     };
361 
362     /**
363      * Request authentication of a crypto object. This call warms up the fingerprint hardware
364      * and starts scanning for a fingerprint. It terminates when
365      * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
366      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
367      * which point the object is no longer valid. The operation can be canceled by using the
368      * provided cancel object.
369      *
370      * @param crypto object associated with the call or null if none required.
371      * @param cancel an object that can be used to cancel authentication
372      * @param flags optional flags; should be 0
373      * @param callback an object to receive authentication events
374      * @param handler an optional handler to handle callback events
375      *
376      * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
377      *         by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
378      *         facility</a>.
379      * @throws IllegalStateException if the crypto primitive is not initialized.
380      * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
381      * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
382      * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
383      * BiometricPrompt.AuthenticationCallback)}
384      */
385     @Deprecated
386     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
authenticate(@ullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler)387     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
388             int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
389         authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId());
390     }
391 
392     /**
393      * Use the provided handler thread for events.
394      * @param handler
395      */
useHandler(Handler handler)396     private void useHandler(Handler handler) {
397         if (handler != null) {
398             mHandler = new MyHandler(handler.getLooper());
399         } else if (mHandler.getLooper() != mContext.getMainLooper()){
400             mHandler = new MyHandler(mContext.getMainLooper());
401         }
402     }
403 
404     /**
405      * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
406      * CancellationSignal, int, AuthenticationCallback, Handler)}
407      * @param userId the user ID that the fingerprint hardware will authenticate for.
408      * @hide
409      */
410     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
authenticate(@ullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId)411     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
412             int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) {
413         if (callback == null) {
414             throw new IllegalArgumentException("Must supply an authentication callback");
415         }
416 
417         if (cancel != null) {
418             if (cancel.isCanceled()) {
419                 Slog.w(TAG, "authentication already canceled");
420                 return;
421             } else {
422                 cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
423             }
424         }
425 
426         if (mService != null) try {
427             useHandler(handler);
428             mAuthenticationCallback = callback;
429             mCryptoObject = crypto;
430             long sessionId = crypto != null ? crypto.getOpId() : 0;
431             mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
432                     mContext.getOpPackageName(), null /* bundle */, null /* receiver */);
433         } catch (RemoteException e) {
434             Slog.w(TAG, "Remote exception while authenticating: ", e);
435             if (callback != null) {
436                 // Though this may not be a hardware issue, it will cause apps to give up or try
437                 // again later.
438                 callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
439                         getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
440             }
441         }
442     }
443 
444     /**
445      * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
446      * CancellationSignal, Bundle, Executor, IBiometricPromptReceiver, AuthenticationCallback)}
447      * @param userId the user ID that the fingerprint hardware will authenticate for.
448      */
authenticate(int userId, @Nullable android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)449     private void authenticate(int userId,
450             @Nullable android.hardware.biometrics.CryptoObject crypto,
451             @NonNull CancellationSignal cancel,
452             @NonNull Bundle bundle,
453             @NonNull @CallbackExecutor Executor executor,
454             @NonNull IBiometricPromptReceiver receiver,
455             @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
456         mCryptoObject = crypto;
457         if (cancel.isCanceled()) {
458             Slog.w(TAG, "authentication already canceled");
459             return;
460         } else {
461             cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
462         }
463 
464         if (mService != null) {
465             try {
466                 mExecutor = executor;
467                 mAuthenticationCallback = callback;
468                 final long sessionId = crypto != null ? crypto.getOpId() : 0;
469                 mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
470                         0 /* flags */, mContext.getOpPackageName(), bundle, receiver);
471             } catch (RemoteException e) {
472                 Slog.w(TAG, "Remote exception while authenticating", e);
473                 mExecutor.execute(() -> {
474                     callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
475                             getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
476                 });
477             }
478         }
479     }
480 
481     /**
482      * Private method, see {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
483      * BiometricPrompt.AuthenticationCallback)}
484      * @param cancel
485      * @param executor
486      * @param callback
487      * @hide
488      */
authenticate( @onNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)489     public void authenticate(
490             @NonNull CancellationSignal cancel,
491             @NonNull Bundle bundle,
492             @NonNull @CallbackExecutor Executor executor,
493             @NonNull IBiometricPromptReceiver receiver,
494             @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
495         if (cancel == null) {
496             throw new IllegalArgumentException("Must supply a cancellation signal");
497         }
498         if (bundle == null) {
499             throw new IllegalArgumentException("Must supply a bundle");
500         }
501         if (executor == null) {
502             throw new IllegalArgumentException("Must supply an executor");
503         }
504         if (receiver == null) {
505             throw new IllegalArgumentException("Must supply a receiver");
506         }
507         if (callback == null) {
508             throw new IllegalArgumentException("Must supply a calback");
509         }
510         authenticate(mContext.getUserId(), null, cancel, bundle, executor, receiver, callback);
511     }
512 
513     /**
514      * Private method, see {@link BiometricPrompt#authenticate(BiometricPrompt.CryptoObject,
515      * CancellationSignal, Executor, BiometricPrompt.AuthenticationCallback)}
516      * @param crypto
517      * @param cancel
518      * @param executor
519      * @param callback
520      * @hide
521      */
authenticate(@onNull android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)522     public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto,
523             @NonNull CancellationSignal cancel,
524             @NonNull Bundle bundle,
525             @NonNull @CallbackExecutor Executor executor,
526             @NonNull IBiometricPromptReceiver receiver,
527             @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
528         if (crypto == null) {
529             throw new IllegalArgumentException("Must supply a crypto object");
530         }
531         if (cancel == null) {
532             throw new IllegalArgumentException("Must supply a cancellation signal");
533         }
534         if (bundle == null) {
535             throw new IllegalArgumentException("Must supply a bundle");
536         }
537         if (executor == null) {
538             throw new IllegalArgumentException("Must supply an executor");
539         }
540         if (receiver == null) {
541             throw new IllegalArgumentException("Must supply a receiver");
542         }
543         if (callback == null) {
544             throw new IllegalArgumentException("Must supply a callback");
545         }
546         authenticate(mContext.getUserId(), crypto, cancel,
547                 bundle, executor, receiver, callback);
548     }
549 
550     /**
551      * Request fingerprint enrollment. This call warms up the fingerprint hardware
552      * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
553      * {@link EnrollmentCallback} object. It terminates when
554      * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
555      * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
556      * which point the object is no longer valid. The operation can be canceled by using the
557      * provided cancel object.
558      * @param token a unique token provided by a recent creation or verification of device
559      * credentials (e.g. pin, pattern or password).
560      * @param cancel an object that can be used to cancel enrollment
561      * @param flags optional flags
562      * @param userId the user to whom this fingerprint will belong to
563      * @param callback an object to receive enrollment events
564      * @hide
565      */
566     @RequiresPermission(MANAGE_FINGERPRINT)
enroll(byte [] token, CancellationSignal cancel, int flags, int userId, EnrollmentCallback callback)567     public void enroll(byte [] token, CancellationSignal cancel, int flags,
568             int userId, EnrollmentCallback callback) {
569         if (userId == UserHandle.USER_CURRENT) {
570             userId = getCurrentUserId();
571         }
572         if (callback == null) {
573             throw new IllegalArgumentException("Must supply an enrollment callback");
574         }
575 
576         if (cancel != null) {
577             if (cancel.isCanceled()) {
578                 Slog.w(TAG, "enrollment already canceled");
579                 return;
580             } else {
581                 cancel.setOnCancelListener(new OnEnrollCancelListener());
582             }
583         }
584 
585         if (mService != null) try {
586             mEnrollmentCallback = callback;
587             mService.enroll(mToken, token, userId, mServiceReceiver, flags,
588                     mContext.getOpPackageName());
589         } catch (RemoteException e) {
590             Slog.w(TAG, "Remote exception in enroll: ", e);
591             if (callback != null) {
592                 // Though this may not be a hardware issue, it will cause apps to give up or try
593                 // again later.
594                 callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
595                         getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
596             }
597         }
598     }
599 
600     /**
601      * Requests a pre-enrollment auth token to tie enrollment to the confirmation of
602      * existing device credentials (e.g. pin/pattern/password).
603      * @hide
604      */
605     @RequiresPermission(MANAGE_FINGERPRINT)
preEnroll()606     public long preEnroll() {
607         long result = 0;
608         if (mService != null) try {
609             result = mService.preEnroll(mToken);
610         } catch (RemoteException e) {
611             throw e.rethrowFromSystemServer();
612         }
613         return result;
614     }
615 
616     /**
617      * Finishes enrollment and cancels the current auth token.
618      * @hide
619      */
620     @RequiresPermission(MANAGE_FINGERPRINT)
postEnroll()621     public int postEnroll() {
622         int result = 0;
623         if (mService != null) try {
624             result = mService.postEnroll(mToken);
625         } catch (RemoteException e) {
626             throw e.rethrowFromSystemServer();
627         }
628         return result;
629     }
630 
631     /**
632      * Sets the active user. This is meant to be used to select the current profile for enrollment
633      * to allow separate enrolled fingers for a work profile
634      * @param userId
635      * @hide
636      */
637     @RequiresPermission(MANAGE_FINGERPRINT)
setActiveUser(int userId)638     public void setActiveUser(int userId) {
639         if (mService != null) try {
640             mService.setActiveUser(userId);
641         } catch (RemoteException e) {
642             throw e.rethrowFromSystemServer();
643         }
644     }
645 
646     /**
647      * Remove given fingerprint template from fingerprint hardware and/or protected storage.
648      * @param fp the fingerprint item to remove
649      * @param userId the user who this fingerprint belongs to
650      * @param callback an optional callback to verify that fingerprint templates have been
651      * successfully removed. May be null of no callback is required.
652      *
653      * @hide
654      */
655     @RequiresPermission(MANAGE_FINGERPRINT)
remove(Fingerprint fp, int userId, RemovalCallback callback)656     public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
657         if (mService != null) try {
658             mRemovalCallback = callback;
659             mRemovalFingerprint = fp;
660             mService.remove(mToken, fp.getFingerId(), fp.getGroupId(), userId, mServiceReceiver);
661         } catch (RemoteException e) {
662             Slog.w(TAG, "Remote exception in remove: ", e);
663             if (callback != null) {
664                 callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE,
665                         getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
666             }
667         }
668     }
669 
670     /**
671      * Enumerate all fingerprint templates stored in hardware and/or protected storage.
672      * @param userId the user who this fingerprint belongs to
673      * @param callback an optional callback to verify that fingerprint templates have been
674      * successfully removed. May be null of no callback is required.
675      *
676      * @hide
677      */
678     @RequiresPermission(MANAGE_FINGERPRINT)
enumerate(int userId, @NonNull EnumerateCallback callback)679     public void enumerate(int userId, @NonNull EnumerateCallback callback) {
680         if (mService != null) try {
681             mEnumerateCallback = callback;
682             mService.enumerate(mToken, userId, mServiceReceiver);
683         } catch (RemoteException e) {
684             Slog.w(TAG, "Remote exception in enumerate: ", e);
685             if (callback != null) {
686                 callback.onEnumerateError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
687                         getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
688             }
689         }
690     }
691 
692     /**
693      * Renames the given fingerprint template
694      * @param fpId the fingerprint id
695      * @param userId the user who this fingerprint belongs to
696      * @param newName the new name
697      *
698      * @hide
699      */
700     @RequiresPermission(MANAGE_FINGERPRINT)
rename(int fpId, int userId, String newName)701     public void rename(int fpId, int userId, String newName) {
702         // Renames the given fpId
703         if (mService != null) {
704             try {
705                 mService.rename(fpId, userId, newName);
706             } catch (RemoteException e) {
707                 throw e.rethrowFromSystemServer();
708             }
709         } else {
710             Slog.w(TAG, "rename(): Service not connected!");
711         }
712     }
713 
714     /**
715      * Obtain the list of enrolled fingerprints templates.
716      * @return list of current fingerprint items
717      *
718      * @hide
719      */
720     @RequiresPermission(USE_FINGERPRINT)
getEnrolledFingerprints(int userId)721     public List<Fingerprint> getEnrolledFingerprints(int userId) {
722         if (mService != null) try {
723             return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName());
724         } catch (RemoteException e) {
725             throw e.rethrowFromSystemServer();
726         }
727         return null;
728     }
729 
730     /**
731      * Obtain the list of enrolled fingerprints templates.
732      * @return list of current fingerprint items
733      *
734      * @hide
735      */
736     @RequiresPermission(USE_FINGERPRINT)
getEnrolledFingerprints()737     public List<Fingerprint> getEnrolledFingerprints() {
738         return getEnrolledFingerprints(mContext.getUserId());
739     }
740 
741     /**
742      * Determine if there is at least one fingerprint enrolled.
743      *
744      * @return true if at least one fingerprint is enrolled, false otherwise
745      * @deprecated See {@link BiometricPrompt} and
746      * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
747      */
748     @Deprecated
749     @RequiresPermission(USE_FINGERPRINT)
hasEnrolledFingerprints()750     public boolean hasEnrolledFingerprints() {
751         if (mService != null) try {
752             return mService.hasEnrolledFingerprints(
753                     mContext.getUserId(), mContext.getOpPackageName());
754         } catch (RemoteException e) {
755             throw e.rethrowFromSystemServer();
756         }
757         return false;
758     }
759 
760     /**
761      * @hide
762      */
763     @RequiresPermission(allOf = {
764             USE_FINGERPRINT,
765             INTERACT_ACROSS_USERS})
hasEnrolledFingerprints(int userId)766     public boolean hasEnrolledFingerprints(int userId) {
767         if (mService != null) try {
768             return mService.hasEnrolledFingerprints(userId, mContext.getOpPackageName());
769         } catch (RemoteException e) {
770             throw e.rethrowFromSystemServer();
771         }
772         return false;
773     }
774 
775     /**
776      * Determine if fingerprint hardware is present and functional.
777      *
778      * @return true if hardware is present and functional, false otherwise.
779      * @deprecated See {@link BiometricPrompt} and
780      * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
781      */
782     @Deprecated
783     @RequiresPermission(USE_FINGERPRINT)
isHardwareDetected()784     public boolean isHardwareDetected() {
785         if (mService != null) {
786             try {
787                 long deviceId = 0; /* TODO: plumb hardware id to FPMS */
788                 return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
789             } catch (RemoteException e) {
790                 throw e.rethrowFromSystemServer();
791             }
792         } else {
793             Slog.w(TAG, "isFingerprintHardwareDetected(): Service not connected!");
794         }
795         return false;
796     }
797 
798     /**
799      * Retrieves the authenticator token for binding keys to the lifecycle
800      * of the calling user's fingerprints. Used only by internal clients.
801      *
802      * @hide
803      */
getAuthenticatorId()804     public long getAuthenticatorId() {
805         if (mService != null) {
806             try {
807                 return mService.getAuthenticatorId(mContext.getOpPackageName());
808             } catch (RemoteException e) {
809                 throw e.rethrowFromSystemServer();
810             }
811         } else {
812             Slog.w(TAG, "getAuthenticatorId(): Service not connected!");
813         }
814         return 0;
815     }
816 
817     /**
818      * Reset the lockout timer when asked to do so by keyguard.
819      *
820      * @param token an opaque token returned by password confirmation.
821      *
822      * @hide
823      */
resetTimeout(byte[] token)824     public void resetTimeout(byte[] token) {
825         if (mService != null) {
826             try {
827                 mService.resetTimeout(token);
828             } catch (RemoteException e) {
829                 throw e.rethrowFromSystemServer();
830             }
831         } else {
832             Slog.w(TAG, "resetTimeout(): Service not connected!");
833         }
834     }
835 
836     /**
837      * @hide
838      */
addLockoutResetCallback(final LockoutResetCallback callback)839     public void addLockoutResetCallback(final LockoutResetCallback callback) {
840         if (mService != null) {
841             try {
842                 final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
843                 mService.addLockoutResetCallback(
844                         new IFingerprintServiceLockoutResetCallback.Stub() {
845 
846                     @Override
847                     public void onLockoutReset(long deviceId, IRemoteCallback serverCallback)
848                             throws RemoteException {
849                         try {
850                             final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
851                                     PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback");
852                             wakeLock.acquire();
853                             mHandler.post(() -> {
854                                 try {
855                                     callback.onLockoutReset();
856                                 } finally {
857                                     wakeLock.release();
858                                 }
859                             });
860                         } finally {
861                             serverCallback.sendResult(null /* data */);
862                         }
863                     }
864                 });
865             } catch (RemoteException e) {
866                 throw e.rethrowFromSystemServer();
867             }
868         } else {
869             Slog.w(TAG, "addLockoutResetCallback(): Service not connected!");
870         }
871     }
872 
873     private class MyHandler extends Handler {
MyHandler(Context context)874         private MyHandler(Context context) {
875             super(context.getMainLooper());
876         }
877 
MyHandler(Looper looper)878         private MyHandler(Looper looper) {
879             super(looper);
880         }
881 
882         @Override
handleMessage(android.os.Message msg)883         public void handleMessage(android.os.Message msg) {
884             switch(msg.what) {
885                 case MSG_ENROLL_RESULT:
886                     sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
887                     break;
888                 case MSG_ACQUIRED:
889                     sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
890                             msg.arg2 /* vendorCode */);
891                     break;
892                 case MSG_AUTHENTICATION_SUCCEEDED:
893                     sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
894                     break;
895                 case MSG_AUTHENTICATION_FAILED:
896                     sendAuthenticatedFailed();
897                     break;
898                 case MSG_ERROR:
899                     sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */,
900                             msg.arg2 /* vendorCode */);
901                     break;
902                 case MSG_REMOVED:
903                     sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
904                     break;
905                 case MSG_ENUMERATED:
906                     sendEnumeratedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */,
907                             msg.arg2 /* groupId */);
908                     break;
909             }
910         }
911 
sendRemovedResult(Fingerprint fingerprint, int remaining)912         private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
913             if (mRemovalCallback == null) {
914                 return;
915             }
916             if (fingerprint == null) {
917                 Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
918                 return;
919             }
920 
921             int fingerId = fingerprint.getFingerId();
922             int reqFingerId = mRemovalFingerprint.getFingerId();
923             if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
924                 Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
925                 return;
926             }
927             int groupId = fingerprint.getGroupId();
928             int reqGroupId = mRemovalFingerprint.getGroupId();
929             if (groupId != reqGroupId) {
930                 Slog.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId);
931                 return;
932             }
933 
934             mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
935         }
936 
sendEnumeratedResult(long deviceId, int fingerId, int groupId)937         private void sendEnumeratedResult(long deviceId, int fingerId, int groupId) {
938             if (mEnumerateCallback != null) {
939                 mEnumerateCallback.onEnumerate(new Fingerprint(null, groupId, fingerId, deviceId));
940             }
941         }
942 
sendEnrollResult(Fingerprint fp, int remaining)943         private void sendEnrollResult(Fingerprint fp, int remaining) {
944             if (mEnrollmentCallback != null) {
945                 mEnrollmentCallback.onEnrollmentProgress(remaining);
946             }
947         }
948     };
949 
sendAuthenticatedSucceeded(Fingerprint fp, int userId)950     private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
951         if (mAuthenticationCallback != null) {
952             final BiometricAuthenticator.AuthenticationResult result =
953                     new BiometricAuthenticator.AuthenticationResult(mCryptoObject, fp, userId);
954             mAuthenticationCallback.onAuthenticationSucceeded(result);
955         }
956     }
957 
sendAuthenticatedFailed()958     private void sendAuthenticatedFailed() {
959         if (mAuthenticationCallback != null) {
960             mAuthenticationCallback.onAuthenticationFailed();
961         }
962     }
963 
sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode)964     private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
965         if (mAuthenticationCallback != null) {
966             mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
967         }
968         final String msg = getAcquiredString(acquireInfo, vendorCode);
969         if (msg == null) {
970             return;
971         }
972         // emulate HAL 2.1 behavior and send real acquiredInfo
973         final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
974                 ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
975         if (mEnrollmentCallback != null) {
976             mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
977         } else if (mAuthenticationCallback != null) {
978             mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
979         }
980     }
981 
sendErrorResult(long deviceId, int errMsgId, int vendorCode)982     private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
983         // emulate HAL 2.1 behavior and send real errMsgId
984         final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
985                 ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
986         if (mEnrollmentCallback != null) {
987             mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
988                     getErrorString(errMsgId, vendorCode));
989         } else if (mAuthenticationCallback != null) {
990             mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
991                     getErrorString(errMsgId, vendorCode));
992         } else if (mRemovalCallback != null) {
993             mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId,
994                     getErrorString(errMsgId, vendorCode));
995         } else if (mEnumerateCallback != null) {
996             mEnumerateCallback.onEnumerateError(clientErrMsgId,
997                     getErrorString(errMsgId, vendorCode));
998         }
999     }
1000 
1001     /**
1002      * @hide
1003      */
FingerprintManager(Context context, IFingerprintService service)1004     public FingerprintManager(Context context, IFingerprintService service) {
1005         mContext = context;
1006         mService = service;
1007         if (mService == null) {
1008             Slog.v(TAG, "FingerprintManagerService was null");
1009         }
1010         mHandler = new MyHandler(context);
1011     }
1012 
getCurrentUserId()1013     private int getCurrentUserId() {
1014         try {
1015             return ActivityManager.getService().getCurrentUser().id;
1016         } catch (RemoteException e) {
1017             throw e.rethrowFromSystemServer();
1018         }
1019     }
1020 
cancelEnrollment()1021     private void cancelEnrollment() {
1022         if (mService != null) try {
1023             mService.cancelEnrollment(mToken);
1024         } catch (RemoteException e) {
1025             throw e.rethrowFromSystemServer();
1026         }
1027     }
1028 
cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject)1029     private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
1030         if (mService != null) try {
1031             mService.cancelAuthentication(mToken, mContext.getOpPackageName());
1032         } catch (RemoteException e) {
1033             throw e.rethrowFromSystemServer();
1034         }
1035     }
1036 
1037     /**
1038      * @hide
1039      */
getErrorString(int errMsg, int vendorCode)1040     public String getErrorString(int errMsg, int vendorCode) {
1041         switch (errMsg) {
1042             case FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
1043                 return mContext.getString(
1044                     com.android.internal.R.string.fingerprint_error_unable_to_process);
1045             case FINGERPRINT_ERROR_HW_UNAVAILABLE:
1046                 return mContext.getString(
1047                     com.android.internal.R.string.fingerprint_error_hw_not_available);
1048             case FINGERPRINT_ERROR_NO_SPACE:
1049                 return mContext.getString(
1050                     com.android.internal.R.string.fingerprint_error_no_space);
1051             case FINGERPRINT_ERROR_TIMEOUT:
1052                 return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout);
1053             case FINGERPRINT_ERROR_CANCELED:
1054                 return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled);
1055             case FINGERPRINT_ERROR_LOCKOUT:
1056                 return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout);
1057             case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
1058                 return mContext.getString(
1059                         com.android.internal.R.string.fingerprint_error_lockout_permanent);
1060             case FINGERPRINT_ERROR_USER_CANCELED:
1061                 return mContext.getString(
1062                         com.android.internal.R.string.fingerprint_error_user_canceled);
1063             case FINGERPRINT_ERROR_NO_FINGERPRINTS:
1064                 return mContext.getString(
1065                         com.android.internal.R.string.fingerprint_error_no_fingerprints);
1066             case FINGERPRINT_ERROR_HW_NOT_PRESENT:
1067                 return mContext.getString(
1068                         com.android.internal.R.string.fingerprint_error_hw_not_present);
1069             case FINGERPRINT_ERROR_VENDOR: {
1070                     String[] msgArray = mContext.getResources().getStringArray(
1071                             com.android.internal.R.array.fingerprint_error_vendor);
1072                     if (vendorCode < msgArray.length) {
1073                         return msgArray[vendorCode];
1074                     }
1075                 }
1076         }
1077         Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
1078         return null;
1079     }
1080 
1081     /**
1082      * @hide
1083      */
getAcquiredString(int acquireInfo, int vendorCode)1084     public String getAcquiredString(int acquireInfo, int vendorCode) {
1085         switch (acquireInfo) {
1086             case FINGERPRINT_ACQUIRED_GOOD:
1087                 return null;
1088             case FINGERPRINT_ACQUIRED_PARTIAL:
1089                 return mContext.getString(
1090                     com.android.internal.R.string.fingerprint_acquired_partial);
1091             case FINGERPRINT_ACQUIRED_INSUFFICIENT:
1092                 return mContext.getString(
1093                     com.android.internal.R.string.fingerprint_acquired_insufficient);
1094             case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
1095                 return mContext.getString(
1096                     com.android.internal.R.string.fingerprint_acquired_imager_dirty);
1097             case FINGERPRINT_ACQUIRED_TOO_SLOW:
1098                 return mContext.getString(
1099                     com.android.internal.R.string.fingerprint_acquired_too_slow);
1100             case FINGERPRINT_ACQUIRED_TOO_FAST:
1101                 return mContext.getString(
1102                     com.android.internal.R.string.fingerprint_acquired_too_fast);
1103             case FINGERPRINT_ACQUIRED_VENDOR: {
1104                     String[] msgArray = mContext.getResources().getStringArray(
1105                             com.android.internal.R.array.fingerprint_acquired_vendor);
1106                     if (vendorCode < msgArray.length) {
1107                         return msgArray[vendorCode];
1108                     }
1109                 }
1110         }
1111         Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
1112         return null;
1113     }
1114 
1115     private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
1116 
1117         @Override // binder call
1118         public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
1119             mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
1120                     new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
1121         }
1122 
1123         @Override // binder call
1124         public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
1125             if (mExecutor != null) {
1126                 mExecutor.execute(() -> {
1127                     sendAcquiredResult(deviceId, acquireInfo, vendorCode);
1128                 });
1129             } else {
1130                 mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode,
1131                         deviceId).sendToTarget();
1132             }
1133         }
1134 
1135         @Override // binder call
1136         public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
1137             if (mExecutor != null) {
1138                 mExecutor.execute(() -> {
1139                     sendAuthenticatedSucceeded(fp, userId);
1140                 });
1141             } else {
1142                 mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
1143             }
1144         }
1145 
1146         @Override // binder call
1147         public void onAuthenticationFailed(long deviceId) {
1148             if (mExecutor != null) {
1149                 mExecutor.execute(() -> {
1150                     sendAuthenticatedFailed();
1151                 });
1152             } else {
1153                 mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
1154             }
1155         }
1156 
1157         @Override // binder call
1158         public void onError(long deviceId, int error, int vendorCode) {
1159             if (mExecutor != null) {
1160                 // BiometricPrompt case
1161                 if (error == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
1162                         || error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
1163                     // User tapped somewhere to cancel, or authentication was cancelled by the app
1164                     // or got kicked out. The prompt is already gone, so send the error immediately.
1165                     mExecutor.execute(() -> {
1166                         sendErrorResult(deviceId, error, vendorCode);
1167                     });
1168                 } else {
1169                     // User got an error that needs to be displayed on the dialog, post a delayed
1170                     // runnable on the FingerprintManager handler that sends the error message after
1171                     // FingerprintDialog.HIDE_DIALOG_DELAY to send the error to the application.
1172                     mHandler.postDelayed(() -> {
1173                         mExecutor.execute(() -> {
1174                             sendErrorResult(deviceId, error, vendorCode);
1175                         });
1176                     }, BiometricPrompt.HIDE_DIALOG_DELAY);
1177                 }
1178             } else {
1179                 mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
1180             }
1181         }
1182 
1183         @Override // binder call
1184         public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
1185             mHandler.obtainMessage(MSG_REMOVED, remaining, 0,
1186                     new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
1187         }
1188 
1189         @Override // binder call
1190         public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
1191             // TODO: propagate remaining
1192             mHandler.obtainMessage(MSG_ENUMERATED, fingerId, groupId, deviceId).sendToTarget();
1193         }
1194     };
1195 
1196 }
1197