blob: 2334cd61ef7687a782ea6d08b87d4aef11ba39f7 [file] [log] [blame]
/*
* 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.app.KeyguardManager;
import android.content.Context;
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.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 {}
/**
* An injector for various class and method dependencies. Used for testing.
*/
@VisibleForTesting
interface Injector {
/**
* 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 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
@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 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 9.0 (API 28) 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;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mBiometricManager = injector.getBiometricManager();
mFingerprintManager = null;
} else {
mBiometricManager = null;
mFingerprintManager = injector.getFingerprintManager();
}
}
/**
* 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;
}
// No authenticators are enrolled if the device is not secured.
if (!mInjector.isDeviceSecuredWithCredential()) {
return BIOMETRIC_ERROR_NONE_ENROLLED;
}
// Credential authentication is always possible if the device is secured.
if (AuthenticatorUtils.isDeviceCredentialAllowed(authenticators)) {
return BIOMETRIC_SUCCESS;
}
// 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()
? canAuthenticateWithFingerprintOrUnknown()
: 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 =
canAuthenticateWithCrypto.invoke(mBiometricManager, crypto);
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 canAuthenticateWithFingerprintOrUnknown();
}
/**
* 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, falling back to
* {@link #BIOMETRIC_STATUS_UNKNOWN} for any error condition.
*
* @return {@link #BIOMETRIC_SUCCESS} if the user can authenticate with fingerprint, or
* {@link #BIOMETRIC_STATUS_UNKNOWN} otherwise.
*/
@AuthenticationStatus
private int canAuthenticateWithFingerprintOrUnknown() {
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;
}
/**
* 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;
}
}
}
}