/*
 * 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.USE_BIOMETRIC;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.TextUtils;

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

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

A class that manages a system-provided biometric dialog.
/** * A class that manages a system-provided biometric dialog. */
public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
@hide
/** * @hide */
public static final String KEY_TITLE = "title";
@hide
/** * @hide */
public static final String KEY_SUBTITLE = "subtitle";
@hide
/** * @hide */
public static final String KEY_DESCRIPTION = "description";
@hide
/** * @hide */
public static final String KEY_POSITIVE_TEXT = "positive_text";
@hide
/** * @hide */
public static final String KEY_NEGATIVE_TEXT = "negative_text";
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
/** * 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
/** * @hide */
public static final int DISMISSED_REASON_POSITIVE = 1;
@hide
/** * @hide */
public static final int DISMISSED_REASON_NEGATIVE = 2;
@hide
/** * @hide */
public static final int DISMISSED_REASON_USER_CANCEL = 3; 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.
/** * A builder that collects arguments to be shown on the system-provided biometric dialog. **/
public static class Builder { private final Bundle mBundle; private ButtonInfo mPositiveButtonInfo; private ButtonInfo mNegativeButtonInfo; private Context mContext;
Creates a builder for a biometric dialog.
Params:
  • context –
/** * Creates a builder for a biometric dialog. * @param context */
public Builder(Context context) { mBundle = new Bundle(); mContext = context; }
Required: Set the title to display.
Params:
  • title –
Returns:
/** * Required: Set the title to display. * @param title * @return */
public Builder setTitle(@NonNull CharSequence title) { mBundle.putCharSequence(KEY_TITLE, title); return this; }
Optional: Set the subtitle to display.
Params:
  • subtitle –
Returns:
/** * Optional: Set the subtitle to display. * @param subtitle * @return */
public Builder setSubtitle(@NonNull CharSequence subtitle) { mBundle.putCharSequence(KEY_SUBTITLE, subtitle); return this; }
Optional: Set the description to display.
Params:
  • description –
Returns:
/** * Optional: Set the description to display. * @param description * @return */
public Builder setDescription(@NonNull CharSequence description) { mBundle.putCharSequence(KEY_DESCRIPTION, description); return this; }
Optional: Set the text for the positive button. If not set, the positive button will not show.
Params:
  • text –
Returns:
@hide
/** * Optional: Set the text for the positive button. If not set, the positive button * will not show. * @param text * @return * @hide */
public Builder setPositiveButton(@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"); } mBundle.putCharSequence(KEY_POSITIVE_TEXT, text); mPositiveButtonInfo = new ButtonInfo(executor, listener); return this; }
Required: Set the text for the negative button. This would typically be used as a "Cancel" button, but may be also used to show an alternative method for authentication, such as screen that asks for a backup password.
Params:
  • text –
Returns:
/** * Required: Set the text for the negative button. This would typically be used as a * "Cancel" button, but may be also used to show an alternative method for authentication, * such as screen that asks for a backup password. * @param text * @return */
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"); } mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text); mNegativeButtonInfo = new ButtonInfo(executor, listener); return this; }
Creates a BiometricPrompt.
Throws:
Returns:a BiometricPrompt
/** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. */
public BiometricPrompt build() { final CharSequence title = mBundle.getCharSequence(KEY_TITLE); final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT); if (TextUtils.isEmpty(title)) { throw new IllegalArgumentException("Title must be set and non-empty"); } else if (TextUtils.isEmpty(negative)) { throw new IllegalArgumentException("Negative text must be set and non-empty"); } return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo); } } private PackageManager mPackageManager; private FingerprintManager mFingerprintManager; private Bundle mBundle; private ButtonInfo mPositiveButtonInfo; private ButtonInfo mNegativeButtonInfo; IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() { @Override public void onDialogDismissed(int reason) { // Check the reason and invoke OnClickListener(s) if necessary if (reason == DISMISSED_REASON_POSITIVE) { mPositiveButtonInfo.executor.execute(() -> { mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE); }); } else if (reason == DISMISSED_REASON_NEGATIVE) { mNegativeButtonInfo.executor.execute(() -> { mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE); }); } } }; private BiometricPrompt(Context context, Bundle bundle, ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) { mBundle = bundle; mPositiveButtonInfo = positiveButtonInfo; mNegativeButtonInfo = negativeButtonInfo; mFingerprintManager = context.getSystemService(FingerprintManager.class); mPackageManager = context.getPackageManager(); }
A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework supports Signature, Cipher and Mac objects.
/** * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework * supports {@link Signature}, {@link Cipher} and {@link Mac} objects. */
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 Signature object.
Returns:Signature object or null if this doesn't contain one.
/** * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. */
public Signature getSignature() { return super.getSignature(); }
Get Cipher object.
Returns:Cipher object or null if this doesn't contain one.
/** * Get {@link Cipher} object. * @return {@link Cipher} object or null if this doesn't contain one. */
public Cipher getCipher() { return super.getCipher(); }
Get Mac object.
Returns:Mac object or null if this doesn't contain one.
/** * 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 #authenticate( CancellationSignal, Executor, * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor, * AuthenticationCallback)} */ public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
Authentication result
Params:
  • crypto –
  • identifier –
  • userId –
@hide
/** * Authentication result * @param crypto * @param identifier * @param userId * @hide */
public AuthenticationResult(CryptoObject crypto, BiometricIdentifier identifier, int userId) { super(crypto, identifier, userId); }
Obtain the crypto object associated with this transaction
Returns:crypto object provided to BiometricPrompt.authenticate(CryptoObject, CancellationSignal, Executor, AuthenticationCallback)
/** * Obtain the crypto object associated with this transaction * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal, * Executor, AuthenticationCallback)} */
public CryptoObject getCryptoObject() { return (CryptoObject) super.getCryptoObject(); } }
Callback structure provided to BiometricPrompt.authenticate(CancellationSignal, Executor, AuthenticationCallback) or BiometricPrompt.authenticate(CryptoObject, CancellationSignal, Executor, AuthenticationCallback). Users must provide an implementation of this for listening to authentication events.
/** * 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.
Params:
  • errorCode – An integer identifying the error message
  • errString – A human-readable error string that can be shown on an UI
/** * 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."
Params:
  • helpCode – An integer identifying the error message
  • helpString – A human-readable string that can be shown on an UI
/** * 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.
Params:
  • result – An object containing authentication-related data
/** * 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.
/** * 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
/** * Called when a biometric has been acquired, but hasn't been processed yet. * @hide */
@Override public void onAuthenticationAcquired(int acquireInfo) {}
Params:
  • result – An object containing authentication-related data
@hide
/** * @param result An object containing authentication-related data * @hide */
@Override public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { onAuthenticationSucceeded(new AuthenticationResult( (CryptoObject) result.getCryptoObject(), result.getId(), result.getUserId())); } }
Params:
  • crypto – Object associated with the call
  • cancel – An object that can be used to cancel authentication
  • executor – An executor to handle callback events
  • callback – An object to receive authentication events
@hide
/** * @param crypto Object associated with the call * @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 * @hide */
@Override public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (!(callback instanceof BiometricPrompt.AuthenticationCallback)) { throw new IllegalArgumentException("Callback cannot be casted"); } authenticate(crypto, cancel, executor, (AuthenticationCallback) callback); }
Params:
  • cancel – An object that can be used to cancel authentication
  • executor – An executor to handle callback events
  • callback – An object to receive authentication events
@hide
/** * * @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 * @hide */
@Override public void authenticate(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull BiometricAuthenticator.AuthenticationCallback callback) { if (!(callback instanceof BiometricPrompt.AuthenticationCallback)) { throw new IllegalArgumentException("Callback cannot be casted"); } authenticate(cancel, executor, (AuthenticationCallback) callback); }
This call warms up the fingerprint hardware, displays a system-provided dialog, and starts scanning for a fingerprint. It terminates when AuthenticationCallback.onAuthenticationError(int, CharSequence) is called, when 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 AuthenticationCallback, and button events through the corresponding callback set in Builder.setNegativeButton(CharSequence, Executor, OnClickListener). It is safe to reuse the BiometricPrompt object, and calling 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 AuthenticationCallback.onAuthenticationError(int, CharSequence).
Params:
  • crypto – Object associated with the call
  • cancel – An object that can be used to cancel authentication
  • executor – An executor to handle callback events
  • callback – An object to receive authentication events
Throws:
/** * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts * scanning for a fingerprint. 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)}. * * @throws IllegalArgumentException If any of the arguments are null * * @param crypto Object associated with the call * @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) { if (handlePreAuthenticationErrors(callback, executor)) { return; } mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver, callback); }
This call warms up the fingerprint hardware, displays a system-provided dialog, and starts scanning for a fingerprint. It terminates when AuthenticationCallback.onAuthenticationError(int, CharSequence) is called, when 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 AuthenticationCallback, and button events through the corresponding callback set in Builder.setNegativeButton(CharSequence, Executor, OnClickListener). It is safe to reuse the BiometricPrompt object, and calling 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 AuthenticationCallback.onAuthenticationError(int, CharSequence).
Params:
  • cancel – An object that can be used to cancel authentication
  • executor – An executor to handle callback events
  • callback – An object to receive authentication events
Throws:
/** * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts * scanning for a fingerprint. 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)}. * * @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) { if (handlePreAuthenticationErrors(callback, executor)) { return; } mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback); } private boolean handlePreAuthenticationErrors(AuthenticationCallback callback, Executor executor) { if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { sendError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, callback, executor); return true; } else if (!mFingerprintManager.isHardwareDetected()) { sendError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE, callback, executor); return true; } else if (!mFingerprintManager.hasEnrolledFingerprints()) { sendError(BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS, callback, executor); return true; } return false; } private void sendError(int error, AuthenticationCallback callback, Executor executor) { executor.execute(() -> { callback.onAuthenticationError(error, mFingerprintManager.getErrorString( error, 0 /* vendorCode */)); }); } }