diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 0389b4521fb..db50676ebbd 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 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra 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 mPreferenceControllers = new ArrayList<>(); @@ -216,6 +219,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra 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 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra 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 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra @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 6fe3ca4521b..cf6b3e33e76 100644 --- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java @@ -55,6 +55,7 @@ public class BuildNumberPreferenceController extends BasePreferenceController im 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 @@ public class BuildNumberPreferenceController extends BasePreferenceController im * @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 37a4aeab572..9f45edb4930 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 @@ package com.android.settings.development; 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 com.android.settingslib.development.DevelopmentSettingsEnabler; 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.Config; 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 @@ import java.util.List; 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 @@ -176,6 +198,41 @@ public class DevelopmentSettingsDashboardFragmentTest { assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isTrue(); } + @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) diff --git a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java index 237786bf191..326627a6247 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 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.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.core.lifecycle.Lifecycle; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class BuildNumberPreferenceControllerTest { private Context mContext; private UserManager mUserManager; + private BiometricManager mBiometricManager; private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; private FakeFeatureFactory mFactory; @@ -76,7 +86,13 @@ public class BuildNumberPreferenceControllerTest { 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 @@ public class BuildNumberPreferenceControllerTest { @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 @@ public class BuildNumberPreferenceControllerTest { 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(); + } }