/*
* Copyright 2019 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 androidx.biometric;
import android.Manifest;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* A class that provides system information related to biometrics (e.g. fingerprint, face, etc.).
*
* <p>On devices running Android 10 (API 29) and above, this will query the framework's version of
* {@link android.hardware.biometrics.BiometricManager}. On Android 9.0 (API 28) and prior
* versions, this will query {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
*
* @see BiometricPrompt To prompt the user to authenticate with their biometric.
*/
@SuppressWarnings("deprecation")
public class BiometricManager {
private static final String TAG = "BiometricManager";
/**
* The user can successfully authenticate.
*/
public static final int BIOMETRIC_SUCCESS = 0;
/**
* Unable to determine whether the user can authenticate.
*
* <p>This status code may be returned on older Android versions due to partial incompatibility
* with a newer API. Applications that wish to enable biometric authentication on affected
* devices may still call {@code BiometricPrompt#authenticate()} after receiving this status
* code but should be prepared to handle possible errors.
*/
public static final int BIOMETRIC_STATUS_UNKNOWN = -1;
/**
* The user can't authenticate because the specified options are incompatible with the current
* Android version.
*/
public static final int BIOMETRIC_ERROR_UNSUPPORTED = -2;
/**
* The user can't authenticate because the hardware is unavailable. Try again later.
*/
public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1;
/**
* The user can't authenticate because no biometric or device credential is enrolled.
*/
public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11;
/**
* The user can't authenticate because there is no suitable hardware (e.g. no biometric sensor
* or no keyguard).
*/
public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12;
/**
* The user can't authenticate because a security vulnerability has been discovered with one or
* more hardware sensors. The affected sensor(s) are unavailable until a security update has
* addressed the issue.
*/
public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
/**
* A status code that may be returned when checking for biometric authentication.
*/
@IntDef({
BIOMETRIC_SUCCESS,
BIOMETRIC_STATUS_UNKNOWN,
BIOMETRIC_ERROR_UNSUPPORTED,
BIOMETRIC_ERROR_HW_UNAVAILABLE,
BIOMETRIC_ERROR_NONE_ENROLLED,
BIOMETRIC_ERROR_NO_HARDWARE,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticationStatus {}
/**
* Types of authenticators, defined at a level of granularity supported by
* {@link BiometricManager} and {@link BiometricPrompt}.
*
* <p>Types may combined via bitwise OR into a single integer representing multiple
* authenticators (e.g. {@code DEVICE_CREDENTIAL | BIOMETRIC_WEAK}).
*
* @see #canAuthenticate(int)
* @see BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int)
*/
public interface Authenticators {
/**
* Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
* requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>), as defined
* by the Android CDD.
*/
int BIOMETRIC_STRONG = 0x000F;
/**
* Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
* requirements for <strong>Class 2</strong> (formerly <strong>Weak</strong>), as defined by
* the Android CDD.
*
* <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that
* {@code BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK}.
*/
int BIOMETRIC_WEAK = 0x00FF;
/**
* The non-biometric credential used to secure the device (i.e. PIN, pattern, or password).
* This should typically only be used in combination with a biometric auth type, such as
* {@link #BIOMETRIC_WEAK}.
*/
int DEVICE_CREDENTIAL = 1 << 15;
}
/**
* A bitwise combination of authenticator types defined in {@link Authenticators}.
*/
@IntDef(flag = true, value = {
Authenticators.BIOMETRIC_STRONG,
Authenticators.BIOMETRIC_WEAK,
Authenticators.DEVICE_CREDENTIAL
})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorTypes {}
private static final int AUTH_MODALITY_NONE = 0;
private static final int AUTH_MODALITY_CREDENTIAL = 1;
private static final int AUTH_MODALITY_UNKNOWN_BIOMETRIC = 1 << 1;
private static final int AUTH_MODALITY_FINGERPRINT = 1 << 2;
private static final int AUTH_MODALITY_FACE = 1 << 3;
@IntDef(flag = true, value = {
AUTH_MODALITY_NONE,
AUTH_MODALITY_CREDENTIAL,
AUTH_MODALITY_UNKNOWN_BIOMETRIC,
AUTH_MODALITY_FINGERPRINT,
AUTH_MODALITY_FACE
})
@Retention(RetentionPolicy.SOURCE)
@interface AuthModalities {}
/**
* Provides localized strings for an application that uses {@link BiometricPrompt} to
* authenticate the user.
*/
public static class Strings {
/**
* The framework strings instance. Non-null on Android 12 (API 31) and above.
*/
@Nullable private final android.hardware.biometrics.BiometricManager.Strings mStrings;
/**
* The compatibility strings instance. Non-null on Android 11 (API 30) and below.
*/
@Nullable private final StringsCompat mStringsCompat;
@RequiresApi(Build.VERSION_CODES.S)
Strings(@NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
mStrings = strings;
mStringsCompat = null;
}
Strings(@NonNull StringsCompat stringsCompat) {
mStrings = null;
mStringsCompat = stringsCompat;
}
/**
* Provides a localized string that can be used as the label for a button that invokes
* {@link BiometricPrompt}.
*
* <p>When possible, this method should use the given authenticator requirements to more
* precisely specify the authentication type that will be used. For example, if
* <strong>Class 3</strong> biometric authentication is requested on a device with a
* <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
* the returned string should indicate that fingerprint authentication will be used.
*
* <p>This method should also try to specify which authentication method(s) will be used in
* practice when multiple authenticators meet the given requirements. For example, if
* biometric authentication is requested on a device with both face and fingerprint sensors
* but the user has selected face as their preferred method, the returned string should
* indicate that face authentication will be used.
*
* <p>This method may return {@code null} if none of the requested authenticator types are
* available, but this should <em>not</em> be relied upon for checking the status of
* authenticators. Instead, use {@link #canAuthenticate(int)}.
*
* @return The label for a button that invokes {@link BiometricPrompt} for authentication.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
public CharSequence getButtonLabel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
return Api31Impl.getButtonLabel(mStrings);
} else if (mStringsCompat != null) {
return mStringsCompat.getButtonLabel();
} else {
Log.e(TAG, "Failure in Strings.getButtonLabel(). No available string provider.");
return null;
}
}
/**
* Provides a localized string that can be shown while the user is authenticating with
* {@link BiometricPrompt}.
*
* <p>When possible, this method should use the given authenticator requirements to more
* precisely specify the authentication type that will be used. For example, if
* <strong>Class 3</strong> biometric authentication is requested on a device with a
* <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
* the returned string should indicate that fingerprint authentication will be used.
*
* <p>This method should also try to specify which authentication method(s) will be used in
* practice when multiple authenticators meet the given requirements. For example, if
* biometric authentication is requested on a device with both face and fingerprint sensors
* but the user has selected face as their preferred method, the returned string should
* indicate that face authentication will be used.
*
* <p>This method may return {@code null} if none of the requested authenticator types are
* available, but this should <em>not</em> be relied upon for checking the status of
* authenticators. Instead, use {@link #canAuthenticate(int)}.
*
* @return A message to be shown on {@link BiometricPrompt} during authentication.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
public CharSequence getPromptMessage() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
return Api31Impl.getPromptMessage(mStrings);
} else if (mStringsCompat != null) {
return mStringsCompat.getPromptMessage();
} else {
Log.e(TAG, "Failure in Strings.getPromptMessage(). No available string provider.");
return null;
}
}
/**
* Provides a localized string that can be shown as the title for an app setting that
* allows authentication with {@link BiometricPrompt}.
*
* <p>When possible, this method should use the given authenticator requirements to more
* precisely specify the authentication type that will be used. For example, if
* <strong>Class 3</strong> biometric authentication is requested on a device with a
* <strong>Class 3</strong> fingerprint sensor and a <strong>Class 2</strong> face sensor,
* the returned string should indicate that fingerprint authentication will be used.
*
* <p>This method should <em>not</em> try to specify which authentication method(s) will be
* used in practice when multiple authenticators meet the given requirements. For example,
* if biometric authentication is requested on a device with both face and fingerprint
* sensors, the returned string should indicate that either face or fingerprint
* authentication may be used, regardless of whether the user has enrolled or selected
* either as their preferred method.
*
* <p>This method may return {@code null} if none of the requested authenticator types are
* supported by the system, but this should <em>not</em> be relied upon for checking the
* status of authenticators. Instead, use {@link #canAuthenticate(int)} or
* {@link android.content.pm.PackageManager#hasSystemFeature(String)}.
*
* @return The name for a setting that allows authentication with {@link BiometricPrompt}.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
public CharSequence getSettingName() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mStrings != null) {
return Api31Impl.getSettingName(mStrings);
} else if (mStringsCompat != null) {
return mStringsCompat.getSettingName();
} else {
Log.e(TAG, "Failure in Strings.getSettingName(). No available string provider.");
return null;
}
}
}
/**
* Compatibility wrapper for the {@link Strings} class on Android 11 (API 30) and below.
*/
private class StringsCompat {
@NonNull private final Resources mResources;
@AuthenticatorTypes private final int mAuthenticators;
@AuthModalities private final int mPossibleModalities;
StringsCompat(
@NonNull Resources resources,
@AuthenticatorTypes int authenticators,
boolean isFingerprintSupported,
boolean isFaceSupported,
boolean isIrisSupported,
boolean isDeviceSecured) {
mResources = resources;
mAuthenticators = authenticators;
@AuthModalities int possibleModalities =
isDeviceSecured && AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)
? AUTH_MODALITY_CREDENTIAL
: AUTH_MODALITY_NONE;
if (AuthenticatorUtils.isSomeBiometricAllowed(authenticators)) {
if (isFingerprintSupported) {
possibleModalities |= AUTH_MODALITY_FINGERPRINT;
}
if (isFaceSupported) {
possibleModalities |= AUTH_MODALITY_FACE;
}
if (isIrisSupported) {
possibleModalities |= AUTH_MODALITY_UNKNOWN_BIOMETRIC;
}
}
mPossibleModalities = possibleModalities;
}
/**
* Provides a localized string that can be used as the label for a button that invokes
* {@link BiometricPrompt}.
*
* This is a backwards-compatible implementation of the {@link Strings#getButtonLabel()}
* method for Android 11 (API 30) and below.
*
* @return The label for a button that invokes {@link BiometricPrompt} for authentication.
*/
@Nullable
CharSequence getButtonLabel() {
@AuthenticatorTypes final int biometricAuthenticators =
AuthenticatorUtils.getBiometricAuthenticators(mAuthenticators);
if (canAuthenticate(biometricAuthenticators) == BIOMETRIC_SUCCESS) {
switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
case AUTH_MODALITY_FINGERPRINT:
// Fingerprint is the only supported and available biometric.
return mResources.getString(R.string.use_fingerprint_label);
case AUTH_MODALITY_FACE:
// Face is the only supported and available biometric.
return mResources.getString(R.string.use_face_label);
default:
// 1+ biometric types are supported and available.
return mResources.getString(R.string.use_biometric_label);
}
}
if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) != 0) {
// Only screen lock is supported and available.
return mResources.getString(R.string.use_screen_lock_label);
}
// Authentication is not supported or not available.
return null;
}
/**
* Provides a localized string that can be shown while the user is authenticating with
* {@link BiometricPrompt}.
*
* This is a backwards-compatible implementation of the {@link Strings#getPromptMessage()}
* method for Android 11 (API 30) and below.
*
* @return A message to be shown on {@link BiometricPrompt} during authentication.
*/
@Nullable
CharSequence getPromptMessage() {
@AuthenticatorTypes final int biometricAuthenticators =
AuthenticatorUtils.getBiometricAuthenticators(mAuthenticators);
if (canAuthenticate(biometricAuthenticators) == BIOMETRIC_SUCCESS) {
@StringRes final int messageRes;
switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
case AUTH_MODALITY_FINGERPRINT:
// Fingerprint is the only supported and available biometric.
messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
? R.string.fingerprint_or_screen_lock_prompt_message
: R.string.fingerprint_prompt_message;
break;
case AUTH_MODALITY_FACE:
// Face is the only supported and available biometric.
messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
? R.string.face_or_screen_lock_prompt_message
: R.string.face_prompt_message;
break;
default:
// 1+ biometric types are supported and available.
messageRes = AuthenticatorUtils.isDeviceCredentialAllowed(mAuthenticators)
? R.string.biometric_or_screen_lock_prompt_message
: R.string.biometric_prompt_message;
break;
}
return mResources.getString(messageRes);
}
if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) != 0) {
// Only screen lock is supported and available.
return mResources.getString(R.string.screen_lock_prompt_message);
}
// Authentication is not supported or not available.
return null;
}
/**
* Provides a localized string that can be shown as the title for an app setting that
* allows authentication with {@link BiometricPrompt}.
*
* This is a backwards-compatible implementation of the {@link Strings#getSettingName()}
* method for Android 11 (API 30) and below.
*
* @return The name for a setting that allows authentication with {@link BiometricPrompt}.
*/
@Nullable
CharSequence getSettingName() {
CharSequence settingName;
switch (mPossibleModalities) {
case AUTH_MODALITY_NONE:
// Authentication is not supported.
settingName = null;
break;
case AUTH_MODALITY_CREDENTIAL:
// Only screen lock is supported.
settingName = mResources.getString(R.string.use_screen_lock_label);
break;
case AUTH_MODALITY_UNKNOWN_BIOMETRIC:
// Only an unknown biometric type(s) is supported.
settingName = mResources.getString(R.string.use_biometric_label);
break;
case AUTH_MODALITY_FINGERPRINT:
// Only fingerprint is supported.
settingName = mResources.getString(R.string.use_fingerprint_label);
break;
case AUTH_MODALITY_FACE:
// Only face is supported.
settingName = mResources.getString(R.string.use_face_label);
break;
default:
if ((mPossibleModalities & AUTH_MODALITY_CREDENTIAL) == 0) {
// 2+ biometric types are supported (but not screen lock).
settingName = mResources.getString(R.string.use_biometric_label);
} else {
switch (mPossibleModalities & ~AUTH_MODALITY_CREDENTIAL) {
case AUTH_MODALITY_FINGERPRINT:
// Only fingerprint and screen lock are supported.
settingName = mResources.getString(
R.string.use_fingerprint_or_screen_lock_label);
break;
case AUTH_MODALITY_FACE:
// Only face and screen lock are supported.
settingName = mResources.getString(
R.string.use_face_or_screen_lock_label);
break;
default:
// 1+ biometric types and screen lock are supported.
settingName = mResources.getString(
R.string.use_biometric_or_screen_lock_label);
break;
}
}
break;
}
return settingName;
}
}
/**
* An injector for various class and method dependencies. Used for testing.
*/
@VisibleForTesting
interface Injector {
/**
* Provides the application {@link Resources} object.
*
* @return An instance of {@link Resources}.
*/
@NonNull
Resources getResources();
/**
* Provides the framework biometric manager that may be used on Android 10 (API 29) and
* above.
*
* @return An instance of {@link android.hardware.biometrics.BiometricManager}.
*/
@RequiresApi(Build.VERSION_CODES.Q)
@Nullable
android.hardware.biometrics.BiometricManager getBiometricManager();
/**
* Provides the fingerprint manager that may be used on Android 9.0 (API 28) and below.
*
* @return An instance of
* {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
*/
@Nullable
androidx.core.hardware.fingerprint.FingerprintManagerCompat getFingerprintManager();
/**
* Checks if the current device is capable of being secured with a lock screen credential
* (i.e. PIN, pattern, or password).
*/
boolean isDeviceSecurable();
/**
* Checks if the current device is secured with a lock screen credential (i.e. PIN, pattern,
* or password).
*
* @return Whether the device is secured with a lock screen credential.
*/
boolean isDeviceSecuredWithCredential();
/**
* Checks if the current device has a hardware sensor that may be used for fingerprint
* authentication.
*
* @return Whether the device has a fingerprint sensor.
*/
boolean isFingerprintHardwarePresent();
/**
* Checks if the current device has a hardware sensor that may be used for face
* authentication.
*
* @return Whether the device has a face sensor.
*/
boolean isFaceHardwarePresent();
/**
* Checks if the current device has a hardware sensor that may be used for iris
* authentication.
*
* @return Whether the device has an iris sensor.
*/
boolean isIrisHardwarePresent();
/**
* Checks if all biometric sensors on the device are known to meet or exceed the security
* requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>).
*
* @return Whether all biometrics are known to be <strong>Class 3</strong> or stronger.
*/
boolean isStrongBiometricGuaranteed();
}
/**
* Provides the default class and method dependencies that will be used in production.
*/
private static class DefaultInjector implements Injector {
@NonNull private final Context mContext;
/**
* Creates a default injector from the given context.
*
* @param context The application or activity context.
*/
DefaultInjector(@NonNull Context context) {
mContext = context.getApplicationContext();
}
@Override
@NonNull
public Resources getResources() {
return mContext.getResources();
}
@Override
@RequiresApi(Build.VERSION_CODES.Q)
@Nullable
public android.hardware.biometrics.BiometricManager getBiometricManager() {
return Api29Impl.create(mContext);
}
@Override
@Nullable
public androidx.core.hardware.fingerprint.FingerprintManagerCompat getFingerprintManager() {
return androidx.core.hardware.fingerprint.FingerprintManagerCompat.from(mContext);
}
@Override
public boolean isDeviceSecurable() {
return KeyguardUtils.getKeyguardManager(mContext) != null;
}
@Override
public boolean isDeviceSecuredWithCredential() {
return KeyguardUtils.isDeviceSecuredWithCredential(mContext);
}
@Override
public boolean isFingerprintHardwarePresent() {
return PackageUtils.hasSystemFeatureFingerprint(mContext);
}
@Override
public boolean isFaceHardwarePresent() {
return PackageUtils.hasSystemFeatureFace(mContext);
}
@Override
public boolean isIrisHardwarePresent() {
return PackageUtils.hasSystemFeatureIris(mContext);
}
@Override
public boolean isStrongBiometricGuaranteed() {
return DeviceUtils.canAssumeStrongBiometrics(mContext, Build.MODEL);
}
}
/**
* The injector for class and method dependencies used by this manager.
*/
@NonNull private final Injector mInjector;
/**
* The framework biometric manager. Should be non-null on Android 10 (API 29) and above.
*/
@Nullable private final android.hardware.biometrics.BiometricManager mBiometricManager;
/**
* The framework fingerprint manager. Should be non-null on Android 10 (API 29) and below.
*/
@Nullable private final androidx.core.hardware.fingerprint.FingerprintManagerCompat
mFingerprintManager;
/**
* Creates a {@link BiometricManager} instance from the given context.
*
* @param context The application or activity context.
* @return An instance of {@link BiometricManager}.
*/
@NonNull
public static BiometricManager from(@NonNull Context context) {
return new BiometricManager(new DefaultInjector(context));
}
/**
* Creates a {@link BiometricManager} instance with the given injector.
*
* @param injector An injector for class and method dependencies.
*/
@VisibleForTesting
BiometricManager(@NonNull Injector injector) {
mInjector = injector;
mBiometricManager = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? injector.getBiometricManager()
: null;
mFingerprintManager = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
? injector.getFingerprintManager()
: null;
}
/**
* Checks if the user can authenticate with biometrics. This requires at least one biometric
* sensor to be present, enrolled, and available on the device.
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with biometrics. Otherwise,
* returns an error code indicating why the user can't authenticate, or
* {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
*
* @deprecated Use {@link #canAuthenticate(int)} instead.
*/
@Deprecated
@AuthenticationStatus
public int canAuthenticate() {
return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
}
/**
* Checks if the user can authenticate with an authenticator that meets the given requirements.
* This requires at least one of the specified authenticators to be present, enrolled, and
* available on the device.
*
* <p>Note that not all combinations of authenticator types are supported prior to Android 11
* (API 30). Specifically, {@code DEVICE_CREDENTIAL} alone is unsupported prior to API 30, and
* {@code BIOMETRIC_STRONG | DEVICE_CREDENTIAL} is unsupported on API 28-29. Developers that
* wish to check for the presence of a PIN, pattern, or password on these versions should
* instead use {@link KeyguardManager#isDeviceSecure()}.
*
* @param authenticators A bit field representing the types of {@link Authenticators} that may
* be used for authentication.
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with an allowed
* authenticator. Otherwise, returns {@link #BIOMETRIC_STATUS_UNKNOWN} or an error code
* indicating why the user can't authenticate.
*/
@AuthenticationStatus
public int canAuthenticate(@AuthenticatorTypes int authenticators) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (mBiometricManager == null) {
Log.e(TAG, "Failure in canAuthenticate(). BiometricManager was null.");
return BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
return Api30Impl.canAuthenticate(mBiometricManager, authenticators);
}
return canAuthenticateCompat(authenticators);
}
/**
* Checks if the user can authenticate with an authenticator that meets the given requirements.
*
* <p>This method attempts to emulate the behavior of {@link #canAuthenticate(int)} on devices
* running Android 10 (API 29) and below.
*
* @param authenticators A bit field representing the types of {@link Authenticators} that may
* be used for authentication.
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with the given set of allowed
* authenticators. Otherwise, returns an error code indicating why the user can't authenticate,
* or {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
*/
@AuthenticationStatus
private int canAuthenticateCompat(@AuthenticatorTypes int authenticators) {
if (!AuthenticatorUtils.isSupportedCombination(authenticators)) {
return BIOMETRIC_ERROR_UNSUPPORTED;
}
// Match the framework's behavior for an empty authenticator set on API 30.
if (authenticators == 0) {
return BIOMETRIC_ERROR_NO_HARDWARE;
}
// Authentication is impossible if the device can't be secured.
if (!mInjector.isDeviceSecurable()) {
return BIOMETRIC_ERROR_NO_HARDWARE;
}
// Credential authentication is always possible if the device is secured. Conversely, no
// form of authentication is possible if the device is not secured.
if (AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)) {
return mInjector.isDeviceSecuredWithCredential()
? BIOMETRIC_SUCCESS
: BIOMETRIC_ERROR_NONE_ENROLLED;
}
// The class of some non-fingerprint biometrics can be checked on API 29.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
return AuthenticatorUtils.isWeakBiometricAllowed(authenticators)
? canAuthenticateWithWeakBiometricOnApi29()
: canAuthenticateWithStrongBiometricOnApi29();
}
// Non-fingerprint biometrics may be invoked but can't be checked on API 28.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
// Having fingerprint hardware is a prerequisite, since BiometricPrompt internally
// calls FingerprintManager#getErrorString() on API 28 (b/151443237).
return mInjector.isFingerprintHardwarePresent()
? canAuthenticateWithFingerprintOrUnknownBiometric()
: BIOMETRIC_ERROR_NO_HARDWARE;
}
// No non-fingerprint biometric APIs exist prior to API 28.
return canAuthenticateWithFingerprint();
}
/**
* Checks if the user can authenticate with a <strong>Class 3</strong> (formerly
* <strong>Strong</strong>) or better biometric sensor on a device running Android 10 (API 29).
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with a
* <strong>Class 3</strong> or better biometric sensor. Otherwise, returns an error code
* indicating why the user can't authenticate, or {@link #BIOMETRIC_STATUS_UNKNOWN} if it is
* unknown whether the user can authenticate.
*/
@RequiresApi(Build.VERSION_CODES.Q)
@AuthenticationStatus
private int canAuthenticateWithStrongBiometricOnApi29() {
// Use the hidden canAuthenticate(CryptoObject) method if it exists.
final Method canAuthenticateWithCrypto = Api29Impl.getCanAuthenticateWithCryptoMethod();
if (canAuthenticateWithCrypto != null) {
final android.hardware.biometrics.BiometricPrompt.CryptoObject crypto =
CryptoObjectUtils.wrapForBiometricPrompt(
CryptoObjectUtils.createFakeCryptoObject());
if (crypto != null) {
try {
final Object result =
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
? canAuthenticateWithCrypto.invoke(mBiometricManager, crypto)
: null;
if (result instanceof Integer) {
return (int) result;
}
Log.w(TAG, "Invalid return type for canAuthenticate(CryptoObject).");
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
Log.w(TAG, "Failed to invoke canAuthenticate(CryptoObject).", e);
}
}
}
// Check if we can use canAuthenticate() as a proxy for canAuthenticate(BIOMETRIC_STRONG).
@AuthenticationStatus final int result = canAuthenticateWithWeakBiometricOnApi29();
if (mInjector.isStrongBiometricGuaranteed() || result != BIOMETRIC_SUCCESS) {
return result;
}
// If all else fails, check if fingerprint authentication is available.
return canAuthenticateWithFingerprintOrUnknownBiometric();
}
/**
* Checks if the user can authenticate with a <strong>Class 2</strong> (formerly
* <strong>Weak</strong>) or better biometric sensor on a device running Android 10 (API 29).
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with a
* <strong>Class 2</strong> or better biometric sensor. Otherwise, returns an error code
* indicating why the user can't authenticate.
*/
@RequiresApi(Build.VERSION_CODES.Q)
@AuthenticationStatus
private int canAuthenticateWithWeakBiometricOnApi29() {
if (mBiometricManager == null) {
Log.e(TAG, "Failure in canAuthenticate(). BiometricManager was null.");
return BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
return Api29Impl.canAuthenticate(mBiometricManager);
}
/**
* Checks if the user can authenticate with fingerprint or with a biometric sensor for which
* there is no platform method to check availability.
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint. Otherwise,
* returns an error code indicating why the user can't authenticate, or
* {@link #BIOMETRIC_STATUS_UNKNOWN} if it is unknown whether the user can authenticate.
*/
@AuthenticationStatus
private int canAuthenticateWithFingerprintOrUnknownBiometric() {
// If the device is not secured, authentication is definitely not possible. Use
// FingerprintManager to distinguish between the "no hardware" and "none enrolled" cases.
if (!mInjector.isDeviceSecuredWithCredential()) {
return canAuthenticateWithFingerprint();
}
// Check for definite availability of fingerprint. Otherwise, return "unknown" to allow for
// non-fingerprint biometrics (e.g. iris) that may be available via BiometricPrompt.
return canAuthenticateWithFingerprint() == BIOMETRIC_SUCCESS
? BIOMETRIC_SUCCESS
: BIOMETRIC_STATUS_UNKNOWN;
}
/**
* Checks if the user can authenticate with fingerprint.
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint.
* Otherwise, returns an error code indicating why the user can't authenticate.
*/
@AuthenticationStatus
private int canAuthenticateWithFingerprint() {
if (mFingerprintManager == null) {
Log.e(TAG, "Failure in canAuthenticate(). FingerprintManager was null.");
return BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
if (!mFingerprintManager.isHardwareDetected()) {
return BIOMETRIC_ERROR_NO_HARDWARE;
}
if (!mFingerprintManager.hasEnrolledFingerprints()) {
return BIOMETRIC_ERROR_NONE_ENROLLED;
}
return BIOMETRIC_SUCCESS;
}
/**
* Produces an instance of the {@link Strings} class, which provides localized strings for an
* application, given a set of allowed authenticator types.
*
* @param authenticators A bit field representing the types of {@link Authenticators} that may
* be used for authentication.
* @return A {@link Strings} collection for the given allowed authenticator types.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
public Strings getStrings(@AuthenticatorTypes int authenticators) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (mBiometricManager == null) {
Log.e(TAG, "Failure in getStrings(). BiometricManager was null.");
return null;
}
return new Strings(Api31Impl.getStrings(mBiometricManager, authenticators));
}
final StringsCompat stringsCompat = new StringsCompat(
mInjector.getResources(),
authenticators,
mInjector.isFingerprintHardwarePresent(),
mInjector.isFaceHardwarePresent(),
mInjector.isIrisHardwarePresent(),
mInjector.isDeviceSecuredWithCredential());
return new Strings(stringsCompat);
}
/**
* Nested class to avoid verification errors for methods introduced in Android 12 (API 31).
*/
@RequiresApi(Build.VERSION_CODES.S)
private static class Api31Impl {
// Prevent instantiation.
private Api31Impl() {}
/**
* Gets an instance of the framework
* {@link android.hardware.biometrics.BiometricManager.Strings} class.
*
* @param biometricManager An instance of
* {@link android.hardware.biometrics.BiometricManager}.
* @param authenticators A bit field representing the types of {@link Authenticators} that
* may be used for authentication.
* @return An instance of {@link android.hardware.biometrics.BiometricManager.Strings}.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@NonNull
static android.hardware.biometrics.BiometricManager.Strings getStrings(
@NonNull android.hardware.biometrics.BiometricManager biometricManager,
@AuthenticatorTypes int authenticators) {
return biometricManager.getStrings(authenticators);
}
/**
* Calls {@link android.hardware.biometrics.BiometricManager.Strings#getButtonLabel()} for
* the given framework strings instance.
*
* @param strings An instance of
* {@link android.hardware.biometrics.BiometricManager.Strings}.
* @return The result of
* {@link android.hardware.biometrics.BiometricManager.Strings#getButtonLabel()}.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
static CharSequence getButtonLabel(
@NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
return strings.getButtonLabel();
}
/**
* Calls {@link android.hardware.biometrics.BiometricManager.Strings#getPromptMessage()} for
* the given framework strings instance.
*
* @param strings An instance of
* {@link android.hardware.biometrics.BiometricManager.Strings}.
* @return The result of
* {@link android.hardware.biometrics.BiometricManager.Strings#getPromptMessage()}.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
static CharSequence getPromptMessage(
@NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
return strings.getPromptMessage();
}
/**
* Calls {@link android.hardware.biometrics.BiometricManager.Strings#getSettingName()} for
* the given framework strings instance.
*
* @param strings An instance of
* {@link android.hardware.biometrics.BiometricManager.Strings}.
* @return The result of
* {@link android.hardware.biometrics.BiometricManager.Strings#getSettingName()}.
*/
@RequiresPermission(Manifest.permission.USE_BIOMETRIC)
@Nullable
static CharSequence getSettingName(
@NonNull android.hardware.biometrics.BiometricManager.Strings strings) {
return strings.getSettingName();
}
}
/**
* Nested class to avoid verification errors for methods introduced in Android 11 (API 30).
*/
@RequiresApi(Build.VERSION_CODES.R)
private static class Api30Impl {
// Prevent instantiation.
private Api30Impl() {}
/**
* Calls {@link android.hardware.biometrics.BiometricManager#canAuthenticate(int)} for the
* given biometric manager and set of allowed authenticators.
*
* @param biometricManager An instance of
* {@link android.hardware.biometrics.BiometricManager}.
* @param authenticators A bit field representing the types of {@link Authenticators} that
* may be used for authentication.
* @return The result of
* {@link android.hardware.biometrics.BiometricManager#canAuthenticate(int)}.
*/
@AuthenticationStatus
static int canAuthenticate(
@NonNull android.hardware.biometrics.BiometricManager biometricManager,
@AuthenticatorTypes int authenticators) {
return biometricManager.canAuthenticate(authenticators);
}
}
/**
* Nested class to avoid verification errors for methods introduced in Android 10 (API 29).
*/
@RequiresApi(Build.VERSION_CODES.Q)
private static class Api29Impl {
// Prevent instantiation.
private Api29Impl() {}
/**
* Gets an instance of the framework
* {@link android.hardware.biometrics.BiometricManager} class.
*
* @param context The application or activity context.
* @return An instance of {@link android.hardware.biometrics.BiometricManager}.
*/
@Nullable
static android.hardware.biometrics.BiometricManager create(@NonNull Context context) {
return context.getSystemService(android.hardware.biometrics.BiometricManager.class);
}
/**
* Calls {@link android.hardware.biometrics.BiometricManager#canAuthenticate()} for the
* given biometric manager.
*
* @param biometricManager An instance of
* {@link android.hardware.biometrics.BiometricManager}.
* @return The result of
* {@link android.hardware.biometrics.BiometricManager#canAuthenticate()}.
*/
@AuthenticationStatus
static int canAuthenticate(
@NonNull android.hardware.biometrics.BiometricManager biometricManager) {
return biometricManager.canAuthenticate();
}
/**
* Checks for and returns the hidden {@link android.hardware.biometrics.BiometricManager}
* method {@code canAuthenticate(CryptoObject)} via reflection.
*
* @return The method {@code BiometricManager#canAuthenticate(CryptoObject)}, if present.
*/
@SuppressWarnings("JavaReflectionMemberAccess")
@Nullable
static Method getCanAuthenticateWithCryptoMethod() {
try {
return android.hardware.biometrics.BiometricManager.class.getMethod(
"canAuthenticate",
android.hardware.biometrics.BiometricPrompt.CryptoObject.class);
} catch (NoSuchMethodException e) {
return null;
}
}
}
}