summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kevin Chyn <kchyn@google.com> 2019-12-16 20:06:31 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-12-16 20:06:31 +0000
commit14ff3aab30c75249341d8b7672b191ce644cf852 (patch)
treee73055eff29e9e8412c204b8eaf90c71da13fc74
parent18408b132fc80e34f7cd9cb83f875388401ac8c2 (diff)
parent9cef3631fb8eaecd4ea4583337f160af24237e12 (diff)
Merge changes from topic "biometric-strength"
* changes: Enforce that registered authenticators are not null Ensure that cancelling authentication ends up in the correct state Enforce authenticator registration Enforce that only public authenticator combinations are accepted Add phenotype namespace and flag for biometrics Use @Authenticators.Types for authenticator selection Add setAllowedAuthenticators(int) to BiometricPrompt
-rw-r--r--api/current.txt18
-rwxr-xr-xapi/system-current.txt10
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/hardware/biometrics/BiometricConstants.java7
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java127
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java319
-rw-r--r--core/java/android/hardware/biometrics/IAuthService.aidl2
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl5
-rw-r--r--core/java/android/provider/DeviceConfig.java9
-rw-r--r--core/res/res/values/config.xml8
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java71
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java95
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java304
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricStrengthController.java119
-rw-r--r--services/core/java/com/android/server/biometrics/SensorConfig.java (renamed from core/java/android/hardware/biometrics/Authenticator.java)26
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java180
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java708
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java226
24 files changed, 1837 insertions, 469 deletions
diff --git a/api/current.txt b/api/current.txt
index 2c1b066f9a76..d15069a43e7a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16780,16 +16780,29 @@ package android.hardware {
package android.hardware.biometrics {
public class BiometricManager {
- method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+ method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
+ public static interface BiometricManager.Authenticators {
+ field public static final int BIOMETRIC_STRONG = 15; // 0xf
+ field public static final int BIOMETRIC_WEAK = 255; // 0xff
+ field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
+ }
+
public class BiometricPrompt {
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
+ method @Nullable public int getAllowedAuthenticators();
+ method @Nullable public CharSequence getDescription();
+ method @Nullable public CharSequence getNegativeButtonText();
+ method @Nullable public CharSequence getSubtitle();
+ method @NonNull public CharSequence getTitle();
+ method public boolean isConfirmationRequired();
field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16825,9 +16838,10 @@ package android.hardware.biometrics {
public static class BiometricPrompt.Builder {
ctor public BiometricPrompt.Builder(android.content.Context);
method @NonNull public android.hardware.biometrics.BiometricPrompt build();
+ method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
- method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+ method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
diff --git a/api/system-current.txt b/api/system-current.txt
index b449b2e739e7..3eb71245a5da 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2277,6 +2277,15 @@ package android.hardware {
}
+package android.hardware.biometrics {
+
+ public static interface BiometricManager.Authenticators {
+ field public static final int BIOMETRIC_CONVENIENCE = 4095; // 0xfff
+ field public static final int EMPTY_SET = 0; // 0x0
+ }
+
+}
+
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
@@ -7126,6 +7135,7 @@ package android.provider {
field public static final String NAMESPACE_APP_COMPAT = "app_compat";
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
field public static final String NAMESPACE_AUTOFILL = "autofill";
+ field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
diff --git a/api/test-current.txt b/api/test-current.txt
index 7cfc21864636..49426399d6e6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2433,6 +2433,7 @@ package android.provider {
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ANDROID = "android";
field public static final String NAMESPACE_AUTOFILL = "autofill";
+ field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field public static final String NAMESPACE_PERMISSIONS = "permissions";
field public static final String NAMESPACE_PRIVACY = "privacy";
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 191516b5b992..d28b7c531430 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -16,8 +16,9 @@
package android.hardware.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import android.annotation.UnsupportedAppUsage;
-import android.app.KeyguardManager;
/**
@@ -126,8 +127,8 @@ public interface BiometricConstants {
/**
* The device does not have pin, pattern, or password set up. See
- * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and
- * {@link KeyguardManager#isDeviceSecure()}
+ * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)},
+ * {@link Authenticators#DEVICE_CREDENTIAL}, and {@link BiometricManager#canAuthenticate(int)}.
*/
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index f17b3ae2052f..7e1506f61a51 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -65,6 +66,77 @@ public class BiometricManager {
BIOMETRIC_ERROR_NO_HARDWARE})
@interface BiometricError {}
+ /**
+ * 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</code>).
+ */
+ public interface Authenticators {
+ /**
+ * An {@link IntDef} representing valid combinations of authenticator types.
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ BIOMETRIC_STRONG,
+ BIOMETRIC_WEAK,
+ DEVICE_CREDENTIAL,
+ })
+ @interface Types {}
+
+ /**
+ * Empty set with no authenticators specified.
+ * @hide
+ */
+ @SystemApi
+ int EMPTY_SET = 0x0;
+
+ /**
+ * Placeholder for the theoretical strongest biometric security tier.
+ * @hide
+ */
+ int BIOMETRIC_MAX_STRENGTH = 0x001;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Strong</strong>, as defined by the Android CDD.
+ */
+ int BIOMETRIC_STRONG = 0x00F;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <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</code>.
+ */
+ int BIOMETRIC_WEAK = 0x0FF;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Convenience</strong>, as defined by the Android CDD. This
+ * is not a valid parameter to any of the {@link android.hardware.biometrics} APIs, since
+ * the CDD allows only {@link #BIOMETRIC_WEAK} and stronger authenticators to participate.
+ * @hide
+ */
+ @SystemApi
+ int BIOMETRIC_CONVENIENCE = 0xFFF;
+
+ /**
+ * Placeholder for the theoretical weakest biometric security tier.
+ * @hide
+ */
+ int BIOMETRIC_MIN_STRENGTH = 0x7FFF;
+
+ /**
+ * 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;
+ }
+
private final Context mContext;
private final IAuthService mService;
private final boolean mHasHardware;
@@ -94,27 +166,64 @@ public class BiometricManager {
}
/**
- * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
- * can be expected to be shown (hardware available, templates enrolled, user-enabled).
+ * Determine if biometrics can be used. In other words, determine if
+ * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+ * user-enabled). This is the equivalent of {@link #canAuthenticate(int)} with
+ * {@link Authenticators#BIOMETRIC_WEAK}
+ *
+ * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any strong
+ * biometrics enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
+ * supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a strong biometric can currently
+ * be used (enrolled and available).
*
- * @return Returns {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any
- * enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
- * supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a biometric can currently be
- * used (enrolled and available).
+ * @deprecated See {@link #canAuthenticate(int)}.
*/
+ @Deprecated
@RequiresPermission(USE_BIOMETRIC)
public @BiometricError int canAuthenticate() {
- return canAuthenticate(mContext.getUserId());
+ return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
+ }
+
+ /**
+ * Determine if any of the provided authenticators can be used. In other words, determine if
+ * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+ * user-enabled).
+ *
+ * For biometric authenticators, determine if the device can currently authenticate with at
+ * least the requested strength. For example, invoking this API with
+ * {@link Authenticators#BIOMETRIC_WEAK} on a device that currently only has
+ * {@link Authenticators#BIOMETRIC_STRONG} enrolled will return {@link #BIOMETRIC_SUCCESS}.
+ *
+ * Invoking this API with {@link Authenticators#DEVICE_CREDENTIAL} can be used to determine
+ * if the user has a PIN/Pattern/Password set up.
+ *
+ * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+ * If multiple authenticators are queried, a logical OR will be applied.
+ * For example, if {@link Authenticators#DEVICE_CREDENTIAL} |
+ * {@link Authenticators#BIOMETRIC_STRONG} is queried and only
+ * {@link Authenticators#DEVICE_CREDENTIAL} is set up, this API will
+ * return {@link #BIOMETRIC_SUCCESS}
+ *
+ * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any of the
+ * requested authenticators enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are
+ * currently supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if one of the requested
+ * authenticators can currently be used (enrolled and available).
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public @BiometricError int canAuthenticate(@Authenticators.Types int authenticators) {
+ return canAuthenticate(mContext.getUserId(), authenticators);
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public @BiometricError int canAuthenticate(int userId) {
+ public @BiometricError int canAuthenticate(int userId,
+ @Authenticators.Types int authenticators) {
if (mService != null) {
try {
- return mService.canAuthenticate(mContext.getOpPackageName(), userId);
+ final String opPackageName = mContext.getOpPackageName();
+ return mService.canAuthenticate(opPackageName, userId, authenticators);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 9c51b5246749..6f9c9e6db025 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -18,6 +18,7 @@ package android.hardware.biometrics;
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.NonNull;
@@ -149,7 +150,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* 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;
@@ -157,8 +158,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private Context mContext;
/**
- * Creates a builder for a biometric dialog.
- * @param context
+ * 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) {
mBundle = new Bundle();
@@ -166,58 +167,67 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
- * Required: Set the title to display.
- * @param title
- * @return
+ * 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) {
+ @NonNull
+ public Builder setTitle(@NonNull CharSequence title) {
mBundle.putCharSequence(KEY_TITLE, title);
return this;
}
/**
- * For internal use currently. Only takes effect if title is null/empty. Shows a default
- * modality-specific title.
+ * 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() {
+ @NonNull
+ public Builder setUseDefaultTitle() {
mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true);
return this;
}
/**
- * Optional: Set the subtitle to display.
- * @param subtitle
- * @return
+ * 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) {
+ @NonNull
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
return this;
}
/**
- * Optional: Set the description to display.
- * @param description
- * @return
+ * 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) {
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
mBundle.putCharSequence(KEY_DESCRIPTION, description);
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.
+ * 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 should not be set if {@link #setDeviceCredentialAllowed(boolean)}(boolean)
- * is set to true.
+ * <p>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
- * @return
+ * @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
+ public Builder setNegativeButton(@NonNull CharSequence text,
@NonNull @CallbackExecutor Executor executor,
@NonNull DialogInterface.OnClickListener listener) {
if (TextUtils.isEmpty(text)) {
@@ -235,70 +245,112 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
- * Optional: A hint to the system to require user confirmation after a biometric has been
- * authenticated. For example, implicit modalities like Face and Iris authentication are
- * passive, meaning they don't require an explicit user action to complete. When set to
- * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
- * will require confirmation by default.
+ * 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,
+ * <p>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 is a hint to the system. The system may choose to ignore the flag. For
- * example, if the user disables implicit authentication in Settings, or if it does not
- * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
- * requiring confirmation.
+ * <p>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
+ * @param requireConfirmation true if explicit user confirmation should be required, or
+ * false otherwise.
+ * @return This builder.
*/
- @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) {
+ @NonNull
+ public Builder setConfirmationRequired(boolean requireConfirmation) {
mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
return this;
}
/**
- * The user will first be prompted to authenticate with biometrics, but also given the
- * option to authenticate with their device PIN, pattern, or password. Developers should
- * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device
- * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be
- * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}.
+ * Optional: If enabled, the user will first be prompted to authenticate with biometrics,
+ * but also given the option to authenticate with their device PIN, pattern, or password.
+ * Developers should first check {@link KeyguardManager#isDeviceSecure()} before enabling.
+ * If the device is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}
+ * will be given to {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
* Defaults to false.
*
- * Note that {@link #setNegativeButton(CharSequence, Executor,
- * DialogInterface.OnClickListener)} should not be set if this is set to true.
+ * <p>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.
*
- * @param allowed When true, the prompt will fall back to ask for the user's device
- * credentials (PIN, pattern, or password).
- * @return
+ * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}.
*/
- @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) {
+ @Deprecated
+ @NonNull
+ public Builder setDeviceCredentialAllowed(boolean allowed) {
mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, 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:
+ * <ul>
+ * <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li>
+ * <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li>
+ * </ul>
+ *
+ * <p>If this method is used and no authenticator of any of the specified types is available
+ * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will
+ * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}
+ * will be invoked with an appropriate error code.
+ *
+ * <p>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)}.
+ *
+ * @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) {
+ mBundle.putInt(KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
- * @return a {@link BiometricPrompt}
- * @throws IllegalArgumentException if any of the required fields are not set.
+ *
+ * @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() {
+ @NonNull
+ public BiometricPrompt build() {
final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
- final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
- final boolean allowCredential = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
- final Object authenticatorsAllowed = mBundle.get(KEY_AUTHENTICATORS_ALLOWED);
+ final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+ final boolean deviceCredentialAllowed = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
+ final @Authenticators.Types int authenticators =
+ mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+ final boolean willShowDeviceCredentialButton = deviceCredentialAllowed
+ || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
if (TextUtils.isEmpty(title) && !useDefaultTitle) {
throw new IllegalArgumentException("Title must be set and non-empty");
- } else if (TextUtils.isEmpty(negative) && !allowCredential) {
+ } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) {
throw new IllegalArgumentException("Negative text must be set and non-empty");
- } else if (!TextUtils.isEmpty(negative) && allowCredential) {
+ } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) {
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
- } else if (authenticatorsAllowed != null && allowCredential) {
- throw new IllegalArgumentException("setAuthenticatorsAllowed and"
- + " setDeviceCredentialAllowed should not be used simultaneously");
}
return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
}
@@ -394,6 +446,75 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * 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 mBundle.getCharSequence(KEY_TITLE, "");
+ }
+
+ /**
+ * 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 mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+ }
+
+ /**
+ * 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 mBundle.getCharSequence(KEY_SUBTITLE);
+ }
+
+ /**
+ * 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 mBundle.getCharSequence(KEY_DESCRIPTION);
+ }
+
+ /**
+ * 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 mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
+ }
+
+ /**
+ * 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 mBundle.getBoolean(KEY_REQUIRE_CONFIRMATION, true);
+ }
+
+ /**
+ * 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 mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+ }
+
+ /**
* A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework
* supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
*/
@@ -509,10 +630,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* 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)
@@ -542,23 +665,33 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* 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,
+ * 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.
+ * <p>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
+ * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements
+ * for <strong>Strong</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 value other than
+ * {@link Authenticators#BIOMETRIC_STRONG}.
*
- * @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
+ * @throws IllegalArgumentException If any of the arguments are null, if
+ * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true, or if
+ * {@link Builder#setAllowedAuthenticators(int)} was explicitly called with any value other than
+ * {@link Authenticators#BIOMETRIC_STRONG}.
+ *
+ * @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,
@@ -577,9 +710,20 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
+
+ @Authenticators.Types int authenticators = mBundle.getInt(
+ KEY_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_STRONG);
+
+ if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)
+ || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
throw new IllegalArgumentException("Device credential not supported with crypto");
}
+
+ // Disallow any non-Strong biometric authenticator types.
+ if ((authenticators & ~Authenticators.BIOMETRIC_STRONG) != 0) {
+ throw new IllegalArgumentException("Only Strong biometrics supported with crypto");
+ }
+
authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
}
@@ -598,16 +742,17 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* 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.
+ * <p>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
+ * @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
+ * @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,
@@ -653,8 +798,22 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
mAuthenticationCallback = callback;
final long sessionId = crypto != null ? crypto.getOpId() : 0;
if (BiometricManager.hasBiometrics(mContext)) {
+ final Bundle bundle;
+ if (crypto != null) {
+ // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth.
+ // Note that we use a new bundle 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.
+ bundle = new Bundle(mBundle);
+ bundle.putInt(KEY_AUTHENTICATORS_ALLOWED,
+ mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED,
+ Authenticators.BIOMETRIC_STRONG));
+ } else {
+ bundle = mBundle;
+ }
+
mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
- mContext.getOpPackageName(), mBundle);
+ mContext.getOpPackageName(), bundle);
} else {
mExecutor.execute(() -> {
callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 516a25d0b9b1..d482198b82b3 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -35,7 +35,7 @@ interface IAuthService {
// TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
// Checks if biometrics can be used.
- int canAuthenticate(String opPackageName, int userId);
+ int canAuthenticate(String opPackageName, int userId, int authenticators);
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index ca024215f782..8a6be18e403d 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -35,7 +35,7 @@ interface IBiometricService {
void cancelAuthentication(IBinder token, String opPackageName);
// Checks if biometrics can be used.
- int canAuthenticate(String opPackageName, int userId);
+ int canAuthenticate(String opPackageName, int userId, int authenticators);
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
@@ -43,7 +43,8 @@ interface IBiometricService {
// Registers an authenticator (e.g. face, fingerprint, iris).
// Id must be unique, whereas strength and modality don't need to be.
// TODO(b/123321528): Turn strength and modality into enums.
- void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
+ void registerAuthenticator(int id, int modality, int strength,
+ IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index ef22d70f715b..6650cf23d611 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -350,6 +350,15 @@ public final class DeviceConfig {
public static final String NAMESPACE_PRIVACY = "privacy";
/**
+ * Namespace for biometrics related features
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String NAMESPACE_BIOMETRICS = "biometrics";
+
+ /**
* Permission related properties definitions.
*
* @hide
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 89c913b8f580..76860745503d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4095,6 +4095,14 @@
<!-- Which binder services to include in incident reports containing restricted images. -->
<string-array name="config_restrictedImagesServices" translatable="false"/>
+ <!-- List of biometric sensors on the device, in decreasing strength. Consumed by AuthService
+ when registering authenticators with BiometricService. Format must be ID:Modality:Strength,
+ where: IDs are unique per device, Modality as defined in BiometricAuthenticator.java,
+ and Strength as defined in Authenticators.java -->
+ <string-array name="config_biometric_sensors" translatable="false" >
+ <item>0:2:15</item> <!-- ID0:Fingerprint:Strong -->
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6956c39375a2..1bd006bd1e15 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2471,6 +2471,8 @@
<java-symbol type="string" name="face_authenticated_no_confirmation_required" />
<java-symbol type="string" name="face_authenticated_confirmation_required" />
+ <java-symbol type="array" name="config_biometric_sensors" />
+
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" />
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 6b0d3c807079..e0ca1ace2ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -29,7 +30,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -341,6 +341,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
mCurrentDialog.dismissFromSystemServer();
+
+ // BiometricService will have already sent the callback to the client in this case.
+ // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
+ mCurrentDialog = null;
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -416,7 +420,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
// TODO: Clean this up
Bundle bundle = (Bundle) mCurrentDialogArgs.arg1;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
- Authenticator.TYPE_CREDENTIAL);
+ Authenticators.DEVICE_CREDENTIAL);
}
showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index d6f830dd2e7a..7d237c45c7b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -16,23 +16,21 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
import android.annotation.IntDef;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.os.UserManager;
import android.util.DisplayMetrics;
-import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -71,12 +69,12 @@ public class Utils {
static boolean isDeviceCredentialAllowed(Bundle biometricPromptBundle) {
final int authenticators = getAuthenticators(biometricPromptBundle);
- return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+ return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
}
static boolean isBiometricAllowed(Bundle biometricPromptBundle) {
final int authenticators = getAuthenticators(biometricPromptBundle);
- return (authenticators & Authenticator.TYPE_BIOMETRIC) != 0;
+ return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
}
static int getAuthenticators(Bundle biometricPromptBundle) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index df676376b479..25cc9a304f2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -24,7 +26,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -292,9 +293,9 @@ public class AuthBiometricViewTest extends SysuiTestCase {
private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) {
Bundle bundle = new Bundle();
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
- int authenticators = Authenticator.TYPE_BIOMETRIC;
+ int authenticators = Authenticators.BIOMETRIC_WEAK;
if (allowDeviceCredential) {
- authenticators |= Authenticator.TYPE_CREDENTIAL;
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
} else {
bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 6e438e8e5772..162b16ed3728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -26,7 +28,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
@@ -64,7 +65,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionAuthenticated_sendsDismissedAuthenticated() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_AUTHENTICATED);
@@ -73,7 +74,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionUserCanceled_sendsDismissedUserCanceled() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -82,7 +83,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionButtonNegative_sendsDismissedButtonNegative() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -91,7 +92,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionTryAgain_sendsTryAgain() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -100,7 +101,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionError_sendsDismissedError() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_ERROR);
@@ -110,7 +111,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
initializeContainer(
- Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+ Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -125,7 +126,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
initializeContainer(
- Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+ Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
mAuthContainer.animateToCredentialUI();
@@ -134,7 +135,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testShowBiometricUI() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
assertNotEquals(null, mAuthContainer.mBiometricView);
@@ -146,7 +147,7 @@ public class AuthContainerViewTest extends SysuiTestCase {
@Test
public void testShowCredentialUI_doesNotInflateBiometricUI() {
- initializeContainer(Authenticator.TYPE_CREDENTIAL);
+ initializeContainer(Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.onAttachedToWindowInternal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index f6375fce8b4e..c0e92e09c486 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertNotNull;
@@ -38,7 +40,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -109,28 +110,28 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Test
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
}
@Test
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
}
@Test
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
@@ -138,14 +139,14 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonError_whenDismissedByError() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
}
@Test
public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
}
@@ -153,7 +154,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
}
@@ -163,20 +164,20 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testShowInvoked_whenSystemRequested()
throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
}
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onBiometricAuthenticated();
verify(mDialog1).onAuthenticationSucceeded();
}
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
@@ -189,7 +190,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -202,7 +203,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnHelpInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final String helpMessage = "help";
mAuthController.onBiometricHelp(helpMessage);
@@ -214,7 +215,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testOnErrorInvoked_whenSystemRequested() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = 1;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -227,7 +228,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final int vendorCode = 0;
@@ -240,7 +241,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final int vendorCode = 0;
@@ -253,7 +254,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final int vendorCode = 0;
@@ -266,7 +267,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final int vendorCode = 0;
@@ -278,30 +279,24 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Test
- public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.hideAuthenticationDialog();
verify(mDialog1).dismissFromSystemServer();
- }
- @Test
- public void testClientNotified_whenDismissedBySystemServer() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
- mAuthController.hideAuthenticationDialog();
- verify(mDialog1).dismissFromSystemServer();
-
- assertNotNull(mAuthController.mCurrentDialog);
- assertNotNull(mAuthController.mReceiver);
+ // In this case, BiometricService sends the error to the client immediately, without
+ // doing a round trip to SystemUI.
+ assertNull(mAuthController.mCurrentDialog);
}
// Corner case tests
@Test
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
// First dialog should be dismissed without animation
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
@@ -312,7 +307,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testConfigurationPersists_whenOnConfigurationChanged() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
// Return that the UI is in "showing" state
@@ -342,7 +337,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testConfigurationPersists_whenBiometricFallbackToCredential() {
- showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC,
+ showDialog(Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
@@ -361,14 +356,14 @@ public class AuthControllerTest extends SysuiTestCase {
// Check that the new dialog was initialized to the credential UI.
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(mDialog2).show(any(), captor.capture());
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mAuthController.mLastBiometricPromptBundle
.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
}
@Test
public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -388,21 +383,21 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
mAuthController.onTryAgainPressed();
}
@Test
public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
mAuthController.onDeviceCredentialPressed();
}
@Test
public void testActionCloseSystemDialogs_dismissesDialogIfShowing() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mAuthController.mBroadcastReceiver.onReceive(mContext, intent);
waitForIdleSync();
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 22cb507d449f..0d88388742d2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -26,10 +26,12 @@ import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IAuthService;
+import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -43,6 +45,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.android.server.biometrics.face.FaceAuthenticator;
@@ -57,9 +60,6 @@ public class AuthService extends SystemService {
private static final String TAG = "AuthService";
private static final boolean DEBUG = false;
- private final boolean mHasFeatureFace;
- private final boolean mHasFeatureFingerprint;
- private final boolean mHasFeatureIris;
private final Injector mInjector;
private IBiometricService mBiometricService;
@@ -89,6 +89,16 @@ public class AuthService extends SystemService {
public void publishBinderService(AuthService service, IAuthService.Stub impl) {
service.publishBinderService(Context.AUTH_SERVICE, impl);
}
+
+ /**
+ * Allows to test with various device sensor configurations.
+ * @param context
+ * @return
+ */
+ @VisibleForTesting
+ public String[] getConfiguration(Context context) {
+ return context.getResources().getStringArray(R.array.config_biometric_sensors);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -119,17 +129,17 @@ public class AuthService extends SystemService {
}
@Override
- public int canAuthenticate(String opPackageName, int userId) throws RemoteException {
+ public int canAuthenticate(String opPackageName, int userId,
+ @Authenticators.Types int authenticators) throws RemoteException {
final int callingUserId = UserHandle.getCallingUserId();
- Slog.d(TAG, "canAuthenticate, userId: " + userId
- + ", callingUserId: " + callingUserId);
-
+ Slog.d(TAG, "canAuthenticate, userId: " + userId + ", callingUserId: " + callingUserId
+ + ", authenticators: " + authenticators);
if (userId != callingUserId) {
checkInternalPermission();
} else {
checkPermission();
}
- return mBiometricService.canAuthenticate(opPackageName, userId);
+ return mBiometricService.canAuthenticate(opPackageName, userId, authenticators);
}
@Override
@@ -169,47 +179,56 @@ public class AuthService extends SystemService {
mInjector = injector;
mImpl = new AuthServiceImpl();
final PackageManager pm = context.getPackageManager();
- mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
- mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
- mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
+ }
+
+ private void registerAuthenticator(SensorConfig config) throws RemoteException {
+
+ Slog.d(TAG, "Registering ID: " + config.mId
+ + " Modality: " + config.mModality
+ + " Strength: " + config.mStrength);
+
+ final IBiometricAuthenticator.Stub authenticator;
+
+ switch (config.mModality) {
+ case TYPE_FINGERPRINT:
+ authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
+ break;
+
+ case TYPE_FACE:
+ authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface(
+ ServiceManager.getService(Context.FACE_SERVICE)));
+ break;
+
+ case TYPE_IRIS:
+ authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface(
+ ServiceManager.getService(Context.IRIS_SERVICE)));
+ break;
+
+ default:
+ Slog.e(TAG, "Unknown modality: " + config.mModality);
+ return;
+ }
+
+ mBiometricService.registerAuthenticator(config.mId, config.mModality, config.mStrength,
+ authenticator);
}
@Override
public void onStart() {
mBiometricService = mInjector.getBiometricService();
- if (mHasFeatureFace) {
- final FaceAuthenticator faceAuthenticator = new FaceAuthenticator(
- IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
- try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(0, 0, TYPE_FACE, faceAuthenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- if (mHasFeatureFingerprint) {
- final FingerprintAuthenticator fingerprintAuthenticator = new FingerprintAuthenticator(
- IFingerprintService.Stub.asInterface(
- ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
- try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(1, 0, TYPE_FINGERPRINT,
- fingerprintAuthenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- if (mHasFeatureIris) {
- final IrisAuthenticator irisAuthenticator = new IrisAuthenticator(
- IIrisService.Stub.asInterface(ServiceManager.getService(Context.IRIS_SERVICE)));
+ final String[] configs = mInjector.getConfiguration(getContext());
+
+ for (int i = 0; i < configs.length; i++) {
try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(2, 0, TYPE_IRIS, irisAuthenticator);
+ registerAuthenticator(new SensorConfig(configs[i]));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
+
}
+
mInjector.publishBinderService(this, mImpl);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 5d3679389b48..0f51e39d128a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -23,15 +23,16 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UserSwitchObserver;
+import android.app.trust.ITrustManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -78,7 +79,7 @@ import java.util.Random;
*/
public class BiometricService extends SystemService {
- private static final String TAG = "BiometricService";
+ static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
@@ -220,11 +221,15 @@ public class BiometricService extends SystemService {
IStatusBarService mStatusBarService;
@VisibleForTesting
KeyStore mKeyStore;
+ @VisibleForTesting
+ ITrustManager mTrustManager;
// Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
// polymorphism :/
final ArrayList<AuthenticatorWrapper> mAuthenticators = new ArrayList<>();
+ BiometricStrengthController mBiometricStrengthController;
+
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
@VisibleForTesting
@@ -345,17 +350,50 @@ public class BiometricService extends SystemService {
@VisibleForTesting
public static final class AuthenticatorWrapper {
public final int id;
- public final int strength;
+ public final int OEMStrength; // strength as configured by the OEM
+ private int updatedStrength; // strength updated by BiometricStrengthController
public final int modality;
public final IBiometricAuthenticator impl;
- AuthenticatorWrapper(int id, int strength, int modality,
+ AuthenticatorWrapper(int id, int modality, int strength,
IBiometricAuthenticator impl) {
this.id = id;
- this.strength = strength;
this.modality = modality;
+ this.OEMStrength = strength;
+ this.updatedStrength = strength;
this.impl = impl;
}
+
+ /**
+ * Returns the actual strength, taking any updated strengths into effect. Since more bits
+ * means lower strength, the resulting strength is never stronger than the OEM's configured
+ * strength.
+ * @return a bitfield, see {@link Authenticators}
+ */
+ public int getActualStrength() {
+ return OEMStrength | updatedStrength;
+ }
+
+ /**
+ * Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
+ * is checked.
+ * @param newStrength
+ */
+ public void updateStrength(int newStrength) {
+ String log = "updateStrength: Before(" + toString() + ")";
+ updatedStrength = newStrength;
+ log += " After(" + toString() + ")";
+ Slog.d(TAG, log);
+ }
+
+ @Override
+ public String toString() {
+ return "ID(" + id + ")"
+ + " OEMStrength: " + OEMStrength
+ + " updatedStrength: " + updatedStrength
+ + " modality " + modality
+ + " authenticator: " + impl;
+ }
}
@VisibleForTesting
@@ -606,14 +644,14 @@ public class BiometricService extends SystemService {
return;
}
- if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
- checkInternalPermission();
+ if (!Utils.isValidAuthenticatorConfig(bundle)) {
+ throw new SecurityException("Invalid authenticator configuration");
}
Utils.combineAuthenticatorBundles(bundle);
- // Check the usage of this in system server. Need to remove this check if it becomes
- // a public API.
+ // Check the usage of this in system server. Need to remove this check if it becomes a
+ // public API.
final boolean useDefaultTitle =
bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
if (useDefaultTitle) {
@@ -651,9 +689,11 @@ public class BiometricService extends SystemService {
}
@Override // Binder call
- public int canAuthenticate(String opPackageName, int userId) {
+ public int canAuthenticate(String opPackageName, int userId,
+ @Authenticators.Types int authenticators) {
Slog.d(TAG, "canAuthenticate: User=" + userId
- + ", Caller=" + UserHandle.getCallingUserId());
+ + ", Caller=" + UserHandle.getCallingUserId()
+ + ", Authenticators=" + authenticators);
if (userId != UserHandle.getCallingUserId()) {
checkInternalPermission();
@@ -661,16 +701,39 @@ public class BiometricService extends SystemService {
checkPermission();
}
+
+ if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ throw new SecurityException("Invalid authenticator configuration");
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+
+ int biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
final long ident = Binder.clearCallingIdentity();
- int error;
try {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
- opPackageName);
- error = result.second;
+ biometricConstantsResult =
+ checkAndGetAuthenticators(userId, bundle, opPackageName).second;
+ if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS
+ && Utils.isDeviceCredentialAllowed(bundle)) {
+ // If there's an issue with biometrics, but device credential is allowed and
+ // set up, return SUCCESS. If device credential isn't set up either, return
+ // ERROR_NO_DEVICE_CREDENTIAL.
+ if (mTrustManager.isDeviceSecure(userId)) {
+ biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS;
+ } else {
+ biometricConstantsResult =
+ BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
+ }
+ }
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
- return error;
+
+ return Utils.biometricConstantsToBiometricManager(biometricConstantsResult);
}
@Override
@@ -693,11 +756,46 @@ public class BiometricService extends SystemService {
}
@Override
- public void registerAuthenticator(int id, int strength, int modality,
+ public void registerAuthenticator(int id, int modality, int strength,
IBiometricAuthenticator authenticator) {
checkInternalPermission();
- mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
+ Slog.d(TAG, "Registering ID: " + id
+ + " Modality: " + modality
+ + " Strength: " + strength);
+
+ if (authenticator == null) {
+ throw new IllegalArgumentException("Authenticator must not be null."
+ + " Did you forget to modify the core/res/res/values/xml overlay for"
+ + " config_biometric_sensors?");
+ }
+
+ if (strength != Authenticators.BIOMETRIC_STRONG
+ && strength != Authenticators.BIOMETRIC_WEAK) {
+ throw new IllegalStateException("Unsupported strength");
+ }
+
+ for (AuthenticatorWrapper wrapper : mAuthenticators) {
+ if (wrapper.id == id) {
+ throw new IllegalStateException("Cannot register duplicate authenticator");
+ }
+ }
+
+ // This happens infrequently enough, not worth caching.
+ final String[] configs = mInjector.getConfiguration(getContext());
+ boolean idFound = false;
+ for (int i = 0; i < configs.length; i++) {
+ SensorConfig config = new SensorConfig(configs[i]);
+ if (config.mId == id) {
+ idFound = true;
+ break;
+ }
+ }
+ if (!idFound) {
+ throw new IllegalStateException("Cannot register unknown id");
+ }
+
+ mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator));
}
@Override // Binder call
@@ -771,6 +869,11 @@ public class BiometricService extends SystemService {
}
@VisibleForTesting
+ public ITrustManager getTrustManager() {
+ return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
+ }
+
+ @VisibleForTesting
public IStatusBarService getStatusBarService() {
return IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -805,6 +908,25 @@ public class BiometricService extends SystemService {
public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
}
+
+ /**
+ * Allows to mock BiometricStrengthController for testing.
+ */
+ @VisibleForTesting
+ public BiometricStrengthController getBiometricStrengthController(
+ BiometricService service) {
+ return new BiometricStrengthController(service);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ * @param context System Server context
+ * @return the sensor configuration from core/res/res/values/config.xml
+ */
+ @VisibleForTesting
+ public String[] getConfiguration(Context context) {
+ return context.getResources().getStringArray(R.array.config_biometric_sensors);
+ }
}
/**
@@ -849,7 +971,10 @@ public class BiometricService extends SystemService {
public void onStart() {
mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
+ mTrustManager = mInjector.getTrustManager();
mInjector.publishBinderService(this, mImpl);
+ mBiometricStrengthController = mInjector.getBiometricStrengthController(this);
+ mBiometricStrengthController.startListening();
}
/**
@@ -857,25 +982,36 @@ public class BiometricService extends SystemService {
* returns errors through the callback (no biometric feature, hardware not detected, no
* templates enrolled, etc). This service must not start authentication if errors are sent.
*
- * @Returns A pair [Modality, Error] with Modality being one of
+ * @param userId the user to check for
+ * @param bundle passed from {@link BiometricPrompt}
+ * @param opPackageName see {@link android.app.AppOpsManager}
+ *
+ * @return A pair [Modality, Error] with Modality being one of
* {@link BiometricAuthenticator#TYPE_NONE},
* {@link BiometricAuthenticator#TYPE_FINGERPRINT},
* {@link BiometricAuthenticator#TYPE_IRIS},
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
+ *
+ * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers
*/
- private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
- // No biometric features, send error
- if (mAuthenticators.isEmpty()) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+ private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
+ String opPackageName) throws RemoteException {
+ if (!Utils.isBiometricAllowed(bundle)
+ && Utils.isDeviceCredentialAllowed(bundle)
+ && !mTrustManager.isDeviceSecure(userId)) {
+ // If only device credential is being checked, and the user doesn't have one set up
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL);
}
// Assuming that authenticators are listed in priority-order, the rest of this function
- // will go through and find the first authenticator that's available, enrolled, and enabled.
- // The tricky part is returning the correct error. Error strings that are modality-specific
- // should also respect the priority-order.
+ // will attempt to find the first authenticator that's as strong or stronger than the
+ // requested strength, available, enrolled, and enabled. The tricky part is returning the
+ // correct error. Error strings that are modality-specific should also respect the
+ // priority-order.
- // Find first authenticator that's detected, enrolled, and enabled.
+ // Find first authenticator that's strong enough, detected, enrolled, and enabled.
+ boolean hasSufficientStrength = false;
boolean isHardwareDetected = false;
boolean hasTemplatesEnrolled = false;
boolean enabledForApps = false;
@@ -883,13 +1019,16 @@ public class BiometricService extends SystemService {
int modality = TYPE_NONE;
int firstHwAvailable = TYPE_NONE;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- modality = authenticator.modality;
- try {
+ final int actualStrength = authenticator.getActualStrength();
+ final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
+ if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
+ hasSufficientStrength = true;
+ modality = authenticator.modality;
if (authenticator.impl.isHardwareDetected(opPackageName)) {
isHardwareDetected = true;
if (firstHwAvailable == TYPE_NONE) {
- // Store the first one since we want to return the error in correct priority
- // order.
+ // Store the first one since we want to return the error in correct
+ // priority order.
firstHwAvailable = modality;
}
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -900,18 +1039,18 @@ public class BiometricService extends SystemService {
}
}
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
}
}
- Slog.d(TAG, "checkAndGetBiometricModality: user=" + userId
+ Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
+ " isHardwareDetected=" + isHardwareDetected
+ " hasTemplatesEnrolled=" + hasTemplatesEnrolled
+ " enabledForApps=" + enabledForApps);
// Check error conditions
- if (!isHardwareDetected) {
+ if (!hasSufficientStrength) {
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+ } else if (!isHardwareDetected) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
} else if (!hasTemplatesEnrolled) {
// Return the modality here so the correct error string can be sent. This error is
@@ -1107,13 +1246,16 @@ public class BiometricService extends SystemService {
// SystemUI handles transition from biometric to device credential.
mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mStatusBarService.onBiometricError(modality, error, vendorCode);
+ } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+ mStatusBarService.hideAuthenticationDialog();
+ // TODO: If multiple authenticators are simultaneously running, this will
+ // need to be modified. Send the error to the client here, instead of doing
+ // a round trip to SystemUI.
+ mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
+ mCurrentAuthSession = null;
} else {
mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
- if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
- mStatusBarService.hideAuthenticationDialog();
- } else {
- mStatusBarService.onBiometricError(modality, error, vendorCode);
- }
+ mStatusBarService.onBiometricError(modality, error, vendorCode);
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
@@ -1135,10 +1277,11 @@ public class BiometricService extends SystemService {
// If any error is received while preparing the auth session (lockout, etc),
// and if device credential is allowed, just show the credential UI.
if (mPendingAuthSession.isAllowDeviceCredential()) {
- int authenticators = mPendingAuthSession.mBundle
- .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+ @Authenticators.Types int authenticators =
+ mPendingAuthSession.mBundle.getInt(
+ BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
// Disallow biometric and notify SystemUI to show the authentication prompt.
- authenticators &= ~Authenticator.TYPE_BIOMETRIC;
+ authenticators &= ~Authenticators.BIOMETRIC_WEAK;
mPendingAuthSession.mBundle.putInt(
BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
authenticators);
@@ -1355,31 +1498,43 @@ public class BiometricService extends SystemService {
int callingUid, int callingPid, int callingUserId) {
mHandler.post(() -> {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
- opPackageName);
- final int modality = result.first;
- final int error = result.second;
-
- final boolean credentialAllowed = Utils.isDeviceCredentialAllowed(bundle);
-
- if (error != BiometricConstants.BIOMETRIC_SUCCESS && credentialAllowed) {
- // If there's a problem but device credential is allowed, only show credential UI.
- bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
- Authenticator.TYPE_CREDENTIAL);
- } else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
- // Check for errors, notify callback, and return
- try {
- receiver.onError(modality, error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- return;
+ int modality = TYPE_NONE;
+ int result;
+
+ try {
+ final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle,
+ opPackageName);
+ modality = pair.first;
+ result = pair.second;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
- // Start preparing for authentication. Authentication starts when
- // all modalities requested have invoked onReadyForAuthentication.
- authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
- callingUid, callingPid, callingUserId, modality);
+ try {
+ if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) {
+ // If the app allowed device credential but the user hasn't set it up yet,
+ // return this error.
+ receiver.onError(modality, result, 0 /* vendorCode */);
+ } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) {
+ if (Utils.isDeviceCredentialAllowed(bundle)) {
+ // If there's a problem with biometrics but device credential is allowed,
+ // only show credential UI.
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
+ Authenticators.DEVICE_CREDENTIAL);
+ authenticateInternal(token, sessionId, userId, receiver, opPackageName,
+ bundle, callingUid, callingPid, callingUserId, modality);
+ } else {
+ receiver.onError(modality, result, 0 /* vendorCode */);
+ }
+ } else {
+ // BIOMETRIC_SUCCESS, proceed to authentication
+ authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
+ callingUid, callingPid, callingUserId, modality);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
});
}
@@ -1407,7 +1562,8 @@ public class BiometricService extends SystemService {
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
- final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
+ final @Authenticators.Types int authenticators = bundle.getInt(
+ BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
Slog.d(TAG, "Creating auth session. Modality: " + modality
+ ", cookie: " + cookie
+ ", authenticators: " + authenticators);
@@ -1415,7 +1571,7 @@ public class BiometricService extends SystemService {
// If it's only device credential, we don't need to wait - LockSettingsService is
// always ready to check credential (SystemUI invokes that path).
- if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
+ if ((authenticators & ~Authenticators.DEVICE_CREDENTIAL) != 0) {
modalities.put(modality, cookie);
}
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
@@ -1423,7 +1579,7 @@ public class BiometricService extends SystemService {
modality, requireConfirmation);
try {
- if (authenticators == Authenticator.TYPE_CREDENTIAL) {
+ if (authenticators == Authenticators.DEVICE_CREDENTIAL) {
mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
@@ -1438,9 +1594,13 @@ public class BiometricService extends SystemService {
} else {
mPendingAuthSession.mState = STATE_AUTH_CALLED;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- authenticator.impl.prepareForAuthentication(requireConfirmation, token,
- sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
- callingPid, callingUserId);
+ // TODO(b/141025588): use ids instead of modalities to avoid ambiguity.
+ if (authenticator.modality == modality) {
+ authenticator.impl.prepareForAuthentication(requireConfirmation, token,
+ sessionId, userId, mInternalReceiver, opPackageName, cookie,
+ callingUid, callingPid, callingUserId);
+ break;
+ }
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
new file mode 100644
index 000000000000..4e16189e3ad1
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 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 com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class for maintaining and updating the strengths for biometric sensors. Strengths can only
+ * be downgraded from the device's default, and never upgraded.
+ */
+public class BiometricStrengthController implements DeviceConfig.OnPropertiesChangedListener {
+ private static final String TAG = "BiometricStrengthController";
+
+ private final BiometricService mService;
+
+ /**
+ * Flag stored in the DeviceConfig API: biometric modality strengths to downgrade.
+ * This is encoded as a key:value list, separated by comma, e.g.
+ *
+ * "id1:strength1,id2:strength2,id3:strength3"
+ *
+ * where strength is one of the values defined in
+ * {@link android.hardware.biometrics.Authenticators}
+ *
+ * Both id and strength should be int, otherwise Exception will be thrown when parsing and the
+ * downgrade will fail.
+ */
+ private static final String KEY_BIOMETRIC_STRENGTHS = "biometric_strengths";
+
+ /**
+ * Default (no-op) value of the flag KEY_BIOMETRIC_STRENGTHS
+ */
+ public static final String DEFAULT_BIOMETRIC_STRENGTHS = null;
+
+ BiometricStrengthController(@NonNull BiometricService service) {
+ mService = service;
+ }
+
+ void startListening() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BackgroundThread.getExecutor(), this);
+ updateStrengths();
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (KEY_BIOMETRIC_STRENGTHS.equals(name)) {
+ updateStrengths();
+ }
+ }
+ }
+
+ /**
+ * Updates the strengths of authenticators in BiometricService if a matching ID's configuration
+ * has been changed.
+ */
+ private void updateStrengths() {
+ final Map<Integer, Integer> idToStrength = getIdToStrengthMap();
+ if (idToStrength == null) {
+ return;
+ }
+
+ for (BiometricService.AuthenticatorWrapper authenticator : mService.mAuthenticators) {
+ final int id = authenticator.id;
+ if (idToStrength.containsKey(id)) {
+ final int newStrength = idToStrength.get(id);
+ authenticator.updateStrength(newStrength);
+ }
+ }
+ }
+
+ /**
+ * @return a map of <ID, Strength>
+ */
+ private Map<Integer, Integer> getIdToStrengthMap() {
+ final String flags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BIOMETRICS,
+ KEY_BIOMETRIC_STRENGTHS, DEFAULT_BIOMETRIC_STRENGTHS);
+ if (flags == null || flags.isEmpty()) {
+ Slog.d(TAG, "Flags are null or empty");
+ return null;
+ }
+
+ Map<Integer, Integer> map = new HashMap<>();
+ try {
+ for (String item : flags.split(",")) {
+ String[] elems = item.split(":");
+ final int id = Integer.parseInt(elems[0]);
+ final int strength = Integer.parseInt(elems[1]);
+ map.put(id, strength);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Can't parse flag: " + flags);
+ map = null;
+ }
+ return map;
+ }
+}
diff --git a/core/java/android/hardware/biometrics/Authenticator.java b/services/core/java/com/android/server/biometrics/SensorConfig.java
index 6d7e7488f2d0..9eda6da26967 100644
--- a/core/java/android/hardware/biometrics/Authenticator.java
+++ b/services/core/java/com/android/server/biometrics/SensorConfig.java
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package android.hardware.biometrics;
+package com.android.server.biometrics;
/**
- * Type of authenticators defined on a granularity that the BiometricManager / BiometricPrompt
- * supports.
- * @hide
+ * Parsed sensor config. See core/res/res/values/config.xml config_biometric_sensors
*/
-public class Authenticator {
-
- /**
- * Device credential, e.g. Pin/Pattern/Password.
- */
- public static final int TYPE_CREDENTIAL = 1 << 0;
- /**
- * Encompasses all biometrics on the device, e.g. Fingerprint/Iris/Face.
- */
- public static final int TYPE_BIOMETRIC = 1 << 1;
+class SensorConfig {
+ final int mId;
+ final int mModality;
+ final int mStrength;
+ public SensorConfig(String config) {
+ String[] elems = config.split(":");
+ mId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index ed5f9de01bcf..19f535876274 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,15 +16,17 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
-
-import com.android.internal.annotations.VisibleForTesting;
+import android.util.Slog;
public class Utils {
public static boolean isDebugEnabled(Context context, int targetUserId) {
@@ -45,41 +47,167 @@ public class Utils {
}
/**
- * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
- * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible
- * enough.
+ * Combines {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
+ * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible enough.
*/
public static void combineAuthenticatorBundles(Bundle bundle) {
- boolean biometricEnabled = true; // enabled by default
- boolean credentialEnabled = bundle.getBoolean(
- BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
- if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
- final int authenticatorFlags =
- bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
- biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0;
- // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together
- // is not supported. Default to overwriting.
- credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0;
- }
-
+ // Cache and remove explicit ALLOW_DEVICE_CREDENTIAL boolean flag from the bundle.
+ final boolean deviceCredentialAllowed =
+ bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
- int authenticators = 0;
- if (biometricEnabled) {
- authenticators |= Authenticator.TYPE_BIOMETRIC;
- }
- if (credentialEnabled) {
- authenticators |= Authenticator.TYPE_CREDENTIAL;
+ final @Authenticators.Types int authenticators;
+ if (bundle.containsKey(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)) {
+ // Ignore ALLOW_DEVICE_CREDENTIAL flag if AUTH_TYPES_ALLOWED is defined.
+ authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+ } else {
+ // Otherwise, use ALLOW_DEVICE_CREDENTIAL flag along with Weak+ biometrics by default.
+ authenticators = deviceCredentialAllowed
+ ? Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK
+ : Authenticators.BIOMETRIC_WEAK;
}
+
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
}
/**
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return true if device credential is allowed.
+ */
+ public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) {
+ return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
+ }
+
+ /**
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
- * @return true if device credential allowed.
+ * @return true if device credential is allowed.
*/
public static boolean isDeviceCredentialAllowed(Bundle bundle) {
+ return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+ */
+ public static int getPublicBiometricStrength(@Authenticators.Types int authenticators) {
+ // Only biometrics WEAK and above are allowed to integrate with the public APIs.
+ return authenticators & Authenticators.BIOMETRIC_WEAK;
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+ * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+ */
+ public static int getPublicBiometricStrength(Bundle bundle) {
+ return getPublicBiometricStrength(
+ bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+ * @return true if biometric authentication is allowed.
+ */
+ public static boolean isBiometricAllowed(Bundle bundle) {
+ return getPublicBiometricStrength(bundle) != 0;
+ }
+
+ /**
+ * @param sensorStrength the strength of the sensor
+ * @param requestedStrength the strength that it must meet
+ * @return true only if the sensor is at least as strong as the requested strength
+ */
+ public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) {
+ // If the authenticator contains bits outside of the requested strength, it is too weak.
+ return (~requestedStrength & sensorStrength) == 0;
+ }
+
+ /**
+ * Checks if the authenticator configuration is a valid combination of the public APIs
+ * @param bundle
+ * @return
+ */
+ public static boolean isValidAuthenticatorConfig(Bundle bundle) {
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
- return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+ return isValidAuthenticatorConfig(authenticators);
+ }
+
+ /**
+ * Checks if the authenticator configuration is a valid combination of the public APIs
+ * @param authenticators
+ * @return
+ */
+ public static boolean isValidAuthenticatorConfig(int authenticators) {
+ // The caller is not required to set the authenticators. But if they do, check the below.
+ if (authenticators == 0) {
+ return true;
+ }
+
+ // Check if any of the non-biometric and non-credential bits are set. If so, this is
+ // invalid.
+ final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH);
+ if ((authenticators & testBits) != 0) {
+ Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+ + " Authenticators: " + authenticators);
+ return false;
+ }
+
+ // Check that biometrics bits are either NONE, WEAK, or STRONG. If NONE, DEVICE_CREDENTIAL
+ // should be set.
+ final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
+ if (biometricBits == Authenticators.EMPTY_SET
+ && isDeviceCredentialAllowed(authenticators)) {
+ return true;
+ } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
+ return true;
+ } else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
+ return true;
+ }
+
+ Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: "
+ + authenticators);
+ // Non-supported biometric flags are being used
+ return false;
+ }
+
+ /**
+ * Converts error codes from BiometricConstants, which are used in most of the internal plumbing
+ * and eventually returned to {@link BiometricPrompt.AuthenticationCallback} to public
+ * {@link BiometricManager} constants, which are used by APIs such as
+ * {@link BiometricManager#canAuthenticate(int)}
+ *
+ * @param biometricConstantsCode see {@link BiometricConstants}
+ * @return see {@link BiometricManager}
+ */
+ public static int biometricConstantsToBiometricManager(int biometricConstantsCode) {
+ final int biometricManagerCode;
+
+ switch (biometricConstantsCode) {
+ case BiometricConstants.BIOMETRIC_SUCCESS:
+ biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
+ case BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
+ break;
+ default:
+ Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
+ }
+ return biometricManagerCode;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 106a7238e399..d38c80cdabe0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -20,6 +20,7 @@ import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -65,8 +66,16 @@ public class AuthServiceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
+ // Dummy test config
+ final String[] config = {
+ "0:2:15", // ID0:Fingerprint:Strong
+ "1:4:15", // ID1:Iris:Strong
+ "2:8:15", // ID2:Face:Strong
+ };
+
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mInjector.getBiometricService()).thenReturn(mBiometricService);
+ when(mInjector.getConfiguration(any())).thenReturn(config);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true);
@@ -76,8 +85,7 @@ public class AuthServiceTest {
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
- public void testAuthenticate_callsBiometricServiceAuthenticate() throws
- Exception {
+ public void testAuthenticate_callsBiometricServiceAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -104,22 +112,25 @@ public class AuthServiceTest {
}
@Test
- public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws
- Exception {
+ public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
final int userId = 0;
final int expectedResult = BIOMETRIC_SUCCESS;
- when(mBiometricService.canAuthenticate(anyString(), anyInt())).thenReturn(expectedResult);
+ final int authenticators = 0;
+ when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt()))
+ .thenReturn(expectedResult);
- final int result = mAuthService.mImpl.canAuthenticate(TEST_OP_PACKAGE_NAME, userId);
+ final int result = mAuthService.mImpl
+ .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).canAuthenticate(
eq(TEST_OP_PACKAGE_NAME),
- eq(userId));
+ eq(userId),
+ eq(authenticators));
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 4ced421be63a..211fc4db6211 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,12 +16,14 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,12 +36,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
+import android.app.trust.ITrustManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
@@ -81,8 +84,6 @@ public class BiometricServiceTest {
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
- private static final int STRENGTH_STRONG = 1;
-
private BiometricService mBiometricService;
@Mock
@@ -101,6 +102,8 @@ public class BiometricServiceTest {
IBiometricAuthenticator mFingerprintAuthenticator;
@Mock
IBiometricAuthenticator mFaceAuthenticator;
+ @Mock
+ ITrustManager mTrustManager;
@Before
public void setUp() {
@@ -111,10 +114,13 @@ public class BiometricServiceTest {
when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
- when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
- mock(BiometricService.SettingObserver.class));
+ when(mInjector.getSettingObserver(any(), any(), any()))
+ .thenReturn(mock(BiometricService.SettingObserver.class));
when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
+ when(mInjector.getBiometricStrengthController(any()))
+ .thenReturn(mock(BiometricStrengthController.class));
+ when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -122,6 +128,56 @@ public class BiometricServiceTest {
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+
+ final String[] config = {
+ "0:2:15", // ID0:Fingerprint:Strong
+ "1:8:15", // ID1:Face:Strong
+ "2:4:255", // ID2:Iris:Weak
+ };
+
+ when(mInjector.getConfiguration(any())).thenReturn(config);
+ }
+
+ @Test
+ public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
+ throws Exception {
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL),
+ eq(0 /* vendorCode */));
+ }
+
+ @Test
+ public void testAuthenticate_credentialAllowedAndSetup_callsSystemUI() throws Exception {
+ // When no biometrics are enrolled, but credentials are set up, status bar should be
+ // invoked right away with showAuthenticationDialog
+
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL);
+ waitForIdle();
+
+ assertNull(mBiometricService.mPendingAuthSession);
+ // StatusBar showBiometricDialog invoked
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(0),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
}
@Test
@@ -131,7 +187,7 @@ public class BiometricServiceTest {
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -145,12 +201,12 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
-
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -159,6 +215,54 @@ public class BiometricServiceTest {
}
@Test
+ public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(0 /* vendorCode */));
+ }
+
+ @Test
+ public void testAuthenticate_picksStrongIfAvailable() throws Exception {
+ // If both strong and weak are available, and the caller requires STRONG, authentication
+ // is able to proceed.
+
+ final int[] modalities = new int[] {
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ BiometricAuthenticator.TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ verify(mReceiver1, never()).onError(
+ anyInt(),
+ anyInt(),
+ anyInt() /* vendorCode */);
+
+ // StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(false) /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
Exception {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
@@ -166,11 +270,12 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -181,18 +286,12 @@ public class BiometricServiceTest {
@Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-
- mBiometricService = new BiometricService(mContext, mInjector);
- mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FACE, mFaceAuthenticator);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -205,7 +304,7 @@ public class BiometricServiceTest {
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(true);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
@@ -225,7 +324,7 @@ public class BiometricServiceTest {
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(false) /* requireConfirmation */,
@@ -242,11 +341,11 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
// Start testing the happy path
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
// Creates a pending auth session with the correct initial states
@@ -314,15 +413,16 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, true /* allowDeviceCredential */);
+ true /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mBundle
.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -336,9 +436,9 @@ public class BiometricServiceTest {
@Test
public void testAuthenticate_happyPathWithConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, false /* allowDeviceCredential */);
+ true /* requireConfirmation */, null /* authenticators */);
// Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
// sent to KeyStore yet
@@ -362,9 +462,9 @@ public class BiometricServiceTest {
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
@@ -381,9 +481,9 @@ public class BiometricServiceTest {
@Test
public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
@@ -398,53 +498,10 @@ public class BiometricServiceTest {
}
@Test
- public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
- Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
-
- // Create a new pending auth session but don't start it yet. HAL contract is that previous
- // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session,
- // sending ERROR_CANCELED to the current auth session, and then having the second one
- // onReadyForAuthentication.
- invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
- waitForIdle();
-
- assertEquals(mBiometricService.mCurrentAuthSession.mState,
- BiometricService.STATE_AUTH_STARTED);
- mBiometricService.mInternalReceiver.onError(
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
- BiometricAuthenticator.TYPE_FINGERPRINT,
- BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
- waitForIdle();
-
- // Auth session doesn't become null until SystemUI responds that the animation is completed
- assertNotNull(mBiometricService.mCurrentAuthSession);
- // ERROR_CANCELED is not sent until SystemUI responded that animation is completed
- verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
-
- // SystemUI dialog closed
- verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
-
- // After SystemUI notifies that the animation has completed
- mBiometricService.mInternalReceiver
- .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
- waitForIdle();
- verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
- eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
- eq(0 /* vendorCode */));
- assertNull(mBiometricService.mCurrentAuthSession);
- }
-
- @Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -491,9 +548,9 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -524,9 +581,9 @@ public class BiometricServiceTest {
// For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
// until SystemUI notifies us that the dialog is dismissed at which point the current
// session is done.
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -558,9 +615,10 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
mBiometricService.mInternalReceiver.onError(
@@ -576,7 +634,7 @@ public class BiometricServiceTest {
assertNotNull(mBiometricService.mCurrentAuthSession);
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mBundle.getInt(
BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -591,9 +649,9 @@ public class BiometricServiceTest {
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
waitForIdle();
mBiometricService.mInternalReceiver.onError(
@@ -609,58 +667,64 @@ public class BiometricServiceTest {
}
@Test
- public void testCombineAuthenticatorBundle_keyAllowDeviceCredentialAlwaysRemoved() {
- Bundle bundle;
- int authenticators;
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+ final boolean allowDeviceCredential = false;
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = true
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
- authenticators = Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC;
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
Utils.combineAuthenticatorBundles(bundle);
+
assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
-
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = true
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
- authenticators = Authenticator.TYPE_BIOMETRIC;
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
Utils.combineAuthenticatorBundles(bundle);
+
assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
-
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- authenticators = Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL;
- bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+ final boolean allowDeviceCredential = true;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+ final Bundle bundle = new Bundle();
+
Utils.combineAuthenticatorBundles(bundle);
+
assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.BIOMETRIC_WEAK);
}
@Test
public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
mBiometricService.mInternalReceiver.onDeviceCredentialPressed();
waitForIdle();
@@ -683,9 +747,10 @@ public class BiometricServiceTest {
@Test
public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
assertEquals(BiometricService.STATE_AUTH_STARTED,
mBiometricService.mCurrentAuthSession.mState);
@@ -707,9 +772,9 @@ public class BiometricServiceTest {
@Test
public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
assertEquals(BiometricService.STATE_AUTH_STARTED,
mBiometricService.mCurrentAuthSession.mState);
@@ -732,9 +797,9 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
@@ -755,9 +820,9 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -781,9 +846,9 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -806,9 +871,9 @@ public class BiometricServiceTest {
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, false /* allowDeviceCredential */);
+ true /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
true /* requireConfirmation */,
@@ -835,9 +900,9 @@ public class BiometricServiceTest {
@Test
public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAcquired(
FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
@@ -851,26 +916,354 @@ public class BiometricServiceTest {
BiometricService.STATE_AUTH_STARTED);
}
+ @Test
+ public void testCancel_whenAuthenticating() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, null /* authenticators */);
+
+ mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+ TEST_PACKAGE_NAME);
+ waitForIdle();
+
+ // Pretend that the HAL has responded to cancel with ERROR_CANCELED
+ mBiometricService.mInternalReceiver.onError(getCookieForCurrentSession(
+ mBiometricService.mCurrentAuthSession), BiometricAuthenticator.TYPE_FINGERPRINT,
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
+ waitForIdle();
+
+ // Hides system dialog and invokes the onError callback
+ verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+ eq(0 /* vendorCode */));
+ verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+ }
+
+ @Test
+ public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
+ // When only biometric is requested, and sensor is strong enough
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+ // When only biometric is requested, and sensor is not strong enough
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and sensor is not strong enough
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // Credential requested but not set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+
+ // Credential requested and set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
+ // With credential set up, test the following.
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ false /* enrolled */);
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and credential is not set up
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and credential is set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testAuthenticatorActualStrength() {
+ // Tuple of OEM config, updatedStrength, and expectedStrength
+ final int[][] testCases = {
+ // Downgrades to the specified strength
+ {Authenticators.BIOMETRIC_STRONG, Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_WEAK},
+
+ // Cannot be upgraded
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG,
+ Authenticators.BIOMETRIC_WEAK},
+
+ // Downgrades to convenience
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_CONVENIENCE,
+ Authenticators.BIOMETRIC_CONVENIENCE},
+
+ // EMPTY_SET does not modify specified strength
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.EMPTY_SET,
+ Authenticators.BIOMETRIC_WEAK},
+ };
+
+ for (int i = 0; i < testCases.length; i++) {
+ final BiometricService.AuthenticatorWrapper authenticator =
+ new BiometricService.AuthenticatorWrapper(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ testCases[i][0],
+ null /* impl */);
+ authenticator.updateStrength(testCases[i][1]);
+ assertEquals(testCases[i][2], authenticator.getActualStrength());
+ }
+ }
+
+ @Test
+ public void testWithDowngradedAuthenticator() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ final int testId = 0;
+
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(testId /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
+
+ // Downgrade the authenticator
+ for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
+ if (wrapper.id == testId) {
+ wrapper.updateStrength(Authenticators.BIOMETRIC_WEAK);
+ }
+ }
+
+ // STRONG-only auth is not available
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(0) /* vendorCode */);
+
+ // Request for weak auth works
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_WEAK;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+
+ // Requesting strong and credential, when credential is setup
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_NONE /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithUnknownId_throwsIllegalStateException() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 100 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithUnsupportedStrength_throwsIllegalStateException()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // Only STRONG and WEAK are supported. Let's enforce that CONVENIENCE cannot be
+ // registered. If there is a compelling reason, we can remove this constraint.
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */,
+ Authenticators.BIOMETRIC_CONVENIENCE /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */,
+ Authenticators.BIOMETRIC_STRONG /* strength */,
+ null /* authenticator */);
+ }
+
+ @Test
+ public void testRegistrationHappyPath_isOk() throws Exception {
+ // This is being tested in many of the other cases, but here's the base case.
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ for (String s : mInjector.getConfiguration(null)) {
+ SensorConfig config = new SensorConfig(s);
+ mBiometricService.mImpl.registerAuthenticator(config.mId, config.mModality,
+ config.mStrength, mFingerprintAuthenticator);
+ }
+ }
+
// Helper methods
- private void setupAuthForOnly(int modality) throws RemoteException {
+ private int invokeCanAuthenticate(BiometricService service, int authenticators)
+ throws Exception {
+ return service.mImpl.canAuthenticate(TEST_PACKAGE_NAME, 0 /* userId */, authenticators);
+ }
+
+ private void setupAuthForOnly(int modality, int strength) throws Exception {
+ setupAuthForOnly(modality, strength, true /* enrolled */);
+ }
+
+ // TODO: Reconcile the registration strength with the injector
+ private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
- if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
- when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(enrolled);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
mFingerprintAuthenticator);
- } else if (modality == BiometricAuthenticator.TYPE_FACE) {
- when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ }
+
+ if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+ mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
mFaceAuthenticator);
- } else {
- fail("Unknown modality: " + modality);
+ }
+ }
+
+ // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
+ // all tests.
+ private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+
+ assertEquals(modalities.length, strengths.length);
+
+ for (int i = 0; i < modalities.length; i++) {
+ final int modality = modalities[i];
+ final int strength = strengths[i];
+
+ if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
+ mFingerprintAuthenticator);
+ }
+
+ if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
+ mFaceAuthenticator);
+ }
}
}
@@ -885,9 +1278,9 @@ public class BiometricServiceTest {
private void invokeAuthenticateAndStart(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
- boolean allowDeviceCredential) throws Exception {
+ Integer authenticators) throws Exception {
// Request auth, creates a pending session
- invokeAuthenticate(service, receiver, requireConfirmation, allowDeviceCredential);
+ invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -908,23 +1301,24 @@ public class BiometricServiceTest {
private static void invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
- boolean allowDeviceCredential) throws Exception {
+ Integer authenticators) throws Exception {
service.authenticate(
new Binder() /* token */,
0 /* sessionId */,
0 /* userId */,
receiver,
TEST_PACKAGE_NAME /* packageName */,
- createTestBiometricPromptBundle(requireConfirmation, allowDeviceCredential));
+ createTestBiometricPromptBundle(requireConfirmation, authenticators));
}
- private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation,
- boolean allowDeviceCredential) {
+ private static Bundle createTestBiometricPromptBundle(
+ boolean requireConfirmation,
+ Integer authenticators) {
final Bundle bundle = new Bundle();
bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation);
- if (allowDeviceCredential) {
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
+ if (authenticators != null) {
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
}
return bundle;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
new file mode 100644
index 000000000000..abe39f0934db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 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 com.android.server.biometrics;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNull;
+
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class UtilsTest {
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+ final boolean allowDeviceCredential = false;
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+ final boolean allowDeviceCredential = true;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+ final Bundle bundle = new Bundle();
+
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testIsDeviceCredentialAllowed_withIntegerFlags() {
+ int authenticators = 0;
+ assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
+
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
+ assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+
+ authenticators |= Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ }
+
+ @Test
+ public void testIsDeviceCredentialAllowed_withBundle() {
+ Bundle bundle = new Bundle();
+ assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+ int authenticators = 0;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+
+ authenticators |= Authenticators.BIOMETRIC_WEAK;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ }
+
+ @Test
+ public void testGetBiometricStrength_removeUnrelatedBits() {
+ // BIOMETRIC_MIN_STRENGTH uses all of the allowed bits for biometric strength, so any other
+ // bits aside from these should be clipped off.
+
+ int authenticators = Integer.MAX_VALUE;
+ assertEquals(Authenticators.BIOMETRIC_WEAK,
+ Utils.getPublicBiometricStrength(authenticators));
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertEquals(Authenticators.BIOMETRIC_WEAK, Utils.getPublicBiometricStrength(bundle));
+ }
+
+ @Test
+ public void testIsBiometricAllowed() {
+ // Only the lowest 8 bits (BIOMETRIC_WEAK mask) are allowed to integrate with the
+ // Biometric APIs
+ Bundle bundle = new Bundle();
+ for (int i = 0; i <= 7; i++) {
+ int authenticators = 1 << i;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isBiometricAllowed(bundle));
+ }
+
+ // The rest of the bits are not allowed to integrate with the public APIs
+ for (int i = 8; i < 32; i++) {
+ int authenticators = 1 << i;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertFalse(Utils.isBiometricAllowed(bundle));
+ }
+ }
+
+ @Test
+ public void testIsValidAuthenticatorConfig() {
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_STRONG));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_WEAK));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE
+ | Authenticators.DEVICE_CREDENTIAL));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH));
+
+ // The rest of the bits are not allowed to integrate with the public APIs
+ for (int i = 8; i < 32; i++) {
+ final int authenticator = 1 << i;
+ if (authenticator == Authenticators.DEVICE_CREDENTIAL) {
+ continue;
+ }
+ assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
+ }
+ }
+
+ @Test
+ public void testIsAtLeastStrength() {
+ int sensorStrength = Authenticators.BIOMETRIC_STRONG;
+ int requestedStrength = Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ requestedStrength = Authenticators.BIOMETRIC_STRONG;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ sensorStrength = Authenticators.BIOMETRIC_WEAK;
+ requestedStrength = Authenticators.BIOMETRIC_STRONG;
+ assertFalse(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ requestedStrength = Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+ }
+
+ @Test
+ public void testBiometricConstantsConversion() {
+ final int[][] testCases = {
+ {BiometricConstants.BIOMETRIC_SUCCESS,
+ BiometricManager.BIOMETRIC_SUCCESS},
+ {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+ {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+ {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE},
+ {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT,
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}
+ };
+
+ for (int i = 0; i < testCases.length; i++) {
+ assertEquals(testCases[i][1],
+ Utils.biometricConstantsToBiometricManager(testCases[i][0]));
+ }
+ }
+}