Add mandatory biometric prompt to platform surfaces (4/N)
1. Enable developer options via build info
2. Enable developer options via toggle under system -> developer options
Flag: android.hardware.biometrics.flags.mandatory_biometrics
Fixes: 355500452
Test: atest BuildNumberPreferenceControllerTest
DevelopmentSettingsDashboardFragmentTest
Change-Id: Iecbe34024d287e71e235becec3ce5a2bd5c1697f
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 0389b45..db50676 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -16,6 +16,7 @@
package com.android.settings.development;
+import static android.app.Activity.RESULT_OK;
import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED;
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import static android.view.flags.Flags.sensitiveContentAppProtectionApi;
@@ -100,11 +101,13 @@
NfcRebootDialog.OnNfcRebootDialogConfirmedListener, BluetoothSnoopLogHost {
private static final String TAG = "DevSettingsDashboard";
+ @VisibleForTesting static final int REQUEST_BIOMETRIC_PROMPT = 100;
private final BluetoothA2dpConfigStore mBluetoothA2dpConfigStore =
new BluetoothA2dpConfigStore();
private boolean mIsAvailable = true;
+ private boolean mIsBiometricsAuthenticated;
private SettingsMainSwitchBar mSwitchBar;
private DevelopmentSwitchBarController mSwitchBarController;
private List<AbstractPreferenceController> mPreferenceControllers = new ArrayList<>();
@@ -216,6 +219,7 @@
public void onStart() {
super.onStart();
final ContentResolver cr = getContext().getContentResolver();
+ mIsBiometricsAuthenticated = false;
cr.registerContentObserver(mDevelopEnabled, false, mDeveloperSettingsObserver);
// Restore UI state based on whether developer options is enabled
@@ -360,7 +364,18 @@
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext());
if (isChecked != developmentEnabledState) {
if (isChecked) {
- EnableDevelopmentSettingWarningDialog.show(this /* host */);
+ final int userId = getContext().getUserId();
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
+ mIsBiometricsAuthenticated,
+ false /* biometricsAuthenticationRequested */, userId)) {
+ mSwitchBar.setChecked(false);
+ Utils.launchBiometricPromptForMandatoryBiometrics(this,
+ REQUEST_BIOMETRIC_PROMPT, userId, false /* hideBackground */);
+ } else {
+ //Reset biometrics once enable dialog is shown
+ mIsBiometricsAuthenticated = false;
+ EnableDevelopmentSettingWarningDialog.show(this /* host */);
+ }
} else {
final BluetoothA2dpHwOffloadPreferenceController a2dpController =
getDevelopmentOptionsController(
@@ -534,6 +549,12 @@
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
boolean handledResult = false;
+ if (requestCode == REQUEST_BIOMETRIC_PROMPT) {
+ if (resultCode == RESULT_OK) {
+ mIsBiometricsAuthenticated = true;
+ mSwitchBar.setChecked(true);
+ }
+ }
for (AbstractPreferenceController controller : mPreferenceControllers) {
if (controller instanceof OnActivityResultListener) {
// We do not break early because it is possible for multiple controllers to
diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
index 6fe3ca4..cf6b3e3 100644
--- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
@@ -55,6 +55,7 @@
static final int TAPS_TO_BE_A_DEVELOPER = 7;
static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100;
+ static final int REQUEST_IDENTITY_CHECK_FOR_DEV_PREF = 101;
private Activity mActivity;
private InstrumentedPreferenceFragment mFragment;
@@ -217,10 +218,24 @@
* @return if activity result is handled.
*/
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF) {
+ if (requestCode != REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF
+ && requestCode != REQUEST_IDENTITY_CHECK_FOR_DEV_PREF) {
return false;
}
- if (resultCode == Activity.RESULT_OK) {
+ if (requestCode == REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF
+ && resultCode == Activity.RESULT_OK) {
+ final int userId = mContext.getUserId();
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */,
+ userId)) {
+ Utils.launchBiometricPromptForMandatoryBiometrics(mFragment,
+ REQUEST_IDENTITY_CHECK_FOR_DEV_PREF, userId, false /* hideBackground */);
+ } else {
+ enableDevelopmentSettings();
+ }
+ } else if (requestCode == REQUEST_IDENTITY_CHECK_FOR_DEV_PREF
+ && resultCode == Activity.RESULT_OK) {
enableDevelopmentSettings();
}
mProcessingLastDevHit = false;
diff --git a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
index 37a4aea..9f45edb 100644
--- a/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/development/DevelopmentSettingsDashboardFragmentTest.java
@@ -18,13 +18,21 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.SearchIndexableResource;
import android.provider.Settings;
@@ -42,6 +50,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
@@ -51,6 +60,7 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowBiometricManager;
import org.robolectric.shadows.androidx.fragment.FragmentController;
import org.robolectric.util.ReflectionHelpers;
@@ -61,22 +71,34 @@
ShadowAlertDialogCompat.class,
ShadowUserManager.class,
ShadowUserManager.class,
+ ShadowBiometricManager.class,
})
public class DevelopmentSettingsDashboardFragmentTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
private ShadowUserManager mShadowUserManager;
+ private ShadowBiometricManager mShadowBiometricManager;
private DevelopmentSettingsDashboardFragment mDashboard;
+ private SettingsMainSwitchBar mSwitchBar;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
- SettingsMainSwitchBar switchBar = new SettingsMainSwitchBar(mContext);
+ mSwitchBar = new SettingsMainSwitchBar(mContext);
mDashboard = spy(new DevelopmentSettingsDashboardFragment());
- ReflectionHelpers.setField(mDashboard, "mSwitchBar", switchBar);
+ ReflectionHelpers.setField(mDashboard, "mSwitchBar", mSwitchBar);
mShadowUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE));
mShadowUserManager.setIsAdminUser(true);
+ mShadowBiometricManager = Shadow.extract(mContext.getSystemService(
+ Context.BIOMETRIC_SERVICE));
+ mShadowBiometricManager.setCanAuthenticate(false);
+ //TODO(b/352603684): Should be Authenticators.MANDATORY_BIOMETRICS,
+ // but it is not supported by ShadowBiometricManager
+ mShadowBiometricManager.setAuthenticatorType(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
}
@After
@@ -177,6 +199,41 @@
}
@Test
+ @Config(shadows = ShadowEnableDevelopmentSettingWarningDialog.class)
+ @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void onSwitchChanged_turnOn_shouldLaunchBiometricPromptIfMandatoryBiometricsEffective() {
+ when(mDashboard.getContext()).thenReturn(mContext);
+ doNothing().when(mDashboard).startActivityForResult(any(),
+ eq(DevelopmentSettingsDashboardFragment.REQUEST_BIOMETRIC_PROMPT));
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+ mShadowBiometricManager.setCanAuthenticate(true);
+ mDashboard.onCheckedChanged(null, true /* isChecked */);
+
+ assertThat(mSwitchBar.isChecked()).isFalse();
+ verify(mDashboard).startActivityForResult(any(),
+ eq(DevelopmentSettingsDashboardFragment.REQUEST_BIOMETRIC_PROMPT));
+ assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isFalse();
+ }
+
+ @Test
+ @Config(shadows = ShadowEnableDevelopmentSettingWarningDialog.class)
+ @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void onActivityResult_requestBiometricPrompt_shouldShowWarningDialog() {
+ when(mDashboard.getContext()).thenReturn(mContext);
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+ mDashboard.onActivityResult(DevelopmentSettingsDashboardFragment.REQUEST_BIOMETRIC_PROMPT,
+ Activity.RESULT_OK, null);
+ mDashboard.onCheckedChanged(null, true /* isChecked */);
+
+ assertThat(mSwitchBar.isChecked()).isTrue();
+ assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isTrue();
+ }
+
+ @Test
@Ignore
@Config(shadows = ShadowEnableDevelopmentSettingWarningDialog.class)
public void onSwitchChanged_turnOff_shouldTurnOff() {
diff --git a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
index 237786b..326627a 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
@@ -28,8 +28,13 @@
import android.app.Activity;
import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.os.Looper;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import androidx.lifecycle.LifecycleOwner;
@@ -45,6 +50,7 @@
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -53,6 +59,9 @@
@RunWith(AndroidJUnit4.class)
public class BuildNumberPreferenceControllerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String KEY_BUILD_NUMBER = "build_number";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -60,6 +69,7 @@
private Context mContext;
private UserManager mUserManager;
+ private BiometricManager mBiometricManager;
private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle;
private FakeFeatureFactory mFactory;
@@ -76,7 +86,13 @@
mContext = spy(ApplicationProvider.getApplicationContext());
mUserManager = (UserManager) spy(mContext.getSystemService(Context.USER_SERVICE));
+ mBiometricManager = spy(mContext.getSystemService(BiometricManager.class));
+
doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
+ when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
+ when(mBiometricManager.canAuthenticate(mContext.getUserId(),
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
mFactory = FakeFeatureFactory.setupForTest();
mLifecycleOwner = () -> mLifecycle;
@@ -156,7 +172,7 @@
@Test
public void onActivityResult_notConfirmPasswordRequest_doNothing() {
final boolean activityResultHandled = mController.onActivityResult(
- BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF + 1,
+ BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF + 2,
Activity.RESULT_OK,
null);
@@ -188,4 +204,38 @@
assertThat(activityResultHandled).isTrue();
assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isTrue();
}
+
+ @Test
+ @UiThreadTest
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void onActivityResult_confirmPasswordRequestCompleted_launchBiometricPrompt() {
+ when(mUserManager.isAdminUser()).thenReturn(true);
+ when(mBiometricManager.canAuthenticate(mContext.getUserId(),
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
+
+ final boolean activityResultHandled = mController.onActivityResult(
+ BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
+ Activity.RESULT_OK,
+ null);
+
+ assertThat(activityResultHandled).isTrue();
+ assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse();
+ verify(mFragment).startActivityForResult(any(),
+ eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
+ }
+
+ @Test
+ public void onActivityResult_confirmBiometricAuthentication_enableDevPref() {
+ when(mUserManager.isAdminUser()).thenReturn(true);
+
+ Looper.prepare();
+ final boolean activityResultHandled = mController.onActivityResult(
+ BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF,
+ Activity.RESULT_OK,
+ null);
+
+ assertThat(activityResultHandled).isTrue();
+ assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isTrue();
+ }
}