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
This commit is contained in:
Diya Bera
2024-07-30 17:31:05 +00:00
parent 2e12b95c45
commit 8f0c77bb19
4 changed files with 149 additions and 6 deletions

View File

@@ -16,6 +16,7 @@
package com.android.settings.development; package com.android.settings.development;
import static android.app.Activity.RESULT_OK;
import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED; import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED;
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES; import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import static android.view.flags.Flags.sensitiveContentAppProtectionApi; import static android.view.flags.Flags.sensitiveContentAppProtectionApi;
@@ -100,11 +101,13 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
NfcRebootDialog.OnNfcRebootDialogConfirmedListener, BluetoothSnoopLogHost { NfcRebootDialog.OnNfcRebootDialogConfirmedListener, BluetoothSnoopLogHost {
private static final String TAG = "DevSettingsDashboard"; private static final String TAG = "DevSettingsDashboard";
@VisibleForTesting static final int REQUEST_BIOMETRIC_PROMPT = 100;
private final BluetoothA2dpConfigStore mBluetoothA2dpConfigStore = private final BluetoothA2dpConfigStore mBluetoothA2dpConfigStore =
new BluetoothA2dpConfigStore(); new BluetoothA2dpConfigStore();
private boolean mIsAvailable = true; private boolean mIsAvailable = true;
private boolean mIsBiometricsAuthenticated;
private SettingsMainSwitchBar mSwitchBar; private SettingsMainSwitchBar mSwitchBar;
private DevelopmentSwitchBarController mSwitchBarController; private DevelopmentSwitchBarController mSwitchBarController;
private List<AbstractPreferenceController> mPreferenceControllers = new ArrayList<>(); private List<AbstractPreferenceController> mPreferenceControllers = new ArrayList<>();
@@ -216,6 +219,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
final ContentResolver cr = getContext().getContentResolver(); final ContentResolver cr = getContext().getContentResolver();
mIsBiometricsAuthenticated = false;
cr.registerContentObserver(mDevelopEnabled, false, mDeveloperSettingsObserver); cr.registerContentObserver(mDevelopEnabled, false, mDeveloperSettingsObserver);
// Restore UI state based on whether developer options is enabled // Restore UI state based on whether developer options is enabled
@@ -360,7 +364,18 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext()); DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext());
if (isChecked != developmentEnabledState) { if (isChecked != developmentEnabledState) {
if (isChecked) { 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 { } else {
final BluetoothA2dpHwOffloadPreferenceController a2dpController = final BluetoothA2dpHwOffloadPreferenceController a2dpController =
getDevelopmentOptionsController( getDevelopmentOptionsController(
@@ -534,6 +549,12 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
boolean handledResult = false; boolean handledResult = false;
if (requestCode == REQUEST_BIOMETRIC_PROMPT) {
if (resultCode == RESULT_OK) {
mIsBiometricsAuthenticated = true;
mSwitchBar.setChecked(true);
}
}
for (AbstractPreferenceController controller : mPreferenceControllers) { for (AbstractPreferenceController controller : mPreferenceControllers) {
if (controller instanceof OnActivityResultListener) { if (controller instanceof OnActivityResultListener) {
// We do not break early because it is possible for multiple controllers to // We do not break early because it is possible for multiple controllers to

View File

@@ -55,6 +55,7 @@ public class BuildNumberPreferenceController extends BasePreferenceController im
static final int TAPS_TO_BE_A_DEVELOPER = 7; static final int TAPS_TO_BE_A_DEVELOPER = 7;
static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100; static final int REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF = 100;
static final int REQUEST_IDENTITY_CHECK_FOR_DEV_PREF = 101;
private Activity mActivity; private Activity mActivity;
private InstrumentedPreferenceFragment mFragment; private InstrumentedPreferenceFragment mFragment;
@@ -217,10 +218,24 @@ public class BuildNumberPreferenceController extends BasePreferenceController im
* @return if activity result is handled. * @return if activity result is handled.
*/ */
public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 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; 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(); enableDevelopmentSettings();
} }
mProcessingLastDevHit = false; mProcessingLastDevHit = false;

View File

@@ -18,13 +18,21 @@ package com.android.settings.development;
import static com.google.common.truth.Truth.assertThat; 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.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context; 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.SearchIndexableResource;
import android.provider.Settings; import android.provider.Settings;
@@ -42,6 +50,7 @@ import com.android.settingslib.development.DevelopmentSettingsEnabler;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -51,6 +60,7 @@ import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow; import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowBiometricManager;
import org.robolectric.shadows.androidx.fragment.FragmentController; import org.robolectric.shadows.androidx.fragment.FragmentController;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
@@ -61,22 +71,34 @@ import java.util.List;
ShadowAlertDialogCompat.class, ShadowAlertDialogCompat.class,
ShadowUserManager.class, ShadowUserManager.class,
ShadowUserManager.class, ShadowUserManager.class,
ShadowBiometricManager.class,
}) })
public class DevelopmentSettingsDashboardFragmentTest { public class DevelopmentSettingsDashboardFragmentTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext; private Context mContext;
private ShadowUserManager mShadowUserManager; private ShadowUserManager mShadowUserManager;
private ShadowBiometricManager mShadowBiometricManager;
private DevelopmentSettingsDashboardFragment mDashboard; private DevelopmentSettingsDashboardFragment mDashboard;
private SettingsMainSwitchBar mSwitchBar;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
SettingsMainSwitchBar switchBar = new SettingsMainSwitchBar(mContext); mSwitchBar = new SettingsMainSwitchBar(mContext);
mDashboard = spy(new DevelopmentSettingsDashboardFragment()); mDashboard = spy(new DevelopmentSettingsDashboardFragment());
ReflectionHelpers.setField(mDashboard, "mSwitchBar", switchBar); ReflectionHelpers.setField(mDashboard, "mSwitchBar", mSwitchBar);
mShadowUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE)); mShadowUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE));
mShadowUserManager.setIsAdminUser(true); 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 @After
@@ -176,6 +198,41 @@ public class DevelopmentSettingsDashboardFragmentTest {
assertThat(ShadowEnableDevelopmentSettingWarningDialog.mShown).isTrue(); 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 @Test
@Ignore @Ignore
@Config(shadows = ShadowEnableDevelopmentSettingWarningDialog.class) @Config(shadows = ShadowEnableDevelopmentSettingWarningDialog.class)

View File

@@ -28,8 +28,13 @@ import static org.mockito.Mockito.when;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.Flags;
import android.os.Looper; import android.os.Looper;
import android.os.UserManager; 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 android.provider.Settings;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
@@ -45,6 +50,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.development.DevelopmentSettingsEnabler;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
@@ -53,6 +59,9 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class BuildNumberPreferenceControllerTest { public class BuildNumberPreferenceControllerTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String KEY_BUILD_NUMBER = "build_number"; private static final String KEY_BUILD_NUMBER = "build_number";
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -60,6 +69,7 @@ public class BuildNumberPreferenceControllerTest {
private Context mContext; private Context mContext;
private UserManager mUserManager; private UserManager mUserManager;
private BiometricManager mBiometricManager;
private LifecycleOwner mLifecycleOwner; private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle; private Lifecycle mLifecycle;
private FakeFeatureFactory mFactory; private FakeFeatureFactory mFactory;
@@ -76,7 +86,13 @@ public class BuildNumberPreferenceControllerTest {
mContext = spy(ApplicationProvider.getApplicationContext()); mContext = spy(ApplicationProvider.getApplicationContext());
mUserManager = (UserManager) spy(mContext.getSystemService(Context.USER_SERVICE)); mUserManager = (UserManager) spy(mContext.getSystemService(Context.USER_SERVICE));
mBiometricManager = spy(mContext.getSystemService(BiometricManager.class));
doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE); 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(); mFactory = FakeFeatureFactory.setupForTest();
mLifecycleOwner = () -> mLifecycle; mLifecycleOwner = () -> mLifecycle;
@@ -156,7 +172,7 @@ public class BuildNumberPreferenceControllerTest {
@Test @Test
public void onActivityResult_notConfirmPasswordRequest_doNothing() { public void onActivityResult_notConfirmPasswordRequest_doNothing() {
final boolean activityResultHandled = mController.onActivityResult( final boolean activityResultHandled = mController.onActivityResult(
BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF + 1, BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF + 2,
Activity.RESULT_OK, Activity.RESULT_OK,
null); null);
@@ -188,4 +204,38 @@ public class BuildNumberPreferenceControllerTest {
assertThat(activityResultHandled).isTrue(); assertThat(activityResultHandled).isTrue();
assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).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();
}
} }