android.hardware.fingerprint.FingerprintManager.java Source code

Java tutorial

Introduction

Here is the source code for android.hardware.fingerprint.FingerprintManager.java

Source

/**
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.fingerprint;

import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_FINGERPRINT;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricPromptReceiver;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;

import java.security.Signature;
import java.util.List;
import java.util.concurrent.Executor;

import javax.crypto.Cipher;
import javax.crypto.Mac;

/**
 * A class that coordinates access to the fingerprint hardware.
 * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
 * authentication. In a world where devices may have different types of biometric authentication,
 * it's much more realistic to have a system-provided authentication dialog since the method may
 * vary by vendor/device.
 */
@Deprecated
@SystemService(Context.FINGERPRINT_SERVICE)
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricFingerprintConstants {
    private static final String TAG = "FingerprintManager";
    private static final boolean DEBUG = true;
    private static final int MSG_ENROLL_RESULT = 100;
    private static final int MSG_ACQUIRED = 101;
    private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
    private static final int MSG_AUTHENTICATION_FAILED = 103;
    private static final int MSG_ERROR = 104;
    private static final int MSG_REMOVED = 105;
    private static final int MSG_ENUMERATED = 106;

    private IFingerprintService mService;
    private Context mContext;
    private IBinder mToken = new Binder();
    private BiometricAuthenticator.AuthenticationCallback mAuthenticationCallback;
    private EnrollmentCallback mEnrollmentCallback;
    private RemovalCallback mRemovalCallback;
    private EnumerateCallback mEnumerateCallback;
    private android.hardware.biometrics.CryptoObject mCryptoObject;
    private Fingerprint mRemovalFingerprint;
    private Handler mHandler;
    private Executor mExecutor;

    private class OnEnrollCancelListener implements OnCancelListener {
        @Override
        public void onCancel() {
            cancelEnrollment();
        }
    }

    private class OnAuthenticationCancelListener implements OnCancelListener {
        private android.hardware.biometrics.CryptoObject mCrypto;

        public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
            mCrypto = crypto;
        }

        @Override
        public void onCancel() {
            cancelAuthentication(mCrypto);
        }
    }

    /**
     * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
     * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
     */
    @Deprecated
    public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
        public CryptoObject(@NonNull Signature signature) {
            super(signature);
        }

        public CryptoObject(@NonNull Cipher cipher) {
            super(cipher);
        }

        public CryptoObject(@NonNull Mac mac) {
            super(mac);
        }

        /**
         * Get {@link Signature} object.
         * @return {@link Signature} object or null if this doesn't contain one.
         */
        public Signature getSignature() {
            return super.getSignature();
        }

        /**
         * Get {@link Cipher} object.
         * @return {@link Cipher} object or null if this doesn't contain one.
         */
        public Cipher getCipher() {
            return super.getCipher();
        }

        /**
         * Get {@link Mac} object.
         * @return {@link Mac} object or null if this doesn't contain one.
         */
        public Mac getMac() {
            return super.getMac();
        }
    }

    /**
     * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
     *     CancellationSignal, int, AuthenticationCallback, Handler)}.
     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
     */
    @Deprecated
    public static class AuthenticationResult {
        private Fingerprint mFingerprint;
        private CryptoObject mCryptoObject;
        private int mUserId;

        /**
         * Authentication result
         *
         * @param crypto the crypto object
         * @param fingerprint the recognized fingerprint data, if allowed.
         * @hide
         */
        public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
            mCryptoObject = crypto;
            mFingerprint = fingerprint;
            mUserId = userId;
        }

        /**
         * Obtain the crypto object associated with this transaction
         * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject,
         *     CancellationSignal, int, AuthenticationCallback, Handler)}.
         */
        public CryptoObject getCryptoObject() {
            return mCryptoObject;
        }

        /**
         * Obtain the Fingerprint associated with this operation. Applications are strongly
         * discouraged from associating specific fingers with specific applications or operations.
         *
         * @hide
         */
        @UnsupportedAppUsage
        public Fingerprint getFingerprint() {
            return mFingerprint;
        }

        /**
         * Obtain the userId for which this fingerprint was authenticated.
         * @hide
         */
        public int getUserId() {
            return mUserId;
        }
    };

    /**
     * Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject,
     * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
     * FingerprintManager#authenticate(CryptoObject, CancellationSignal,
     * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
     * fingerprint events.
     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
     */
    @Deprecated
    public static abstract class AuthenticationCallback extends BiometricAuthenticator.AuthenticationCallback {
        /**
         * Called when an unrecoverable error has been encountered and the operation is complete.
         * No further callbacks will be made on this object.
         * @param errorCode An integer identifying the error message
         * @param errString A human-readable error string that can be shown in UI
         */
        @Override
        public void onAuthenticationError(int errorCode, CharSequence errString) {
        }

        /**
         * Called when a recoverable error has been encountered during authentication. The help
         * string is provided to give the user guidance for what went wrong, such as
         * "Sensor dirty, please clean it."
         * @param helpCode An integer identifying the error message
         * @param helpString A human-readable string that can be shown in UI
         */
        @Override
        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
        }

        /**
         * Called when a fingerprint is recognized.
         * @param result An object containing authentication-related data
         */
        public void onAuthenticationSucceeded(AuthenticationResult result) {
        }

        /**
         * Called when a fingerprint is valid but not recognized.
         */
        @Override
        public void onAuthenticationFailed() {
        }

        /**
         * Called when a fingerprint image has been acquired, but wasn't processed yet.
         *
         * @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants
         * @hide
         */
        @Override
        public void onAuthenticationAcquired(int acquireInfo) {
        }

        /**
         * @hide
         * @param result
         */
        @Override
        public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) {
            onAuthenticationSucceeded(new AuthenticationResult((CryptoObject) result.getCryptoObject(),
                    (Fingerprint) result.getId(), result.getUserId()));
        }
    };

    /**
     * Callback structure provided to {@link FingerprintManager#enroll(long, EnrollmentCallback,
     * CancellationSignal, int). Users of {@link #FingerprintManager()}
     * must provide an implementation of this to {@link FingerprintManager#enroll(long,
     * CancellationSignal, int, EnrollmentCallback) for listening to fingerprint events.
     *
     * @hide
     */
    public static abstract class EnrollmentCallback {
        /**
         * Called when an unrecoverable error has been encountered and the operation is complete.
         * No further callbacks will be made on this object.
         * @param errMsgId An integer identifying the error message
         * @param errString A human-readable error string that can be shown in UI
         */
        public void onEnrollmentError(int errMsgId, CharSequence errString) {
        }

        /**
         * Called when a recoverable error has been encountered during enrollment. The help
         * string is provided to give the user guidance for what went wrong, such as
         * "Sensor dirty, please clean it" or what they need to do next, such as
         * "Touch sensor again."
         * @param helpMsgId An integer identifying the error message
         * @param helpString A human-readable string that can be shown in UI
         */
        public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
        }

        /**
         * Called as each enrollment step progresses. Enrollment is considered complete when
         * remaining reaches 0. This function will not be called if enrollment fails. See
         * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
         * @param remaining The number of remaining steps
         */
        public void onEnrollmentProgress(int remaining) {
        }
    };

    /**
     * Callback structure provided to {@link #remove}. Users of {@link FingerprintManager} may
     * optionally provide an implementation of this to
     * {@link #remove(Fingerprint, int, RemovalCallback)} for listening to fingerprint template
     * removal events.
     *
     * @hide
     */
    public static abstract class RemovalCallback {
        /**
         * Called when the given fingerprint can't be removed.
         * @param fp The fingerprint that the call attempted to remove
         * @param errMsgId An associated error message id
         * @param errString An error message indicating why the fingerprint id can't be removed
         */
        public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
        }

        /**
         * Called when a given fingerprint is successfully removed.
         * @param fp The fingerprint template that was removed.
         * @param remaining The number of fingerprints yet to be removed in this operation. If
         *         {@link #remove} is called on one fingerprint, this should be 0. If
         *         {@link #remove} is called on a group, this should be the number of remaining
         *         fingerprints in the group, and 0 after the last fingerprint is removed.
         */
        public void onRemovalSucceeded(Fingerprint fp, int remaining) {
        }
    };

    /**
     * Callback structure provided to {@link FingerprintManager#enumerate(int). Users of
     * {@link #FingerprintManager()} may optionally provide an implementation of this to
     * {@link FingerprintManager#enumerate(int, int, EnumerateCallback)} for listening to
     * fingerprint template removal events.
     *
     * @hide
     */
    public static abstract class EnumerateCallback {
        /**
         * Called when the given fingerprint can't be removed.
         * @param errMsgId An associated error message id
         * @param errString An error message indicating why the fingerprint id can't be removed
         */
        public void onEnumerateError(int errMsgId, CharSequence errString) {
        }

        /**
         * Called when a given fingerprint is successfully removed.
         * @param fingerprint the fingerprint template that was removed.
         */
        public void onEnumerate(Fingerprint fingerprint) {
        }
    };

    /**
     * @hide
     */
    public static abstract class LockoutResetCallback {

        /**
         * Called when lockout period expired and clients are allowed to listen for fingerprint
         * again.
         */
        public void onLockoutReset() {
        }
    };

    /**
     * Request authentication of a crypto object. This call warms up the fingerprint hardware
     * and starts scanning for a fingerprint. It terminates when
     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
     * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
     * which point the object is no longer valid. The operation can be canceled by using the
     * provided cancel object.
     *
     * @param crypto object associated with the call or null if none required.
     * @param cancel an object that can be used to cancel authentication
     * @param flags optional flags; should be 0
     * @param callback an object to receive authentication events
     * @param handler an optional handler to handle callback events
     *
     * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
     *         by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
     *         facility</a>.
     * @throws IllegalStateException if the crypto primitive is not initialized.
     * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
     * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
     * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
     * BiometricPrompt.AuthenticationCallback)}
     */
    @Deprecated
    @RequiresPermission(anyOf = { USE_BIOMETRIC, USE_FINGERPRINT })
    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags,
            @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
        authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId());
    }

    /**
     * Use the provided handler thread for events.
     * @param handler
     */
    private void useHandler(Handler handler) {
        if (handler != null) {
            mHandler = new MyHandler(handler.getLooper());
        } else if (mHandler.getLooper() != mContext.getMainLooper()) {
            mHandler = new MyHandler(mContext.getMainLooper());
        }
    }

    /**
     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
     * CancellationSignal, int, AuthenticationCallback, Handler)}
     * @param userId the user ID that the fingerprint hardware will authenticate for.
     * @hide
     */
    @RequiresPermission(anyOf = { USE_BIOMETRIC, USE_FINGERPRINT })
    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags,
            @NonNull AuthenticationCallback callback, Handler handler, int userId) {
        if (callback == null) {
            throw new IllegalArgumentException("Must supply an authentication callback");
        }

        if (cancel != null) {
            if (cancel.isCanceled()) {
                Slog.w(TAG, "authentication already canceled");
                return;
            } else {
                cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
            }
        }

        if (mService != null)
            try {
                useHandler(handler);
                mAuthenticationCallback = callback;
                mCryptoObject = crypto;
                long sessionId = crypto != null ? crypto.getOpId() : 0;
                mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags,
                        mContext.getOpPackageName(), null /* bundle */, null /* receiver */);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception while authenticating: ", e);
                if (callback != null) {
                    // Though this may not be a hardware issue, it will cause apps to give up or try
                    // again later.
                    callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                }
            }
    }

    /**
     * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject,
     * CancellationSignal, Bundle, Executor, IBiometricPromptReceiver, AuthenticationCallback)}
     * @param userId the user ID that the fingerprint hardware will authenticate for.
     */
    private void 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) {
        mCryptoObject = crypto;
        if (cancel.isCanceled()) {
            Slog.w(TAG, "authentication already canceled");
            return;
        } else {
            cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
        }

        if (mService != null) {
            try {
                mExecutor = executor;
                mAuthenticationCallback = callback;
                final long sessionId = crypto != null ? crypto.getOpId() : 0;
                mService.authenticate(mToken, sessionId, userId, mServiceReceiver, 0 /* flags */,
                        mContext.getOpPackageName(), bundle, receiver);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception while authenticating", e);
                mExecutor.execute(() -> {
                    callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                });
            }
        }
    }

    /**
     * Private method, see {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
     * BiometricPrompt.AuthenticationCallback)}
     * @param cancel
     * @param executor
     * @param callback
     * @hide
     */
    public void authenticate(@NonNull CancellationSignal cancel, @NonNull Bundle bundle,
            @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
        if (cancel == null) {
            throw new IllegalArgumentException("Must supply a cancellation signal");
        }
        if (bundle == null) {
            throw new IllegalArgumentException("Must supply a bundle");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must supply an executor");
        }
        if (receiver == null) {
            throw new IllegalArgumentException("Must supply a receiver");
        }
        if (callback == null) {
            throw new IllegalArgumentException("Must supply a calback");
        }
        authenticate(mContext.getUserId(), null, cancel, bundle, executor, receiver, callback);
    }

    /**
     * Private method, see {@link BiometricPrompt#authenticate(BiometricPrompt.CryptoObject,
     * CancellationSignal, Executor, BiometricPrompt.AuthenticationCallback)}
     * @param crypto
     * @param cancel
     * @param executor
     * @param callback
     * @hide
     */
    public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto,
            @NonNull CancellationSignal cancel, @NonNull Bundle bundle,
            @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver,
            @NonNull BiometricAuthenticator.AuthenticationCallback callback) {
        if (crypto == null) {
            throw new IllegalArgumentException("Must supply a crypto object");
        }
        if (cancel == null) {
            throw new IllegalArgumentException("Must supply a cancellation signal");
        }
        if (bundle == null) {
            throw new IllegalArgumentException("Must supply a bundle");
        }
        if (executor == null) {
            throw new IllegalArgumentException("Must supply an executor");
        }
        if (receiver == null) {
            throw new IllegalArgumentException("Must supply a receiver");
        }
        if (callback == null) {
            throw new IllegalArgumentException("Must supply a callback");
        }
        authenticate(mContext.getUserId(), crypto, cancel, bundle, executor, receiver, callback);
    }

    /**
     * Request fingerprint enrollment. This call warms up the fingerprint hardware
     * and starts scanning for fingerprints. Progress will be indicated by callbacks to the
     * {@link EnrollmentCallback} object. It terminates when
     * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
     * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
     * which point the object is no longer valid. The operation can be canceled by using the
     * provided cancel object.
     * @param token a unique token provided by a recent creation or verification of device
     * credentials (e.g. pin, pattern or password).
     * @param cancel an object that can be used to cancel enrollment
     * @param flags optional flags
     * @param userId the user to whom this fingerprint will belong to
     * @param callback an object to receive enrollment events
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public void enroll(byte[] token, CancellationSignal cancel, int flags, int userId,
            EnrollmentCallback callback) {
        if (userId == UserHandle.USER_CURRENT) {
            userId = getCurrentUserId();
        }
        if (callback == null) {
            throw new IllegalArgumentException("Must supply an enrollment callback");
        }

        if (cancel != null) {
            if (cancel.isCanceled()) {
                Slog.w(TAG, "enrollment already canceled");
                return;
            } else {
                cancel.setOnCancelListener(new OnEnrollCancelListener());
            }
        }

        if (mService != null)
            try {
                mEnrollmentCallback = callback;
                mService.enroll(mToken, token, userId, mServiceReceiver, flags, mContext.getOpPackageName());
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception in enroll: ", e);
                if (callback != null) {
                    // Though this may not be a hardware issue, it will cause apps to give up or try
                    // again later.
                    callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                }
            }
    }

    /**
     * Requests a pre-enrollment auth token to tie enrollment to the confirmation of
     * existing device credentials (e.g. pin/pattern/password).
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public long preEnroll() {
        long result = 0;
        if (mService != null)
            try {
                result = mService.preEnroll(mToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        return result;
    }

    /**
     * Finishes enrollment and cancels the current auth token.
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public int postEnroll() {
        int result = 0;
        if (mService != null)
            try {
                result = mService.postEnroll(mToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        return result;
    }

    /**
     * Sets the active user. This is meant to be used to select the current profile for enrollment
     * to allow separate enrolled fingers for a work profile
     * @param userId
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public void setActiveUser(int userId) {
        if (mService != null)
            try {
                mService.setActiveUser(userId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
    }

    /**
     * Remove given fingerprint template from fingerprint hardware and/or protected storage.
     * @param fp the fingerprint item to remove
     * @param userId the user who this fingerprint belongs to
     * @param callback an optional callback to verify that fingerprint templates have been
     * successfully removed. May be null of no callback is required.
     *
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
        if (mService != null)
            try {
                mRemovalCallback = callback;
                mRemovalFingerprint = fp;
                mService.remove(mToken, fp.getFingerId(), fp.getGroupId(), userId, mServiceReceiver);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception in remove: ", e);
                if (callback != null) {
                    callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                }
            }
    }

    /**
     * Enumerate all fingerprint templates stored in hardware and/or protected storage.
     * @param userId the user who this fingerprint belongs to
     * @param callback an optional callback to verify that fingerprint templates have been
     * successfully removed. May be null of no callback is required.
     *
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public void enumerate(int userId, @NonNull EnumerateCallback callback) {
        if (mService != null)
            try {
                mEnumerateCallback = callback;
                mService.enumerate(mToken, userId, mServiceReceiver);
            } catch (RemoteException e) {
                Slog.w(TAG, "Remote exception in enumerate: ", e);
                if (callback != null) {
                    callback.onEnumerateError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
                            getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */));
                }
            }
    }

    /**
     * Renames the given fingerprint template
     * @param fpId the fingerprint id
     * @param userId the user who this fingerprint belongs to
     * @param newName the new name
     *
     * @hide
     */
    @RequiresPermission(MANAGE_FINGERPRINT)
    public void rename(int fpId, int userId, String newName) {
        // Renames the given fpId
        if (mService != null) {
            try {
                mService.rename(fpId, userId, newName);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            Slog.w(TAG, "rename(): Service not connected!");
        }
    }

    /**
     * Obtain the list of enrolled fingerprints templates.
     * @return list of current fingerprint items
     *
     * @hide
     */
    @RequiresPermission(USE_FINGERPRINT)
    @UnsupportedAppUsage
    public List<Fingerprint> getEnrolledFingerprints(int userId) {
        if (mService != null)
            try {
                return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        return null;
    }

    /**
     * Obtain the list of enrolled fingerprints templates.
     * @return list of current fingerprint items
     *
     * @hide
     */
    @RequiresPermission(USE_FINGERPRINT)
    @UnsupportedAppUsage
    public List<Fingerprint> getEnrolledFingerprints() {
        return getEnrolledFingerprints(mContext.getUserId());
    }

    /**
     * Determine if there is at least one fingerprint enrolled.
     *
     * @return true if at least one fingerprint is enrolled, false otherwise
     * @deprecated See {@link BiometricPrompt} and
     * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
     */
    @Deprecated
    @RequiresPermission(USE_FINGERPRINT)
    public boolean hasEnrolledFingerprints() {
        if (mService != null)
            try {
                return mService.hasEnrolledFingerprints(mContext.getUserId(), mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        return false;
    }

    /**
     * @hide
     */
    @RequiresPermission(allOf = { USE_FINGERPRINT, INTERACT_ACROSS_USERS })
    public boolean hasEnrolledFingerprints(int userId) {
        if (mService != null)
            try {
                return mService.hasEnrolledFingerprints(userId, mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        return false;
    }

    /**
     * Determine if fingerprint hardware is present and functional.
     *
     * @return true if hardware is present and functional, false otherwise.
     * @deprecated See {@link BiometricPrompt} and
     * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
     */
    @Deprecated
    @RequiresPermission(USE_FINGERPRINT)
    public boolean isHardwareDetected() {
        if (mService != null) {
            try {
                long deviceId = 0; /* TODO: plumb hardware id to FPMS */
                return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            Slog.w(TAG, "isFingerprintHardwareDetected(): Service not connected!");
        }
        return false;
    }

    /**
     * Retrieves the authenticator token for binding keys to the lifecycle
     * of the calling user's fingerprints. Used only by internal clients.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public long getAuthenticatorId() {
        if (mService != null) {
            try {
                return mService.getAuthenticatorId(mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            Slog.w(TAG, "getAuthenticatorId(): Service not connected!");
        }
        return 0;
    }

    /**
     * Reset the lockout timer when asked to do so by keyguard.
     *
     * @param token an opaque token returned by password confirmation.
     *
     * @hide
     */
    public void resetTimeout(byte[] token) {
        if (mService != null) {
            try {
                mService.resetTimeout(token);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            Slog.w(TAG, "resetTimeout(): Service not connected!");
        }
    }

    /**
     * @hide
     */
    public void addLockoutResetCallback(final LockoutResetCallback callback) {
        if (mService != null) {
            try {
                final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
                mService.addLockoutResetCallback(new IFingerprintServiceLockoutResetCallback.Stub() {

                    @Override
                    public void onLockoutReset(long deviceId, IRemoteCallback serverCallback)
                            throws RemoteException {
                        try {
                            final PowerManager.WakeLock wakeLock = powerManager
                                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback");
                            wakeLock.acquire();
                            mHandler.post(() -> {
                                try {
                                    callback.onLockoutReset();
                                } finally {
                                    wakeLock.release();
                                }
                            });
                        } finally {
                            serverCallback.sendResult(null /* data */);
                        }
                    }
                });
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } else {
            Slog.w(TAG, "addLockoutResetCallback(): Service not connected!");
        }
    }

    private class MyHandler extends Handler {
        private MyHandler(Context context) {
            super(context.getMainLooper());
        }

        private MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_ENROLL_RESULT:
                sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
                break;
            case MSG_ACQUIRED:
                sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
                        msg.arg2 /* vendorCode */);
                break;
            case MSG_AUTHENTICATION_SUCCEEDED:
                sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
                break;
            case MSG_AUTHENTICATION_FAILED:
                sendAuthenticatedFailed();
                break;
            case MSG_ERROR:
                sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
                break;
            case MSG_REMOVED:
                sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
                break;
            case MSG_ENUMERATED:
                sendEnumeratedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */,
                        msg.arg2 /* groupId */);
                break;
            }
        }

        private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
            if (mRemovalCallback == null) {
                return;
            }
            if (fingerprint == null) {
                Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
                return;
            }

            int fingerId = fingerprint.getFingerId();
            int reqFingerId = mRemovalFingerprint.getFingerId();
            if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
                Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
                return;
            }
            int groupId = fingerprint.getGroupId();
            int reqGroupId = mRemovalFingerprint.getGroupId();
            if (groupId != reqGroupId) {
                Slog.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId);
                return;
            }

            mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
        }

        private void sendEnumeratedResult(long deviceId, int fingerId, int groupId) {
            if (mEnumerateCallback != null) {
                mEnumerateCallback.onEnumerate(new Fingerprint(null, groupId, fingerId, deviceId));
            }
        }

        private void sendEnrollResult(Fingerprint fp, int remaining) {
            if (mEnrollmentCallback != null) {
                mEnrollmentCallback.onEnrollmentProgress(remaining);
            }
        }
    };

    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
        if (mAuthenticationCallback != null) {
            final BiometricAuthenticator.AuthenticationResult result = new BiometricAuthenticator.AuthenticationResult(
                    mCryptoObject, fp, userId);
            mAuthenticationCallback.onAuthenticationSucceeded(result);
        }
    }

    private void sendAuthenticatedFailed() {
        if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationFailed();
        }
    }

    private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) {
        if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
        }
        final String msg = getAcquiredString(acquireInfo, vendorCode);
        if (msg == null) {
            return;
        }
        // emulate HAL 2.1 behavior and send real acquiredInfo
        final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
                ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE)
                : acquireInfo;
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
        } else if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
        }
    }

    private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) {
        // emulate HAL 2.1 behavior and send real errMsgId
        final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
                ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE)
                : errMsgId;
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentError(clientErrMsgId, getErrorString(errMsgId, vendorCode));
        } else if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationError(clientErrMsgId, getErrorString(errMsgId, vendorCode));
        } else if (mRemovalCallback != null) {
            mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId,
                    getErrorString(errMsgId, vendorCode));
        } else if (mEnumerateCallback != null) {
            mEnumerateCallback.onEnumerateError(clientErrMsgId, getErrorString(errMsgId, vendorCode));
        }
    }

    /**
     * @hide
     */
    public FingerprintManager(Context context, IFingerprintService service) {
        mContext = context;
        mService = service;
        if (mService == null) {
            Slog.v(TAG, "FingerprintManagerService was null");
        }
        mHandler = new MyHandler(context);
    }

    private int getCurrentUserId() {
        try {
            return ActivityManager.getService().getCurrentUser().id;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private void cancelEnrollment() {
        if (mService != null)
            try {
                mService.cancelEnrollment(mToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
    }

    private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
        if (mService != null)
            try {
                mService.cancelAuthentication(mToken, mContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
    }

    /**
     * @hide
     */
    public String getErrorString(int errMsg, int vendorCode) {
        switch (errMsg) {
        case FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_unable_to_process);
        case FINGERPRINT_ERROR_HW_UNAVAILABLE:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_hw_not_available);
        case FINGERPRINT_ERROR_NO_SPACE:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_no_space);
        case FINGERPRINT_ERROR_TIMEOUT:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout);
        case FINGERPRINT_ERROR_CANCELED:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled);
        case FINGERPRINT_ERROR_LOCKOUT:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout);
        case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout_permanent);
        case FINGERPRINT_ERROR_USER_CANCELED:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_user_canceled);
        case FINGERPRINT_ERROR_NO_FINGERPRINTS:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_no_fingerprints);
        case FINGERPRINT_ERROR_HW_NOT_PRESENT:
            return mContext.getString(com.android.internal.R.string.fingerprint_error_hw_not_present);
        case FINGERPRINT_ERROR_VENDOR: {
            String[] msgArray = mContext.getResources()
                    .getStringArray(com.android.internal.R.array.fingerprint_error_vendor);
            if (vendorCode < msgArray.length) {
                return msgArray[vendorCode];
            }
        }
        }
        Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
        return null;
    }

    /**
     * @hide
     */
    public String getAcquiredString(int acquireInfo, int vendorCode) {
        switch (acquireInfo) {
        case FINGERPRINT_ACQUIRED_GOOD:
            return null;
        case FINGERPRINT_ACQUIRED_PARTIAL:
            return mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial);
        case FINGERPRINT_ACQUIRED_INSUFFICIENT:
            return mContext.getString(com.android.internal.R.string.fingerprint_acquired_insufficient);
        case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
            return mContext.getString(com.android.internal.R.string.fingerprint_acquired_imager_dirty);
        case FINGERPRINT_ACQUIRED_TOO_SLOW:
            return mContext.getString(com.android.internal.R.string.fingerprint_acquired_too_slow);
        case FINGERPRINT_ACQUIRED_TOO_FAST:
            return mContext.getString(com.android.internal.R.string.fingerprint_acquired_too_fast);
        case FINGERPRINT_ACQUIRED_VENDOR: {
            String[] msgArray = mContext.getResources()
                    .getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor);
            if (vendorCode < msgArray.length) {
                return msgArray[vendorCode];
            }
        }
        }
        Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
        return null;
    }

    private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {

        @Override // binder call
        public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
            mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
                    new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
        }

        @Override // binder call
        public void onAcquired(long deviceId, int acquireInfo, int vendorCode) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAcquiredResult(deviceId, acquireInfo, vendorCode);
                });
            } else {
                mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, deviceId).sendToTarget();
            }
        }

        @Override // binder call
        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAuthenticatedSucceeded(fp, userId);
                });
            } else {
                mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
            }
        }

        @Override // binder call
        public void onAuthenticationFailed(long deviceId) {
            if (mExecutor != null) {
                mExecutor.execute(() -> {
                    sendAuthenticatedFailed();
                });
            } else {
                mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
            }
        }

        @Override // binder call
        public void onError(long deviceId, int error, int vendorCode) {
            if (mExecutor != null) {
                // BiometricPrompt case
                if (error == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                        || error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
                    // User tapped somewhere to cancel, or authentication was cancelled by the app
                    // or got kicked out. The prompt is already gone, so send the error immediately.
                    mExecutor.execute(() -> {
                        sendErrorResult(deviceId, error, vendorCode);
                    });
                } else {
                    // User got an error that needs to be displayed on the dialog, post a delayed
                    // runnable on the FingerprintManager handler that sends the error message after
                    // FingerprintDialog.HIDE_DIALOG_DELAY to send the error to the application.
                    mHandler.postDelayed(() -> {
                        mExecutor.execute(() -> {
                            sendErrorResult(deviceId, error, vendorCode);
                        });
                    }, BiometricPrompt.HIDE_DIALOG_DELAY);
                }
            } else {
                mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget();
            }
        }

        @Override // binder call
        public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
            mHandler.obtainMessage(MSG_REMOVED, remaining, 0, new Fingerprint(null, groupId, fingerId, deviceId))
                    .sendToTarget();
        }

        @Override // binder call
        public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
            // TODO: propagate remaining
            mHandler.obtainMessage(MSG_ENUMERATED, fingerId, groupId, deviceId).sendToTarget();
        }
    };

}