diff --git a/res/values/strings.xml b/res/values/strings.xml
index 96dc07ebbaa..cbfe41593fb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -934,7 +934,8 @@
Face, fingerprint, and %s added
Face, fingerprints, and %s added
-
+
+ This is needed since Identity Check is on
Remote Authenticator Unlock
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 7ed6ba0969e..27a628c5308 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -24,6 +24,9 @@ import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_AUTHENTICATORS;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT;
+
import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityManager;
@@ -54,6 +57,7 @@ import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
@@ -122,6 +126,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settingslib.widget.ActionBarShadowController;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -1478,6 +1483,55 @@ public final class Utils extends com.android.settingslib.Utils {
disableComponent(pm, new ComponentName(context, Settings.CreateShortcutActivity.class));
}
+ /**
+ * Request biometric authentication if all requirements for mandatory biometrics is satisfied.
+ * @param context of the corresponding activity/fragment
+ * @param biometricsSuccessfullyAuthenticated if the user has already authenticated using
+ * biometrics
+ * @param biometricsAuthenticationRequested if the activity/fragment has already requested for
+ * biometric prompt
+ * @return true if all requirements for mandatory biometrics is satisfied
+ */
+ public static boolean requestBiometricAuthenticationForMandatoryBiometrics(
+ @NonNull Context context,
+ boolean biometricsSuccessfullyAuthenticated,
+ boolean biometricsAuthenticationRequested) {
+ final BiometricManager biometricManager = context.getSystemService(BiometricManager.class);
+ if (biometricManager == null) {
+ Log.e(TAG, "Biometric Manager is null.");
+ return false;
+ }
+ final int status = biometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ return android.hardware.biometrics.Flags.mandatoryBiometrics()
+ && status == BiometricManager.BIOMETRIC_SUCCESS
+ && !biometricsSuccessfullyAuthenticated
+ && !biometricsAuthenticationRequested;
+ }
+
+ /**
+ * Launch biometric prompt for mandatory biometrics. Call
+ * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean)}
+ * to check if all requirements for mandatory biometrics is satisfied
+ * before launching biometric prompt.
+ *
+ * @param fragment corresponding fragment of the surface
+ * @param requestCode for starting the new activity
+ */
+ public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Fragment fragment,
+ int requestCode) {
+ final Intent intent = new Intent();
+ intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ intent.putExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT,
+ fragment.getString(R.string.cancel));
+ intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION,
+ fragment.getString(R.string.mandatory_biometrics_prompt_description));
+ intent.setClassName(SETTINGS_PACKAGE_NAME,
+ ConfirmDeviceCredentialActivity.class.getName());
+ fragment.startActivityForResult(intent, requestCode);
+ }
+
private static void disableComponent(PackageManager pm, ComponentName componentName) {
pm.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index 335d0b9dd99..37ada236343 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -68,6 +68,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
+ public static final String EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY =
+ "biometrics_authenticated_successfully";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
@@ -115,6 +117,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final int LEARN_MORE_REQUEST = 3;
public static final int CONFIRM_REQUEST = 4;
public static final int ENROLL_REQUEST = 5;
+ public static final int BIOMETRIC_AUTH_REQUEST = 6;
/**
* Request code when starting another biometric enrollment from within a biometric flow. For
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index b17478881fe..caa7327394a 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -65,6 +65,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
static final int CONFIRM_REQUEST = 2001;
private static final int CHOOSE_LOCK_REQUEST = 2002;
protected static final int ACTIVE_UNLOCK_REQUEST = 2003;
+ private static final int BIOMETRIC_AUTH_REQUEST = 2004;
private static final String SAVE_STATE_CONFIRM_CREDETIAL = "confirm_credential";
private static final String DO_NOT_FINISH_ACTIVITY = "do_not_finish_activity";
@@ -72,10 +73,15 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
static final String RETRY_PREFERENCE_KEY = "retry_preference_key";
@VisibleForTesting
static final String RETRY_PREFERENCE_BUNDLE = "retry_preference_bundle";
+ private static final String BIOMETRICS_AUTH_REQUESTED = "biometrics_auth_requested";
+ private static final String BIOMETRICS_AUTHENTICATED_SUCCESSFULLY =
+ "biometrics_authenticated_successfully";
protected int mUserId;
protected long mGkPwHandle;
private boolean mConfirmCredential;
+ private boolean mBiometricsAuthenticationRequested;
+ private boolean mBiometricsSuccessfullyAuthenticated;
@Nullable private FaceManager mFaceManager;
@Nullable private FingerprintManager mFingerprintManager;
// Do not finish() if choosing/confirming credential, showing fp/face settings, or launching
@@ -113,6 +119,9 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent());
}
+ mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
+ BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
+
if (savedInstanceState != null) {
mConfirmCredential = savedInstanceState.getBoolean(SAVE_STATE_CONFIRM_CREDETIAL);
mDoNotFinishActivity = savedInstanceState.getBoolean(DO_NOT_FINISH_ACTIVITY);
@@ -123,11 +132,20 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
mGkPwHandle = savedInstanceState.getLong(
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE);
}
+ mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
+ BIOMETRICS_AUTH_REQUESTED);
+ mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
+ BIOMETRICS_AUTHENTICATED_SUCCESSFULLY);
}
if (mGkPwHandle == 0L && !mConfirmCredential) {
mConfirmCredential = true;
launchChooseOrConfirmLock();
+ } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(
+ getActivity(), mBiometricsSuccessfullyAuthenticated,
+ mBiometricsAuthenticationRequested)) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
}
updateUnlockPhonePreferenceSummary();
@@ -141,6 +159,12 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
@Override
public void onResume() {
super.onResume();
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)
+ && mGkPwHandle != 0L) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+ }
if (!mConfirmCredential) {
mDoNotFinishActivity = false;
}
@@ -177,6 +201,9 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
+ extras.putBoolean(
+ BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
+ mBiometricsSuccessfullyAuthenticated);
onFaceOrFingerprintPreferenceTreeClick(preference);
} catch (IllegalStateException e) {
if (retry) {
@@ -206,6 +233,9 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
final Bundle extras = preference.getExtras();
extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
+ extras.putBoolean(
+ BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
+ mBiometricsSuccessfullyAuthenticated);
onFaceOrFingerprintPreferenceTreeClick(preference);
} catch (IllegalStateException e) {
if (retry) {
@@ -288,6 +318,10 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
outState.putString(RETRY_PREFERENCE_KEY, mRetryPreferenceKey);
outState.putBundle(RETRY_PREFERENCE_BUNDLE, mRetryPreferenceExtra);
}
+ outState.putBoolean(BIOMETRICS_AUTH_REQUESTED,
+ mBiometricsAuthenticationRequested);
+ outState.putBoolean(BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
+ mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -315,6 +349,13 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
}
mRetryPreferenceKey = null;
mRetryPreferenceExtra = null;
+ } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
+ mBiometricsAuthenticationRequested = false;
+ if (resultCode == RESULT_OK) {
+ mBiometricsSuccessfullyAuthenticated = true;
+ } else {
+ finish();
+ }
}
}
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index 8884ce36253..2a0dd83a491 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -20,8 +20,10 @@ import static android.app.Activity.RESULT_OK;
import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
import static com.android.settings.Utils.isPrivateProfile;
+import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
@@ -66,6 +68,8 @@ public class FaceSettings extends DashboardFragment {
private static final String TAG = "FaceSettings";
private static final String KEY_TOKEN = "hw_auth_token";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
+ private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
+ "biometrics_successfully_authenticated";
private static final String PREF_KEY_DELETE_FACE_DATA =
"security_settings_face_delete_faces_container";
@@ -93,6 +97,8 @@ public class FaceSettings extends DashboardFragment {
private FaceFeatureProvider mFaceFeatureProvider;
private boolean mConfirmingPassword;
+ private boolean mBiometricsAuthenticationRequested;
+ private boolean mBiometricsSuccessfullyAuthenticated;
private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
@@ -144,6 +150,8 @@ public class FaceSettings extends DashboardFragment {
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putByteArray(KEY_TOKEN, mToken);
+ outState.putBoolean(KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED,
+ mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -163,6 +171,8 @@ public class FaceSettings extends DashboardFragment {
mToken = getIntent().getByteArrayExtra(KEY_TOKEN);
mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1);
mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L);
+ mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
+ EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
mUserId = getActivity().getIntent().getIntExtra(
Intent.EXTRA_USER_ID, UserHandle.myUserId());
@@ -231,6 +241,8 @@ public class FaceSettings extends DashboardFragment {
if (savedInstanceState != null) {
mToken = savedInstanceState.getByteArray(KEY_TOKEN);
+ mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
+ KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED);
}
}
@@ -276,6 +288,10 @@ public class FaceSettings extends DashboardFragment {
Log.e(TAG, "Password not set");
finish();
}
+ } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
} else {
mAttentionController.setToken(mToken);
mEnrollController.setToken(mToken);
@@ -318,6 +334,13 @@ public class FaceSettings extends DashboardFragment {
setResult(resultCode, data);
finish();
}
+ } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
+ mBiometricsAuthenticationRequested = false;
+ if (resultCode == RESULT_OK) {
+ mBiometricsSuccessfullyAuthenticated = true;
+ } else {
+ finish();
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 46461340c10..d35cda45bbd 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.Utils.isPrivateProfile;
+import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
@@ -218,6 +219,10 @@ public class FingerprintSettings extends SubSettings {
"security_settings_fingerprint_unlock_category";
private static final String KEY_FINGERPRINT_UNLOCK_FOOTER =
"security_settings_fingerprint_footer";
+ private static final String KEY_BIOMETRICS_AUTHENTICATION_REQUESTED =
+ "biometrics_authentication_requested";
+ private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
+ "biometrics_successfully_authenticated";
private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000;
private static final int MSG_FINGER_AUTH_SUCCESS = 1001;
@@ -251,6 +256,8 @@ public class FingerprintSettings extends SubSettings {
private boolean mInFingerprintLockout;
private byte[] mToken;
private boolean mLaunchedConfirm;
+ private boolean mBiometricsAuthenticationRequested;
+ private boolean mBiometricsSuccessfullyAuthenticated;
private boolean mHasFirstEnrolled = true;
private Drawable mHighlightDrawable;
private int mUserId;
@@ -423,6 +430,8 @@ public class FingerprintSettings extends SubSettings {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mChallenge = activity.getIntent()
.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L);
+ mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
+ BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
mAuthenticateSidecar = (FingerprintAuthenticateSidecar)
getFragmentManager().findFragmentByTag(TAG_AUTHENTICATE_SIDECAR);
@@ -464,6 +473,10 @@ public class FingerprintSettings extends SubSettings {
mIsEnrolling = savedInstanceState.getBoolean(KEY_IS_ENROLLING, mIsEnrolling);
mHasFirstEnrolled = savedInstanceState.getBoolean(KEY_HAS_FIRST_ENROLLED,
mHasFirstEnrolled);
+ mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
+ KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED);
+ mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
+ KEY_BIOMETRICS_AUTHENTICATION_REQUESTED);
}
// (mLaunchedConfirm or mIsEnrolling) means that we are waiting an activity result.
@@ -472,6 +485,10 @@ public class FingerprintSettings extends SubSettings {
if (mToken == null) {
mLaunchedConfirm = true;
launchChooseOrConfirmLock();
+ } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
} else if (!mHasFirstEnrolled) {
mIsEnrolling = true;
addFirstFingerprint(null);
@@ -751,6 +768,12 @@ public class FingerprintSettings extends SubSettings {
mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
.getUdfpsEnrollCalibrator(getActivity().getApplicationContext(), null, null);
+
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+ }
}
private void updatePreferences() {
@@ -798,6 +821,10 @@ public class FingerprintSettings extends SubSettings {
outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming);
outState.putBoolean(KEY_IS_ENROLLING, mIsEnrolling);
outState.putBoolean(KEY_HAS_FIRST_ENROLLED, mHasFirstEnrolled);
+ outState.putBoolean(KEY_BIOMETRICS_AUTHENTICATION_REQUESTED,
+ mBiometricsAuthenticationRequested);
+ outState.putBoolean(KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED,
+ mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -1018,6 +1045,13 @@ public class FingerprintSettings extends SubSettings {
mIsEnrolling = false;
mHasFirstEnrolled = true;
updateAddPreference();
+ } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
+ mBiometricsAuthenticationRequested = false;
+ if (resultCode == RESULT_OK) {
+ mBiometricsSuccessfullyAuthenticated = true;
+ } else {
+ finish();
+ }
}
}
diff --git a/src/com/android/settings/password/BiometricFragment.java b/src/com/android/settings/password/BiometricFragment.java
index 02f5b861ea3..a7a039e8485 100644
--- a/src/com/android/settings/password/BiometricFragment.java
+++ b/src/com/android/settings/password/BiometricFragment.java
@@ -16,8 +16,11 @@
package com.android.settings.password;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED;
+
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
@@ -137,7 +140,7 @@ public class BiometricFragment extends InstrumentedFragment {
BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
.setTitle(promptInfo.getTitle())
.setUseDefaultTitle() // use default title if title is null/empty
- .setDeviceCredentialAllowed(true)
+ .setAllowedAuthenticators(promptInfo.getAuthenticators())
.setSubtitle(promptInfo.getSubtitle())
.setDescription(promptInfo.getDescription())
.setTextForDeviceCredential(
@@ -170,6 +173,15 @@ public class BiometricFragment extends InstrumentedFragment {
if (promptInfo.isUseDefaultSubtitle()) {
promptBuilder.setUseDefaultSubtitle();
}
+
+ if ((promptInfo.getAuthenticators()
+ & BiometricManager.Authenticators.DEVICE_CREDENTIAL) == 0) {
+ promptBuilder.setNegativeButton(promptInfo.getNegativeButtonText(),
+ getContext().getMainExecutor(),
+ (dialog, which) -> mAuthenticationCallback.onAuthenticationError(
+ BIOMETRIC_ERROR_USER_CANCELED,
+ null /* errString */));
+ }
mBiometricPrompt = promptBuilder.build();
}
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index ce9a5667dfc..4c18309384c 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -160,11 +160,13 @@ public class ChooseLockGeneric extends SettingsActivity {
static final int CHOOSE_LOCK_BEFORE_BIOMETRIC_REQUEST = 103;
@VisibleForTesting
static final int SKIP_FINGERPRINT_REQUEST = 104;
+ private static final int BIOMETRIC_AUTH_REQUEST = 105;
private LockPatternUtils mLockPatternUtils;
private DevicePolicyManager mDpm;
private boolean mRequestGatekeeperPasswordHandle = false;
private boolean mPasswordConfirmed = false;
+ private boolean mBiometricsAuthSuccessful = false;
private boolean mWaitingForConfirmation = false;
private boolean mWaitingForActivityResult = false;
private LockscreenCredential mUserPassword;
@@ -488,6 +490,17 @@ public class ChooseLockGeneric extends SettingsActivity {
? data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)
: null;
updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
+ mBiometricsAuthSuccessful, mWaitingForConfirmation)) {
+ mWaitingForConfirmation = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+ }
+ } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ mBiometricsAuthSuccessful = true;
+ } else {
+ finish();
+ }
} else if (requestCode == CHOOSE_LOCK_REQUEST) {
if (resultCode != RESULT_CANCELED) {
getActivity().setResult(resultCode, data);
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 7f362c32904..c0b3093c2f8 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -17,10 +17,10 @@
package com.android.settings.password;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
-import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.biometrics.Utils.toBitmap;
@@ -40,6 +40,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.PromptInfo;
@@ -76,6 +77,9 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
/** Use this extra value to provide a custom logo description for the biometric prompt. **/
public static final String CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY =
"custom_logo_description";
+ public static final String BIOMETRIC_PROMPT_AUTHENTICATORS = "biometric_prompt_authenticators";
+ public static final String BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT =
+ "biometric_prompt_negative_button_text";
public static class InternalActivity extends ConfirmDeviceCredentialActivity {
}
@@ -177,6 +181,11 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION);
String alternateButton = intent.getStringExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
+ final int authenticators = intent.getIntExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ | BiometricManager.Authenticators.BIOMETRIC_WEAK);
+ final String negativeButtonText = intent.getStringExtra(
+ BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT);
final boolean frp =
KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
final boolean repairMode =
@@ -213,6 +222,8 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
promptInfo.setTitle(mTitle);
promptInfo.setDescription(mDetails);
promptInfo.setDisallowBiometricsIfPolicyExists(mCheckDevicePolicyManager);
+ promptInfo.setAuthenticators(authenticators);
+ promptInfo.setNegativeButtonText(negativeButtonText);
if (android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt()
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index 0c57b014506..77de7496046 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -20,6 +20,10 @@ import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_AUTHENTICATORS;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
@@ -35,10 +39,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActionBar;
+import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyResourcesManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -47,6 +53,8 @@ import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.VectorDrawable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -61,21 +69,28 @@ import android.os.UserManager;
import android.os.storage.DiskInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.IconDrawableFactory;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -92,6 +107,9 @@ import java.util.List;
@Config(shadows = ShadowLockPatternUtils.class)
public class UtilsTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String PACKAGE_NAME = "com.android.app";
private static final int USER_ID = 1;
@@ -113,6 +131,11 @@ public class UtilsTest {
private IconDrawableFactory mIconDrawableFactory;
@Mock
private ApplicationInfo mApplicationInfo;
+ @Mock
+ private BiometricManager mBiometricManager;
+ @Mock
+ private Fragment mFragment;
+
private Context mContext;
private UserManager mUserManager;
private static final int FLAG_SYSTEM = 0x00000000;
@@ -128,6 +151,7 @@ public class UtilsTest {
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
.thenReturn(connectivityManager);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
}
@After
@@ -503,6 +527,62 @@ public class UtilsTest {
assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isFalse();
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerNull_shouldReturnFalse() {
+ when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(null);
+ assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnTrue()
+ throws InterruptedException {
+ when(mBiometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
+ boolean requestBiometricAuthenticationForMandatoryBiometrics =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ true /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */);
+ assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnFalse() {
+ when(mBiometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testLaunchBiometricPrompt_checkIntentValues() {
+ when(mFragment.getContext()).thenReturn(mContext);
+
+ final int requestCode = 1;
+ final ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode);
+
+ verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(), eq(requestCode));
+
+ final Intent intent = intentArgumentCaptor.getValue();
+
+ assertThat(intent.getExtra(BIOMETRIC_PROMPT_AUTHENTICATORS)).isEqualTo(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ assertThat(intent.getExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT)).isNotNull();
+ assertThat(intent.getExtra(KeyguardManager.EXTRA_DESCRIPTION)).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).isEqualTo(SETTINGS_PACKAGE_NAME);
+ assertThat(intent.getComponent().getClassName()).isEqualTo(
+ ConfirmDeviceCredentialActivity.class.getName());
+ }
+
private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) {
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
when(mMockUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(USER_ID);