All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.hardware.biometrics.BiometricPrompt Maven / Gradle / Ivy

/*
 * Copyright (C) 2018 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.biometrics;

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.hardware.biometrics.BiometricManager.Authenticators;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.identity.IdentityCredential;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
import java.util.List;
import java.util.concurrent.Executor;

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

/**
 * A class that manages a system-provided biometric dialog.
 */
public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {

    private static final String TAG = "BiometricPrompt";

    /**
     * Error/help message will show for this amount of time.
     * For error messages, the dialog will also be dismissed after this amount of time.
     * Error messages will be propagated back to the application via AuthenticationCallback
     * after this amount of time.
     * @hide
     */
    public static final int HIDE_DIALOG_DELAY = 2000; // ms

    /**
     * @hide
     */
    public static final int DISMISSED_REASON_BIOMETRIC_CONFIRMED = 1;

    /**
     * Dialog is done animating away after user clicked on the button set via
     * {@link BiometricPrompt.Builder#setNegativeButton(CharSequence, Executor,
     * DialogInterface.OnClickListener)}.
     * @hide
     */
    public static final int DISMISSED_REASON_NEGATIVE = 2;

    /**
     * @hide
     */
    public static final int DISMISSED_REASON_USER_CANCEL = 3;

    /**
     * Authenticated, confirmation not required. Dialog animated away.
     * @hide
     */
    public static final int DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED = 4;

    /**
     * Error message shown on SystemUI. When BiometricService receives this, the UI is already
     * gone.
     * @hide
     */
    public static final int DISMISSED_REASON_ERROR = 5;

    /**
     * Dialog dismissal requested by BiometricService.
     * @hide
     */
    public static final int DISMISSED_REASON_SERVER_REQUESTED = 6;

    /**
     * @hide
     */
    public static final int DISMISSED_REASON_CREDENTIAL_CONFIRMED = 7;

    /**
     * @hide
     */
    @IntDef({DISMISSED_REASON_BIOMETRIC_CONFIRMED,
            DISMISSED_REASON_NEGATIVE,
            DISMISSED_REASON_USER_CANCEL,
            DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
            DISMISSED_REASON_ERROR,
            DISMISSED_REASON_SERVER_REQUESTED,
            DISMISSED_REASON_CREDENTIAL_CONFIRMED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface DismissedReason {}

    private static class ButtonInfo {
        Executor executor;
        DialogInterface.OnClickListener listener;
        ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
            executor = ex;
            listener = l;
        }
    }

    /**
     * A builder that collects arguments to be shown on the system-provided biometric dialog.
     */
    public static class Builder {
        private PromptInfo mPromptInfo;
        private ButtonInfo mNegativeButtonInfo;
        private Context mContext;

        /**
         * Creates a builder for a {@link BiometricPrompt} dialog.
         * @param context The {@link Context} that will be used to build the prompt.
         */
        public Builder(Context context) {
            mPromptInfo = new PromptInfo();
            mContext = context;
        }

        /**
         * Required: Sets the title that will be shown on the prompt.
         * @param title The title to display.
         * @return This builder.
         */
        @NonNull
        public Builder setTitle(@NonNull CharSequence title) {
            mPromptInfo.setTitle(title);
            return this;
        }

        /**
         * Shows a default, modality-specific title for the prompt if the title would otherwise be
         * null or empty. Currently for internal use only.
         * @return This builder.
         * @hide
         */
        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
        @NonNull
        public Builder setUseDefaultTitle() {
            mPromptInfo.setUseDefaultTitle(true);
            return this;
        }

        /**
         * Optional: Sets a subtitle that will be shown on the prompt.
         * @param subtitle The subtitle to display.
         * @return This builder.
         */
        @NonNull
        public Builder setSubtitle(@NonNull CharSequence subtitle) {
            mPromptInfo.setSubtitle(subtitle);
            return this;
        }

        /**
         * Optional: Sets a description that will be shown on the prompt.
         * @param description The description to display.
         * @return This builder.
         */
        @NonNull
        public Builder setDescription(@NonNull CharSequence description) {
            mPromptInfo.setDescription(description);
            return this;
        }

        /**
         * Sets an optional title, subtitle, and/or description that will override other text when
         * the user is authenticating with PIN/pattern/password. Currently for internal use only.
         * @return This builder.
         * @hide
         */
        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
        @NonNull
        public Builder setTextForDeviceCredential(
                @Nullable CharSequence title,
                @Nullable CharSequence subtitle,
                @Nullable CharSequence description) {
            if (title != null) {
                mPromptInfo.setDeviceCredentialTitle(title);
            }
            if (subtitle != null) {
                mPromptInfo.setDeviceCredentialSubtitle(subtitle);
            }
            if (description != null) {
                mPromptInfo.setDeviceCredentialDescription(description);
            }
            return this;
        }

        /**
         * Required: Sets the text, executor, and click listener for the negative button on the
         * prompt. This is typically a cancel button, but may be also used to show an alternative
         * method for authentication, such as a screen that asks for a backup password.
         *
         * 

Note that this setting is not required, and in fact is explicitly disallowed, if * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or * {@link #setDeviceCredentialAllowed(boolean)}. * * @param text Text to be shown on the negative button for the prompt. * @param executor Executor that will be used to run the on click callback. * @param listener Listener containing a callback to be run when the button is pressed. * @return This builder. */ @NonNull public Builder setNegativeButton(@NonNull CharSequence text, @NonNull @CallbackExecutor Executor executor, @NonNull DialogInterface.OnClickListener listener) { if (TextUtils.isEmpty(text)) { throw new IllegalArgumentException("Text must be set and non-empty"); } if (executor == null) { throw new IllegalArgumentException("Executor must not be null"); } if (listener == null) { throw new IllegalArgumentException("Listener must not be null"); } mPromptInfo.setNegativeButtonText(text); mNegativeButtonInfo = new ButtonInfo(executor, listener); return this; } /** * Optional: Sets a hint to the system for whether to require user confirmation after * authentication. For example, implicit modalities like face and iris are passive, meaning * they don't require an explicit user action to complete authentication. If set to true, * these modalities should require the user to take some action (e.g. press a button) * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is * called. Defaults to true. * *

A typical use case for not requiring confirmation would be for low-risk transactions, * such as re-authenticating a recently authenticated application. A typical use case for * requiring confirmation would be for authorizing a purchase. * *

Note that this just passes a hint to the system, which the system may then ignore. For * example, a value of false may be ignored if the user has disabled implicit authentication * in Settings, or if it does not apply to a particular modality (e.g. fingerprint). * * @param requireConfirmation true if explicit user confirmation should be required, or * false otherwise. * @return This builder. */ @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) { mPromptInfo.setConfirmationRequested(requireConfirmation); return this; } /** * Optional: If enabled, the user will be given the option to authenticate with their device * PIN, pattern, or password. Developers should first check {@link * BiometricManager#canAuthenticate(int)} for {@link Authenticators#DEVICE_CREDENTIAL} * before enabling. If the device is not secured with a credential, * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} will be invoked * with {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}. Defaults to false. * *

Note that enabling this option replaces the negative button on the prompt with one * that allows the user to authenticate with their device credential, making it an error to * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * * @param allowed true if the prompt should fall back to asking for the user's device * credential (PIN/pattern/password), or false otherwise. * @return This builder. * * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}. */ @Deprecated @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) { mPromptInfo.setDeviceCredentialAllowed(allowed); return this; } /** * Optional: Specifies the type(s) of authenticators that may be invoked by * {@link BiometricPrompt} to authenticate the user. Available authenticator types are * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to: *

    *
  • {@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or
  • *
  • {@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.
  • *
* *

If this method is used and no authenticator of any of the specified types is available * at the time BiometricPrompt#authenticate(...) is called, authentication will * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} * will be invoked with an appropriate error code. * *

This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and * overrides the latter if both are used. Using this method to enable device credential * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative * button on the prompt, making it an error to also call * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * *

If unlocking cryptographic operation(s), it is the application's responsibility to * request authentication with the proper set of authenticators (e.g. match the * authenticators specified during key generation). * * @see android.security.keystore.KeyGenParameterSpec.Builder * @see KeyProperties#AUTH_BIOMETRIC_STRONG * @see KeyProperties#AUTH_DEVICE_CREDENTIAL * * @param authenticators A bit field representing all valid authenticator types that may be * invoked by the prompt. * @return This builder. */ @NonNull public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) { mPromptInfo.setAuthenticators(authenticators); return this; } /** * If non-empty, requests authentication to be performed only if the sensor is contained * within the list. Note that the actual sensor presented to the user/test will meet all * constraints specified within this builder. For example, on a device with the below * configuration: * * SensorId: 1, Strength: BIOMETRIC_STRONG * SensorId: 2, Strength: BIOMETRIC_WEAK * * If authentication is invoked with setAllowedAuthenticators(BIOMETRIC_STRONG) and * setAllowedSensorIds(2), then no sensor will be eligible for authentication. * * @see {@link BiometricManager#getSensorProperties()} * * @param sensorIds Sensor IDs to constrain this authentication to. * @return This builder * @hide */ @TestApi @NonNull @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) public Builder setAllowedSensorIds(@NonNull List sensorIds) { mPromptInfo.setAllowedSensorIds(sensorIds); return this; } /** * @param allow If true, allows authentication when the calling package is not in the * foreground. This is set to false by default. * @return This builder * @hide */ @TestApi @NonNull @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) public Builder setAllowBackgroundAuthentication(boolean allow) { mPromptInfo.setAllowBackgroundAuthentication(allow); return this; } /** * If set check the Device Policy Manager for disabled biometrics. * * @param checkDevicePolicyManager * @return This builder. * @hide */ @NonNull public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) { mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager); return this; } /** * If set, receive internal events via {@link AuthenticationCallback#onSystemEvent(int)} * @param set * @return This builder. * @hide */ @NonNull public Builder setReceiveSystemEvents(boolean set) { mPromptInfo.setReceiveSystemEvents(set); return this; } /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. * * @throws IllegalArgumentException If any required fields are unset, or if given any * invalid combination of field values. */ @NonNull public BiometricPrompt build() { final CharSequence title = mPromptInfo.getTitle(); final CharSequence negative = mPromptInfo.getNegativeButtonText(); final boolean useDefaultTitle = mPromptInfo.isUseDefaultTitle(); final boolean deviceCredentialAllowed = mPromptInfo.isDeviceCredentialAllowed(); final @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators(); final boolean willShowDeviceCredentialButton = deviceCredentialAllowed || isCredentialAllowed(authenticators); if (TextUtils.isEmpty(title) && !useDefaultTitle) { throw new IllegalArgumentException("Title must be set and non-empty"); } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) { throw new IllegalArgumentException("Negative text must be set and non-empty"); } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) { throw new IllegalArgumentException("Can't have both negative button behavior" + " and device credential enabled"); } return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo); } } private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener { @Override public void onCancel() { cancelAuthentication(); } } private final IBinder mToken = new Binder(); private final Context mContext; private final IAuthService mService; private final PromptInfo mPromptInfo; private final ButtonInfo mNegativeButtonInfo; private CryptoObject mCryptoObject; private Executor mExecutor; private AuthenticationCallback mAuthenticationCallback; private final IBiometricServiceReceiver mBiometricServiceReceiver = new IBiometricServiceReceiver.Stub() { @Override public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) { mExecutor.execute(() -> { final AuthenticationResult result = new AuthenticationResult(mCryptoObject, authenticationType); mAuthenticationCallback.onAuthenticationSucceeded(result); }); } @Override public void onAuthenticationFailed() { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationFailed(); }); } @Override public void onError(@BiometricAuthenticator.Modality int modality, int error, int vendorCode) { String errorMessage = null; switch (modality) { case TYPE_FACE: errorMessage = FaceManager.getErrorString(mContext, error, vendorCode); break; case TYPE_FINGERPRINT: errorMessage = FingerprintManager.getErrorString(mContext, error, vendorCode); break; } // Look for generic errors, as it may be a combination of modalities, or no modality // (e.g. attempted biometric authentication without biometric sensors). if (errorMessage == null) { switch (error) { case BIOMETRIC_ERROR_CANCELED: errorMessage = mContext.getString(R.string.biometric_error_canceled); break; case BIOMETRIC_ERROR_USER_CANCELED: errorMessage = mContext.getString(R.string.biometric_error_user_canceled); break; case BIOMETRIC_ERROR_HW_NOT_PRESENT: errorMessage = mContext.getString(R.string.biometric_error_hw_unavailable); break; case BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL: errorMessage = mContext.getString( R.string.biometric_error_device_not_secured); break; default: Log.e(TAG, "Unknown error, modality: " + modality + " error: " + error + " vendorCode: " + vendorCode); errorMessage = mContext.getString(R.string.biometric_error_generic); break; } } final String stringToSend = errorMessage; mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationError(error, stringToSend); }); } @Override public void onAcquired(int acquireInfo, String message) { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message); }); } @Override public void onDialogDismissed(int reason) { // Check the reason and invoke OnClickListener(s) if necessary if (reason == DISMISSED_REASON_NEGATIVE) { mNegativeButtonInfo.executor.execute(() -> { mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); }); } else { Log.e(TAG, "Unknown reason: " + reason); } } @Override public void onSystemEvent(int event) { mExecutor.execute(() -> { mAuthenticationCallback.onSystemEvent(event); }); } }; private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) { mContext = context; mPromptInfo = promptInfo; mNegativeButtonInfo = negativeButtonInfo; mService = IAuthService.Stub.asInterface( ServiceManager.getService(Context.AUTH_SERVICE)); } /** * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. * @return The title of the prompt, which is guaranteed to be non-null. */ @NonNull public CharSequence getTitle() { return mPromptInfo.getTitle(); } /** * Whether to use a default modality-specific title. For internal use only. * @return See {@link Builder#setUseDefaultTitle()}. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public boolean shouldUseDefaultTitle() { return mPromptInfo.isUseDefaultTitle(); } /** * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}. * @return The subtitle for the prompt, or null if the prompt has no subtitle. */ @Nullable public CharSequence getSubtitle() { return mPromptInfo.getSubtitle(); } /** * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}. * @return The description for the prompt, or null if the prompt has no description. */ @Nullable public CharSequence getDescription() { return mPromptInfo.getDescription(); } /** * Gets the negative button text for the prompt, as set by * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * @return The negative button text for the prompt, or null if no negative button text was set. */ @Nullable public CharSequence getNegativeButtonText() { return mPromptInfo.getNegativeButtonText(); } /** * Determines if explicit user confirmation is required by the prompt, as set by * {@link Builder#setConfirmationRequired(boolean)}. * * @return true if explicit user confirmation is required, or false otherwise. */ public boolean isConfirmationRequired() { return mPromptInfo.isConfirmationRequested(); } /** * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the * user, as set by {@link Builder#setAllowedAuthenticators(int)}. * * @return A bit field representing the type(s) of authenticators that may be invoked by the * prompt (as defined by {@link Authenticators}), or 0 if this field was not set. */ @Nullable public int getAllowedAuthenticators() { return mPromptInfo.getAuthenticators(); } /** * @return The values set by {@link Builder#setAllowedSensorIds(List)} * @hide */ @TestApi @NonNull public List getAllowedSensorIds() { return mPromptInfo.getAllowedSensorIds(); } /** * @return The value set by {@link Builder#setAllowBackgroundAuthentication(boolean)} * @hide */ @TestApi public boolean isAllowBackgroundAuthentication() { return mPromptInfo.isAllowBackgroundAuthentication(); } /** * A wrapper class for the cryptographic operations supported by BiometricPrompt. * *

Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and * {@link IdentityCredential}. * *

Cryptographic operations in Android can be split into two categories: auth-per-use and * time-based. This is specified during key creation via the timeout parameter of the * {@code setUserAuthenticationParameters(int, int)} method of {@link * android.security.keystore.KeyGenParameterSpec.Builder}. * *

CryptoObjects are used to unlock auth-per-use keys via * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor, * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard). * If a time-based key is not available for use (i.e. none of the allowed authenticators have * been unlocked recently), applications can prompt the user to authenticate via * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} * * @see Builder#setAllowedAuthenticators(int) */ 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); } public CryptoObject(@NonNull IdentityCredential credential) { super(credential); } /** * 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. */ public @Nullable IdentityCredential getIdentityCredential() { return super.getIdentityCredential(); } } /** * Authentication type reported by {@link AuthenticationResult} when the user authenticated by * entering their device PIN, pattern, or password. */ public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; /** * Authentication type reported by {@link AuthenticationResult} when the user authenticated by * presenting some form of biometric (e.g. fingerprint or face). */ public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; /** * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}. * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC}) public @interface AuthenticationResultType { } /** * Container for callback data from {@link #authenticate(CancellationSignal, Executor, * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, * AuthenticationCallback)}. */ public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult { /** * Authentication result * @param crypto * @param authenticationType * @hide */ public AuthenticationResult(CryptoObject crypto, @AuthenticationResultType int authenticationType) { // Identifier and userId is not used for BiometricPrompt. super(crypto, authenticationType, null /* identifier */, 0 /* userId */); } /** * Provides the crypto object associated with this transaction. * @return The crypto object provided to {@link #authenticate(CryptoObject, * CancellationSignal, Executor, AuthenticationCallback)} */ public CryptoObject getCryptoObject() { return (CryptoObject) super.getCryptoObject(); } /** * Provides the type of authentication (e.g. device credential or biometric) that was * requested from and successfully provided by the user. * * @return An integer value representing the authentication method used. */ public @AuthenticationResultType int getAuthenticationType() { return super.getAuthenticationType(); } } /** * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal, * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject, * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation * of this for listening to authentication events. */ public abstract static class AuthenticationCallback extends BiometricAuthenticator.AuthenticationCallback { /** * Called when an unrecoverable error has been encountered and the operation is complete. * No further actions 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 on an 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 on an UI */ @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) {} /** * Called when a biometric is recognized. * @param result An object containing authentication-related data */ public void onAuthenticationSucceeded(AuthenticationResult result) {} /** * Called when a biometric is valid but not recognized. */ @Override public void onAuthenticationFailed() {} /** * Called when a biometric has been acquired, but hasn't been processed yet. * @hide */ @Override public void onAuthenticationAcquired(int acquireInfo) {} /** * Receiver for internal system events. See {@link Builder#setReceiveSystemEvents(boolean)} * @hide */ public void onSystemEvent(int event) {} } /** * Authenticates for the given user. * * @param cancel An object that can be used to cancel authentication * @param executor An executor to handle callback events * @param callback An object to receive authentication events * @param userId The user to authenticate * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void authenticateUser(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId) { authenticateUserForOperation(cancel, executor, callback, userId, 0 /* operationId */); } /** * Authenticates for the given user and keystore operation. * * @param cancel An object that can be used to cancel authentication * @param executor An executor to handle callback events * @param callback An object to receive authentication events * @param userId The user to authenticate * @param operationId The keystore operation associated with authentication * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void authenticateUserForOperation( @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId, long operationId) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } if (executor == null) { throw new IllegalArgumentException("Must supply an executor"); } if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } authenticateInternal(operationId, cancel, executor, callback, userId); } /** * This call warms up the biometric hardware, displays a system-provided dialog, and starts * scanning for a biometric. It terminates when {@link * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This * operation can be canceled by using the provided cancel object. The application will receive * authentication errors through {@link AuthenticationCallback}, and button events through the * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor, * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object, * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor, * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the * previous client and start a new authentication. The interrupted client will receive a * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int, * CharSequence)}. * *

Note: Applications generally should not cancel and start authentication in quick * succession. For example, to properly handle authentication across configuration changes, it's * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, * the application will not need to cancel/restart authentication during the configuration * change. * *

Per the Android CDD, only biometric authenticators that meet or exceed the requirements * for Strong are permitted to integrate with Keystore to perform related * cryptographic operations. Therefore, it is an error to call this method after explicitly * calling {@link Builder#setAllowedAuthenticators(int)} with any biometric strength other than * {@link Authenticators#BIOMETRIC_STRONG}. * * @throws IllegalArgumentException If any argument is null, or if the allowed biometric * authenticator strength is explicitly set to {@link Authenticators#BIOMETRIC_WEAK}. Prior to * {@link android.os.Build.VERSION_CODES#R}, this exception is also thrown if * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true. * * @param crypto A cryptographic operation to be unlocked after successful authentication. * @param cancel An object that can be used to cancel authentication. * @param executor An executor to handle callback events. * @param callback An object to receive authentication events. */ @RequiresPermission(USE_BIOMETRIC) public void authenticate(@NonNull CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED, true /* isCrypto */, mPromptInfo.isConfirmationRequested(), mPromptInfo.isDeviceCredentialAllowed(), mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET, mPromptInfo.getAuthenticators()); if (crypto == null) { throw new IllegalArgumentException("Must supply a crypto object"); } if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } if (executor == null) { throw new IllegalArgumentException("Must supply an executor"); } if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } // Disallow explicitly setting any non-Strong biometric authenticator types. @Authenticators.Types int authenticators = mPromptInfo.getAuthenticators(); if (authenticators == Authenticators.EMPTY_SET) { authenticators = Authenticators.BIOMETRIC_STRONG; } final int biometricStrength = authenticators & Authenticators.BIOMETRIC_WEAK; if ((biometricStrength & ~Authenticators.BIOMETRIC_STRONG) != 0) { throw new IllegalArgumentException("Only Strong biometrics supported with crypto"); } authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); } /** * This call warms up the biometric hardware, displays a system-provided dialog, and starts * scanning for a biometric. It terminates when {@link * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when * the user dismisses the system-provided dialog. This operation can be canceled by using the * provided cancel object. The application will receive authentication errors through {@link * AuthenticationCallback}, and button events through the corresponding callback set in {@link * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is * safe to reuse the {@link BiometricPrompt} object, and calling {@link * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while * an existing authentication attempt is occurring will stop the previous client and start a new * authentication. The interrupted client will receive a cancelled notification through {@link * AuthenticationCallback#onAuthenticationError(int, CharSequence)}. * *

Note: Applications generally should not cancel and start authentication in quick * succession. For example, to properly handle authentication across configuration changes, it's * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, * the application will not need to cancel/restart authentication during the configuration * change. * * @throws IllegalArgumentException If any of the arguments are null. * * @param cancel An object that can be used to cancel authentication. * @param executor An executor to handle callback events. * @param callback An object to receive authentication events. */ @RequiresPermission(USE_BIOMETRIC) public void authenticate(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_PROMPT_AUTHENTICATE_INVOKED, false /* isCrypto */, mPromptInfo.isConfirmationRequested(), mPromptInfo.isDeviceCredentialAllowed(), mPromptInfo.getAuthenticators() != Authenticators.EMPTY_SET, mPromptInfo.getAuthenticators()); if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } if (executor == null) { throw new IllegalArgumentException("Must supply an executor"); } if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); } private void cancelAuthentication() { if (mService != null) { try { mService.cancelAuthentication(mToken, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Unable to cancel authentication", e); } } } private void authenticateInternal( @Nullable CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId) { mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0L; authenticateInternal(operationId, cancel, executor, callback, userId); } private void authenticateInternal( long operationId, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, int userId) { // Ensure we don't return the wrong crypto object as an auth result. if (mCryptoObject != null && mCryptoObject.getOpId() != operationId) { Log.w(TAG, "CryptoObject operation ID does not match argument; setting field to null"); mCryptoObject = null; } try { if (cancel.isCanceled()) { Log.w(TAG, "Authentication already canceled"); return; } else { cancel.setOnCancelListener(new OnAuthenticationCancelListener()); } mExecutor = executor; mAuthenticationCallback = callback; final PromptInfo promptInfo; if (operationId != 0L) { // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth. // Note that we use a new PromptInfo here so as to not overwrite the application's // preference, since it is possible that the same prompt configuration be used // without a crypto object later. Parcel parcel = Parcel.obtain(); mPromptInfo.writeToParcel(parcel, 0 /* flags */); parcel.setDataPosition(0); promptInfo = new PromptInfo(parcel); if (promptInfo.getAuthenticators() == Authenticators.EMPTY_SET) { promptInfo.setAuthenticators(Authenticators.BIOMETRIC_STRONG); } } else { promptInfo = mPromptInfo; } mService.authenticate(mToken, operationId, userId, mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo); } catch (RemoteException e) { Log.e(TAG, "Remote exception while authenticating", e); mExecutor.execute(() -> callback.onAuthenticationError( BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, mContext.getString(R.string.biometric_error_hw_unavailable))); } } private static boolean isCredentialAllowed(@Authenticators.Types int allowedAuthenticators) { return (allowedAuthenticators & Authenticators.DEVICE_CREDENTIAL) != 0; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy