Home | History | Annotate | Download | only in fingerprint
      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
     92         public void onCancel() {
     93             cancelEnrollment();
     94         }
     95     }
     96 
     97     private class OnAuthenticationCancelListener implements OnCancelListener {
     98         private android.hardware.biometrics.CryptoObject mCrypto;
     99 
    100         public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
    101             mCrypto = crypto;
    102         }
    103 
    104         @Override
    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 {
    117         public CryptoObject(@NonNull Signature signature) {
    118             super(signature);
    119         }
    120 
    121         public CryptoObject(@NonNull Cipher cipher) {
    122             super(cipher);
    123         }
    124 
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    191         public Fingerprint getFingerprint() { return mFingerprint; }
    192 
    193         /**
    194          * Obtain the userId for which this fingerprint was authenticated.
    195          * @hide
    196          */
    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
    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
    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          */
    234         public void onAuthenticationSucceeded(AuthenticationResult result) { }
    235 
    236         /**
    237          * Called when a fingerprint is valid but not recognized.
    238          */
    239         @Override
    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
    249         public void onAuthenticationAcquired(int acquireInfo) {}
    250 
    251         /**
    252          * @hide
    253          * @param result
    254          */
    255         @Override
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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})
    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      */
    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})
    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      */
    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      */
    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      */
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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})
    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)
    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      */
    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      */
    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      */
    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 {
    874         private MyHandler(Context context) {
    875             super(context.getMainLooper());
    876         }
    877 
    878         private MyHandler(Looper looper) {
    879             super(looper);
    880         }
    881 
    882         @Override
    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 
    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 
    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 
    943         private void sendEnrollResult(Fingerprint fp, int remaining) {
    944             if (mEnrollmentCallback != null) {
    945                 mEnrollmentCallback.onEnrollmentProgress(remaining);
    946             }
    947         }
    948     };
    949 
    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 
    958     private void sendAuthenticatedFailed() {
    959         if (mAuthenticationCallback != null) {
    960             mAuthenticationCallback.onAuthenticationFailed();
    961         }
    962     }
    963 
    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 
    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      */
   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 
   1013     private int getCurrentUserId() {
   1014         try {
   1015             return ActivityManager.getService().getCurrentUser().id;
   1016         } catch (RemoteException e) {
   1017             throw e.rethrowFromSystemServer();
   1018         }
   1019     }
   1020 
   1021     private void cancelEnrollment() {
   1022         if (mService != null) try {
   1023             mService.cancelEnrollment(mToken);
   1024         } catch (RemoteException e) {
   1025             throw e.rethrowFromSystemServer();
   1026         }
   1027     }
   1028 
   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      */
   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      */
   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