/* * 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.RESET_FINGERPRINT_LOCKOUT; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.Manifest.permission.USE_FINGERPRINT; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE; import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT; import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL; import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_HAS_ENROLLED_FINGERPRINTS; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_IS_HARDWARE_DETECTED; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.BiometricTestSession; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.fingerprint.PointerContext; import android.os.Binder; import android.os.Build; import android.os.CancellationSignal; import android.os.CancellationSignal.OnCancelListener; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.security.identity.IdentityCredential; import android.security.identity.PresentationSession; import android.util.Slog; import android.view.Surface; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.Signature; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; 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. */ @SuppressWarnings("deprecation") @Deprecated @SystemService(Context.FINGERPRINT_SERVICE) @RequiresFeature(PackageManager.FEATURE_FINGERPRINT) public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; /** * @hide */ public static final int ENROLL_FIND_SENSOR = 1; /** * @hide */ public static final int ENROLL_ENROLL = 2; /** * @hide */ @IntDef({ENROLL_FIND_SENSOR, ENROLL_ENROLL}) @Retention(RetentionPolicy.SOURCE) public @interface EnrollReason {} /** * Udfps ui event of overlay is shown on the screen. * @hide */ public static final int UDFPS_UI_OVERLAY_SHOWN = 1; /** * Udfps ui event of the udfps UI being ready (e.g. HBM illumination is enabled). * @hide */ public static final int UDFPS_UI_READY = 2; /** * @hide */ @IntDef({UDFPS_UI_OVERLAY_SHOWN, UDFPS_UI_READY}) @Retention(RetentionPolicy.SOURCE) public @interface UdfpsUiEvent{} /** * Request authentication with any single sensor. * @hide */ public static final int SENSOR_ID_ANY = -1; private final IFingerprintService mService; private final Context mContext; private final IBinder mToken = new Binder(); private Handler mHandler; @Nullable private float[] mEnrollStageThresholds; private List mProps = new ArrayList<>(); private HandlerExecutor mExecutor; /** * Retrieves a list of properties for all fingerprint sensors on the device. * @hide */ @TestApi @NonNull @RequiresPermission(TEST_BIOMETRIC) public List getSensorProperties() { final List properties = new ArrayList<>(); final List internalProperties = getSensorPropertiesInternal(); for (FingerprintSensorPropertiesInternal internalProp : internalProperties) { properties.add(FingerprintSensorProperties.from(internalProp)); } return properties; } /** * Retrieves a test session for FingerprintManager. * @hide */ @TestApi @NonNull @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { return new BiometricTestSession(mContext, sensorId, (context, sensorId1, callback) -> mService .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private class OnEnrollCancelListener implements OnCancelListener { private final long mAuthRequestId; private OnEnrollCancelListener(long id) { mAuthRequestId = id; } @Override public void onCancel() { Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId); cancelEnrollment(mAuthRequestId); } } private class OnAuthenticationCancelListener implements OnCancelListener { private final long mAuthRequestId; OnAuthenticationCancelListener(long id) { mAuthRequestId = id; } @Override public void onCancel() { Slog.d(TAG, "Cancel fingerprint authentication requested for: " + mAuthRequestId); cancelAuthentication(mAuthRequestId); } } private class OnFingerprintDetectionCancelListener implements OnCancelListener { private final long mAuthRequestId; OnFingerprintDetectionCancelListener(long id) { mAuthRequestId = id; } @Override public void onCancel() { Slog.d(TAG, "Cancel fingerprint detect requested for: " + mAuthRequestId); cancelFingerprintDetect(mAuthRequestId); } } /** * 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(); } /** * Get {@link IdentityCredential} object. * @return {@link IdentityCredential} object or null if this doesn't contain one. * @hide * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. */ @Deprecated public IdentityCredential getIdentityCredential() { return super.getIdentityCredential(); } /** * Get {@link PresentationSession} object. * @return {@link PresentationSession} object or null if this doesn't contain one. * @hide */ public PresentationSession getPresentationSession() { return super.getPresentationSession(); } /** * Get {@link KeyAgreement} object. * @return {@link KeyAgreement} object or null if this doesn't contain one. * @hide */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) public KeyAgreement getKeyAgreement() { return super.getKeyAgreement(); } } /** * 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; private boolean mIsStrongBiometric; /** * Authentication result * * @param crypto the crypto object * @param fingerprint the recognized fingerprint data, if allowed. * @hide */ public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId, boolean isStrongBiometric) { mCryptoObject = crypto; mFingerprint = fingerprint; mUserId = userId; mIsStrongBiometric = isStrongBiometric; } /** * 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; } /** * Check whether the strength of the fingerprint modality associated with this operation is * strong (i.e. not weak or convenience). * @hide */ public boolean isStrongBiometric() { return mIsStrongBiometric; } } /** * 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 abstract static 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) {} /** * Invoked for under-display fingerprint sensors when a touch has been detected on the * sensor area. * @hide */ public void onUdfpsPointerDown(int sensorId) {} /** * Invoked for under-display fingerprint sensors when a touch has been removed from the * sensor area. * @hide */ public void onUdfpsPointerUp(int sensorId) {} } /** * Callback structure provided for {@link #detectFingerprint(CancellationSignal, * FingerprintDetectionCallback, int, Surface)}. * @hide */ public interface FingerprintDetectionCallback { /** * Invoked when a fingerprint has been detected. */ void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric); /** * An error has occurred with fingerprint detection. * * This callback signifies that this operation has been completed, and * no more callbacks should be expected. */ default void onDetectionError(int errorMsgId) {} } /** * Callback structure provided to {@link FingerprintManager#enroll(byte[], CancellationSignal, * int, EnrollmentCallback)} must provide an implementation of this for listening to * fingerprint events. * * @hide */ public abstract static 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) { } /** * Called when a fingerprint image has been acquired. * @param isAcquiredGood whether the fingerprint image was good. */ public void onAcquired(boolean isAcquiredGood){ } /** * Called when a pointer down event has occurred. */ public void onUdfpsPointerDown(int sensorId){ } /** * Called when a pointer up event has occurred. */ public void onUdfpsPointerUp(int sensorId){ } /** * Called when udfps overlay is shown. */ public void onUdfpsOverlayShown() { } } /** * 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 abstract static 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(@Nullable Fingerprint fp, int remaining) { } } /** * @hide */ public abstract static class LockoutResetCallback { /** * Called when lockout period expired and clients are allowed to listen for fingerprint * again. */ public void onLockoutReset(int sensorId) { } } /** * Callbacks for generate challenge operations. * * @hide */ public interface GenerateChallengeCallback { /** Called when a challenged has been generated. */ void onChallengeGenerated(int sensorId, int userId, long challenge); } /** * Use the provided handler thread for events. * @param handler */ private void useHandler(Handler handler) { if (handler != null) { mHandler = handler; mExecutor = new HandlerExecutor(mHandler); } else if (mHandler != mContext.getMainThreadHandler()) { mHandler = mContext.getMainThreadHandler(); mExecutor = new HandlerExecutor(mHandler); } } /** * 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 Android Keystore * facility. * @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, callback, handler, SENSOR_ID_ANY, mContext.getUserId(), flags); } /** * Per-user version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. * @hide */ @Deprecated @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, Handler handler, int userId) { authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId, 0 /* flags */); } /** * Per-user and per-sensor version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. * @hide */ @Deprecated @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId, int flags) { authenticate(crypto, cancel, callback, handler, new FingerprintAuthenticateOptions.Builder() .setSensorId(sensorId) .setUserId(userId) .setIgnoreEnrollmentState(flags != 0) .build()); } /** * Version of authenticate with additional options. * @hide */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, @NonNull Handler handler, @NonNull FingerprintAuthenticateOptions options) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE, mContext.getApplicationInfo().uid, mContext.getApplicationInfo().targetSdkVersion); if (callback == null) { throw new IllegalArgumentException("Must supply an authentication callback"); } if (cancel != null && cancel.isCanceled()) { Slog.w(TAG, "authentication already canceled"); return; } options.setOpPackageName(mContext.getOpPackageName()); options.setAttributionTag(mContext.getAttributionTag()); if (mService != null) { try { final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, crypto); useHandler(handler); final long operationId = crypto != null ? crypto.getOpId() : 0; final long authId = mService.authenticate(mToken, operationId, new FingerprintServiceReceiver(fingerprintCallback), options); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } } catch (RemoteException e) { Slog.w(TAG, "Remote exception while authenticating: ", e); // 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(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); } } } /** * Uses the fingerprint hardware to detect for the presence of a finger, without giving details * about accept/reject/lockout. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void detectFingerprint(@NonNull CancellationSignal cancel, @NonNull FingerprintDetectionCallback callback, @NonNull FingerprintAuthenticateOptions options) { if (mService == null) { return; } if (cancel.isCanceled()) { Slog.w(TAG, "Detection already cancelled"); return; } options.setOpPackageName(mContext.getOpPackageName()); options.setAttributionTag(mContext.getAttributionTag()); final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); try { final long authId = mService.detectFingerprint(mToken, new FingerprintServiceReceiver(fingerprintCallback), options); cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId)); } catch (RemoteException e) { Slog.w(TAG, "Remote exception when requesting finger detect", e); } } /** * 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 userId the user to whom this fingerprint will belong to * @param callback an object to receive enrollment events * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics * should be logged. * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId, EnrollmentCallback callback, @EnrollReason int enrollReason, FingerprintEnrollOptions options) { if (userId == UserHandle.USER_CURRENT) { userId = getCurrentUserId(); } if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } if (cancel != null && cancel.isCanceled()) { Slog.w(TAG, "enrollment already canceled"); return; } if (hardwareAuthToken == null) { callback.onEnrollmentError(FINGERPRINT_ERROR_UNABLE_TO_PROCESS, getErrorString(mContext, FINGERPRINT_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */)); return; } if (mService != null) { try { final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId, new FingerprintServiceReceiver(fingerprintCallback), mContext.getOpPackageName(), enrollReason, options); if (cancel != null) { cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // 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(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); } } } /** * Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a * HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification. * The HardwareAuthenticationToken can then be sent to the biometric HAL together with a * request to perform sensitive operation(s) (for example enroll), represented by the challenge. * Doing this ensures that a the sensitive operation cannot be performed unless the user has * entered confirmed PIN/Pattern/Password. * * @see com.android.server.locksettings.LockSettingsService * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) { if (mService != null) try { final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback); mService.generateChallenge(mToken, sensorId, userId, new FingerprintServiceReceiver(fingerprintCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first * enumerated sensor. * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void generateChallenge(int userId, GenerateChallengeCallback callback) { final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor(); if (sensorProps == null) { Slog.e(TAG, "No sensors"); return; } generateChallenge(sensorProps.sensorId, userId, callback); } /** * Revokes the specified challenge. * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void revokeChallenge(int userId, long challenge) { if (mService != null) { try { final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor(); if (sensorProps == null) { Slog.e(TAG, "No sensors"); return; } mService.revokeChallenge(mToken, sensorProps.sensorId, userId, mContext.getOpPackageName(), challenge); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password) * * @param sensorId Sensor ID that this operation takes effect for * @param userId User ID that this operation takes effect for. * @param hardwareAuthToken An opaque token returned by password confirmation. * @hide */ @RequiresPermission(RESET_FINGERPRINT_LOCKOUT) public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) { if (mService != null) { try { mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken, mContext.getOpPackageName()); } 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 { final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, REMOVE_SINGLE, fp); mService.remove(mToken, fp.getBiometricId(), userId, new FingerprintServiceReceiver(fingerprintCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Removes all fingerprint templates for the given user. * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) public void removeAll(int userId, @NonNull RemovalCallback callback) { if (mService != null) { try { final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback, REMOVE_ALL, null); mService.removeAll(mToken, userId, new FingerprintServiceReceiver(fingerprintCallback), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public List getEnrolledFingerprints(int userId) { if (mService != null) try { return mService.getEnrolledFingerprints( userId, mContext.getOpPackageName(), mContext.getAttributionTag()); } 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 getEnrolledFingerprints() { return getEnrolledFingerprints(mContext.getUserId()); } /** * @hide */ public boolean hasEnrolledTemplates() { return hasEnrolledFingerprints(); } /** * @hide */ public boolean hasEnrolledTemplates(int userId) { return hasEnrolledFingerprints(userId); } /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { if (mService == null) { Slog.w(TAG, "setUdfpsOverlayController: no fingerprint service"); return; } try { mService.setUdfpsOverlayController(controller); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Forwards BiometricStateListener to FingerprintService * @param listener new BiometricStateListener being added * @hide */ public void registerBiometricStateListener(@NonNull BiometricStateListener listener) { try { mService.registerBiometricStateListener(listener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { if (mService == null) { Slog.w(TAG, "onPointerDown: no fingerprint service"); return; } final PointerContext pc = new PointerContext(); pc.x = (int) x; pc.y = (int) y; pc.minor = minor; pc.major = major; try { mService.onPointerDown(requestId, sensorId, pc); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp(long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onPointerUp: no fingerprint service"); return; } final PointerContext pc = new PointerContext(); try { mService.onPointerUp(requestId, sensorId, pc); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * TODO(b/218388821): The parameter list should be replaced with PointerContext. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerDown( long requestId, int sensorId, int pointerId, float x, float y, float minor, float major, float orientation, long time, long gestureStart, boolean isAod) { if (mService == null) { Slog.w(TAG, "onPointerDown: no fingerprint service"); return; } final PointerContext pc = new PointerContext(); pc.pointerId = pointerId; pc.x = x; pc.y = y; pc.minor = minor; pc.major = major; pc.orientation = orientation; pc.time = time; pc.gestureStart = gestureStart; pc.isAod = isAod; try { mService.onPointerDown(requestId, sensorId, pc); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * TODO(b/218388821): The parameter list should be replaced with PointerContext. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp( long requestId, int sensorId, int pointerId, float x, float y, float minor, float major, float orientation, long time, long gestureStart, boolean isAod) { if (mService == null) { Slog.w(TAG, "onPointerUp: no fingerprint service"); return; } final PointerContext pc = new PointerContext(); pc.pointerId = pointerId; pc.x = x; pc.y = y; pc.minor = minor; pc.major = major; pc.orientation = orientation; pc.time = time; pc.gestureStart = gestureStart; pc.isAod = isAod; try { mService.onPointerUp(requestId, sensorId, pc); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onUdfpsUiEvent(@UdfpsUiEvent int event, long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onUdfpsUiEvent: no fingerprint service"); return; } try { mService.onUdfpsUiEvent(event, requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * This is triggered by SideFpsEventHandler * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPowerPressed() { Slog.i(TAG, "onPowerPressed"); mExecutor.execute(() -> sendPowerPressed()); } /** * 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() { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_HAS_ENROLLED_FINGERPRINTS, mContext.getApplicationInfo().uid, mContext.getApplicationInfo().targetSdkVersion); return hasEnrolledFingerprints(UserHandle.myUserId()); } /** * @hide */ @RequiresPermission(allOf = { USE_FINGERPRINT, INTERACT_ACROSS_USERS}) public boolean hasEnrolledFingerprints(int userId) { if (mService != null) try { return mService.hasEnrolledFingerprintsDeprecated( userId, mContext.getOpPackageName(), mContext.getAttributionTag()); } 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() { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_IS_HARDWARE_DETECTED, mContext.getApplicationInfo().uid, mContext.getApplicationInfo().targetSdkVersion); if (mService != null) { try { return mService.isHardwareDetectedDeprecated( mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } else { Slog.w(TAG, "isFingerprintHardwareDetected(): Service not connected!"); } return false; } /** * Get statically configured sensor properties. * @deprecated Generally unsafe to use, use * {@link FingerprintManager#addAuthenticatorsRegisteredCallback} API instead. * In most cases this method will work as expected, but during early boot up, it will be * null/empty and there is no way for the caller to know when it's actual value is ready. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @NonNull public List getSensorPropertiesInternal() { try { if (!mProps.isEmpty() || mService == null) { return mProps; } return mService.getSensorPropertiesInternal(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns whether the device has a power button fingerprint sensor. * @return boolean indicating whether power button is fingerprint sensor * @hide */ public boolean isPowerbuttonFps() { final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor(); return sensorProps == null ? false : sensorProps.sensorType == TYPE_POWER_BUTTON; } /** * Adds a callback that gets called when the service registers all of the fingerprint * authenticators (HALs). * * If the fingerprint authenticators are already registered when the callback is added, the * callback is invoked immediately. * * The callback is automatically removed after it's invoked. * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void addAuthenticatorsRegisteredCallback( IFingerprintAuthenticatorsRegisteredCallback callback) { if (mService != null) { try { mService.addAuthenticatorsRegisteredCallback(callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } else { Slog.w(TAG, "addProvidersAvailableCallback(): Service not connected!"); } } /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @BiometricConstants.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) { if (mService != null) { try { return mService.getLockoutModeForUser(sensorId, userId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } return BIOMETRIC_LOCKOUT_NONE; } /** * Schedules a watchdog. * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void scheduleWatchdog() { try { mService.scheduleWatchdog(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @hide */ public void addLockoutResetCallback(final LockoutResetCallback callback) { if (mService != null) { try { final PowerManager powerManager = mContext.getSystemService(PowerManager.class); mService.addLockoutResetCallback( new IBiometricServiceLockoutResetCallback.Stub() { @Override public void onLockoutReset(int sensorId, IRemoteCallback serverCallback) throws RemoteException { try { final PowerManager.WakeLock wakeLock = powerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback"); wakeLock.acquire(); mHandler.post(() -> { try { callback.onLockoutReset(sensorId); } finally { wakeLock.release(); } }); } finally { serverCallback.sendResult(null /* data */); } } }, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } else { Slog.w(TAG, "addLockoutResetCallback(): Service not connected!"); } } private void sendPowerPressed() { try { mService.onPowerPressed(); } catch (RemoteException e) { Slog.e(TAG, "Error sending power press", e); } } /** * @hide */ public FingerprintManager(Context context, IFingerprintService service) { mContext = context; mService = service; if (mService == null) { Slog.v(TAG, "FingerprintService was null"); } if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) == PackageManager.PERMISSION_GRANTED) { addAuthenticatorsRegisteredCallback( new IFingerprintAuthenticatorsRegisteredCallback.Stub() { @Override public void onAllAuthenticatorsRegistered( @NonNull List sensors) { mProps = sensors; } }); } mHandler = context.getMainThreadHandler(); mExecutor = new HandlerExecutor(mHandler); } private int getCurrentUserId() { try { return ActivityManager.getService().getCurrentUser().id; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Nullable private FingerprintSensorPropertiesInternal getFirstFingerprintSensor() { final List allSensors = getSensorPropertiesInternal(); return allSensors.isEmpty() ? null : allSensors.get(0); } private void cancelEnrollment(long requestId) { if (mService != null) try { mService.cancelEnrollment(mToken, requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private void cancelAuthentication(long requestId) { if (mService != null) try { mService.cancelAuthentication( mToken, mContext.getOpPackageName(), mContext.getAttributionTag(), requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private void cancelFingerprintDetect(long requestId) { if (mService == null) { return; } try { mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName(), requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @hide */ public int getEnrollStageCount() { if (mEnrollStageThresholds == null) { mEnrollStageThresholds = createEnrollStageThresholds(mContext); } return mEnrollStageThresholds.length + 1; } /** * @hide */ public float getEnrollStageThreshold(int index) { if (mEnrollStageThresholds == null) { mEnrollStageThresholds = createEnrollStageThresholds(mContext); } if (index < 0 || index > mEnrollStageThresholds.length) { Slog.w(TAG, "Unsupported enroll stage index: " + index); return index < 0 ? 0f : 1f; } // The implicit threshold for the final stage is always 1. return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index]; } @NonNull @RequiresPermission(USE_BIOMETRIC_INTERNAL) private float[] createEnrollStageThresholds(@NonNull Context context) { // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config final String[] enrollStageThresholdStrings; if (isPowerbuttonFps()) { enrollStageThresholdStrings = context.getResources().getStringArray( com.android.internal.R.array.config_sfps_enroll_stage_thresholds); } else { enrollStageThresholdStrings = context.getResources().getStringArray( com.android.internal.R.array.config_udfps_enroll_stage_thresholds); } final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length]; for (int i = 0; i < enrollStageThresholds.length; i++) { enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]); } return enrollStageThresholds; } /** * @hide */ public static String getErrorString(Context context, int errMsg, int vendorCode) { switch (errMsg) { case FINGERPRINT_ERROR_HW_UNAVAILABLE: return context.getString( com.android.internal.R.string.fingerprint_error_hw_not_available); case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: return context.getString( com.android.internal.R.string.fingerprint_error_unable_to_process); case FINGERPRINT_ERROR_TIMEOUT: return context.getString(com.android.internal.R.string.fingerprint_error_timeout); case FINGERPRINT_ERROR_NO_SPACE: return context.getString( com.android.internal.R.string.fingerprint_error_no_space); case FINGERPRINT_ERROR_CANCELED: return context.getString(com.android.internal.R.string.fingerprint_error_canceled); case FINGERPRINT_ERROR_LOCKOUT: return context.getString(com.android.internal.R.string.fingerprint_error_lockout); case FINGERPRINT_ERROR_LOCKOUT_PERMANENT: return context.getString( com.android.internal.R.string.fingerprint_error_lockout_permanent); case FINGERPRINT_ERROR_USER_CANCELED: return context.getString( com.android.internal.R.string.fingerprint_error_user_canceled); case FINGERPRINT_ERROR_NO_FINGERPRINTS: return context.getString( com.android.internal.R.string.fingerprint_error_no_fingerprints); case FINGERPRINT_ERROR_HW_NOT_PRESENT: return context.getString( com.android.internal.R.string.fingerprint_error_hw_not_present); case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: return context.getString( com.android.internal.R.string.fingerprint_error_security_update_required); case FINGERPRINT_ERROR_BAD_CALIBRATION: return context.getString( com.android.internal.R.string.fingerprint_error_bad_calibration); case BIOMETRIC_ERROR_POWER_PRESSED: return context.getString( com.android.internal.R.string.fingerprint_error_power_pressed); case FINGERPRINT_ERROR_VENDOR: { String[] msgArray = context.getResources().getStringArray( com.android.internal.R.array.fingerprint_error_vendor); if (vendorCode < msgArray.length) { return msgArray[vendorCode]; } } } // This is used as a last resort in case a vendor string is missing // It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but // warn and use the default if all else fails. Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode); return context.getString( com.android.internal.R.string.fingerprint_error_vendor_unknown); } /** * @hide */ public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) { switch (acquireInfo) { case FINGERPRINT_ACQUIRED_GOOD: return null; case FINGERPRINT_ACQUIRED_PARTIAL: return context.getString( com.android.internal.R.string.fingerprint_acquired_partial); case FINGERPRINT_ACQUIRED_INSUFFICIENT: return context.getString( com.android.internal.R.string.fingerprint_acquired_insufficient); case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: return context.getString( com.android.internal.R.string.fingerprint_acquired_imager_dirty); case FINGERPRINT_ACQUIRED_TOO_SLOW: return context.getString( com.android.internal.R.string.fingerprint_acquired_too_slow); case FINGERPRINT_ACQUIRED_TOO_FAST: return context.getString( com.android.internal.R.string.fingerprint_acquired_too_fast); case FINGERPRINT_ACQUIRED_IMMOBILE: return context.getString( com.android.internal.R.string.fingerprint_acquired_immobile); case FINGERPRINT_ACQUIRED_TOO_BRIGHT: return context.getString( com.android.internal.R.string.fingerprint_acquired_too_bright); case FINGERPRINT_ACQUIRED_POWER_PRESSED: return context.getString( com.android.internal.R.string.fingerprint_acquired_power_press); case FINGERPRINT_ACQUIRED_VENDOR: { String[] msgArray = context.getResources().getStringArray( com.android.internal.R.array.fingerprint_acquired_vendor); if (vendorCode < msgArray.length) { return msgArray[vendorCode]; } } break; case FINGERPRINT_ACQUIRED_START: return null; } Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode); return null; } class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub { private final FingerprintCallback mFingerprintCallback; FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) { mFingerprintCallback = fingerprintCallback; } @Override // binder call public void onEnrollResult(Fingerprint fp, int remaining) { mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining)); } @Override // binder call public void onAcquired(int acquireInfo, int vendorCode) { mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo, vendorCode)); } @Override // binder call public void onAuthenticationSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) { mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId, isStrongBiometric)); } @Override public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId, isStrongBiometric)); } @Override // binder call public void onAuthenticationFailed() { mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed); } @Override // binder call public void onError(int error, int vendorCode) { mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error, vendorCode)); } @Override // binder call public void onRemoved(Fingerprint fp, int remaining) { mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining)); } @Override // binder call public void onChallengeGenerated(int sensorId, int userId, long challenge) { mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId, userId)); } @Override // binder call public void onUdfpsPointerDown(int sensorId) { mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId)); } @Override // binder call public void onUdfpsPointerUp(int sensorId) { mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId)); } @Override public void onUdfpsOverlayShown() { mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown); } } }