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();
+    }
 }