diff --git a/aconfig/settings_display_flag_declarations.aconfig b/aconfig/settings_display_flag_declarations.aconfig new file mode 100644 index 00000000000..52a326da45c --- /dev/null +++ b/aconfig/settings_display_flag_declarations.aconfig @@ -0,0 +1,9 @@ +package: "com.android.settings.flags" + +flag { + name: "protect_screen_timeout_with_auth" + namespace: "safety_center" + description: "Require an auth challenge for increasing screen timeout." + bug: "315937886" +} + diff --git a/src/com/android/settings/display/ScreenTimeoutSettings.java b/src/com/android/settings/display/ScreenTimeoutSettings.java index f7be3192a76..1c99d5f70a8 100644 --- a/src/com/android/settings/display/ScreenTimeoutSettings.java +++ b/src/com/android/settings/display/ScreenTimeoutSettings.java @@ -37,10 +37,12 @@ import android.util.Log; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.support.actionbar.HelpResourceProvider; import com.android.settings.widget.RadioButtonPickerFragment; +import com.android.settings.wifi.dpp.WifiDppUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -55,13 +57,12 @@ import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; -/** - * Fragment that is used to control screen timeout. - */ +/** Fragment that is used to control screen timeout. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) -public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements - HelpResourceProvider { +public class ScreenTimeoutSettings extends RadioButtonPickerFragment + implements HelpResourceProvider { private static final String TAG = "ScreenTimeout"; + /** If there is no setting in the provider, use this. */ public static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000; @@ -72,25 +73,24 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements private FooterPreference mPrivacyPreference; private final MetricsFeatureProvider mMetricsFeatureProvider; private SensorPrivacyManager mPrivacyManager; - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); - mAdaptiveSleepController.updatePreference(); - } - }; + private final BroadcastReceiver mReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); + mAdaptiveSleepController.updatePreference(); + } + }; private DevicePolicyManager mDevicePolicyManager; private SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener; + private boolean mIsUserAuthenticated = false; - @VisibleForTesting - Context mContext; + @VisibleForTesting Context mContext; - @VisibleForTesting - RestrictedLockUtils.EnforcedAdmin mAdmin; + @VisibleForTesting RestrictedLockUtils.EnforcedAdmin mAdmin; - @VisibleForTesting - FooterPreference mDisableOptionsPreference; + @VisibleForTesting FooterPreference mDisableOptionsPreference; @VisibleForTesting FooterPreference mPowerConsumptionPreference; @@ -101,16 +101,14 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements @VisibleForTesting AdaptiveSleepCameraStatePreferenceController mAdaptiveSleepCameraStatePreferenceController; - @VisibleForTesting - AdaptiveSleepPreferenceController mAdaptiveSleepController; + @VisibleForTesting AdaptiveSleepPreferenceController mAdaptiveSleepController; @VisibleForTesting AdaptiveSleepBatterySaverPreferenceController mAdaptiveSleepBatterySaverPreferenceController; public ScreenTimeoutSettings() { super(); - mMetricsFeatureProvider = FeatureFactory.getFeatureFactory() - .getMetricsFeatureProvider(); + mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } @Override @@ -121,8 +119,8 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements mInitialEntries = getResources().getStringArray(R.array.screen_timeout_entries); mInitialValues = getResources().getStringArray(R.array.screen_timeout_values); mAdaptiveSleepController = new AdaptiveSleepPreferenceController(context); - mAdaptiveSleepPermissionController = new AdaptiveSleepPermissionPreferenceController( - context); + mAdaptiveSleepPermissionController = + new AdaptiveSleepPermissionPreferenceController(context); mAdaptiveSleepCameraStatePreferenceController = new AdaptiveSleepCameraStatePreferenceController(context, getLifecycle()); mAdaptiveSleepBatterySaverPreferenceController = @@ -144,8 +142,9 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements if (mInitialValues != null) { for (int i = 0; i < mInitialValues.length; ++i) { if (Long.parseLong(mInitialValues[i].toString()) <= maxTimeout) { - candidates.add(new TimeoutCandidateInfo(mInitialEntries[i], - mInitialValues[i].toString(), true)); + candidates.add( + new TimeoutCandidateInfo( + mInitialEntries[i], mInitialValues[i].toString(), true)); } } } else { @@ -161,9 +160,10 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements mAdaptiveSleepCameraStatePreferenceController.updateVisibility(); mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); mAdaptiveSleepController.updatePreference(); - mContext.registerReceiver(mReceiver, - new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + mContext.registerReceiver( + mReceiver, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener); + mIsUserAuthenticated = false; } @Override @@ -185,19 +185,21 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements } for (CandidateInfo info : candidateList) { - SelectorWithWidgetPreference pref = - new SelectorWithWidgetPreference(getPrefContext()); + ProtectedSelectorWithWidgetPreference pref = + new ProtectedSelectorWithWidgetPreference( + getPrefContext(), info.getKey(), this); bindPreference(pref, info.getKey(), info, defaultKey); screen.addPreference(pref); } - final long selectedTimeout = Long.parseLong(defaultKey); + final long selectedTimeout = getTimeoutFromKey(defaultKey); final long maxTimeout = getMaxScreenTimeout(getContext()); if (!candidateList.isEmpty() && (selectedTimeout > maxTimeout)) { // The selected time out value is longer than the max timeout allowed by the admin. // Select the largest value from the list by default. - final SelectorWithWidgetPreference preferenceWithLargestTimeout = - (SelectorWithWidgetPreference) screen.getPreference(candidateList.size() - 1); + final ProtectedSelectorWithWidgetPreference preferenceWithLargestTimeout = + (ProtectedSelectorWithWidgetPreference) + screen.getPreference(candidateList.size() - 1); preferenceWithLargestTimeout.setChecked(true); } @@ -225,20 +227,34 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements } } + boolean isUserAuthenticated() { + return mIsUserAuthenticated; + } + + void setUserAuthenticated(boolean isUserAuthenticated) { + mIsUserAuthenticated = isUserAuthenticated; + } + @VisibleForTesting void setupDisabledFooterPreference() { - final String textDisabledByAdmin = mDevicePolicyManager.getResources().getString( - OTHER_OPTIONS_DISABLED_BY_ADMIN, () -> getResources().getString( - R.string.admin_disabled_other_options)); + final String textDisabledByAdmin = + mDevicePolicyManager + .getResources() + .getString( + OTHER_OPTIONS_DISABLED_BY_ADMIN, + () -> + getResources() + .getString(R.string.admin_disabled_other_options)); final String textMoreDetails = getResources().getString(R.string.admin_more_details); mDisableOptionsPreference = new FooterPreference(getContext()); mDisableOptionsPreference.setTitle(textDisabledByAdmin); mDisableOptionsPreference.setSelectable(false); mDisableOptionsPreference.setLearnMoreText(textMoreDetails); - mDisableOptionsPreference.setLearnMoreAction(v -> { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin); - }); + mDisableOptionsPreference.setLearnMoreAction( + v -> { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin); + }); mDisableOptionsPreference.setIcon(R.drawable.ic_info_outline_24dp); // The 'disabled by admin' preference should always be at the end of the setting page. @@ -303,17 +319,20 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements if (context == null) { return Long.toString(FALLBACK_SCREEN_TIMEOUT_VALUE); } else { - return Long.toString(Settings.System.getLong(context.getContentResolver(), - SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)); + return Long.toString( + Settings.System.getLong( + context.getContentResolver(), + SCREEN_OFF_TIMEOUT, + FALLBACK_SCREEN_TIMEOUT_VALUE)); } } private void setCurrentSystemScreenTimeout(Context context, String key) { try { if (context != null) { - final long value = Long.parseLong(key); - mMetricsFeatureProvider.action(context, SettingsEnums.ACTION_SCREEN_TIMEOUT_CHANGED, - (int) value); + final long value = getTimeoutFromKey(key); + mMetricsFeatureProvider.action( + context, SettingsEnums.ACTION_SCREEN_TIMEOUT_CHANGED, (int) value); Settings.System.putLong(context.getContentResolver(), SCREEN_OFF_TIMEOUT, value); } } catch (NumberFormatException e) { @@ -325,7 +344,12 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements return AdaptiveSleepPreferenceController.isAdaptiveSleepSupported(context); } - private static class TimeoutCandidateInfo extends CandidateInfo { + private static long getTimeoutFromKey(String key) { + return Long.parseLong(key); + } + + @VisibleForTesting + static class TimeoutCandidateInfo extends CandidateInfo { private final CharSequence mLabel; private final String mKey; @@ -351,10 +375,42 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements } } + @VisibleForTesting + static class ProtectedSelectorWithWidgetPreference + extends SelectorWithWidgetPreference { + + private final long mTimeoutMs; + private final ScreenTimeoutSettings mScreenTimeoutSettings; + + ProtectedSelectorWithWidgetPreference( + Context context, String key, ScreenTimeoutSettings screenTimeoutSettings) { + super(context); + mTimeoutMs = getTimeoutFromKey(key); + mScreenTimeoutSettings = screenTimeoutSettings; + } + + @Override + public void onClick() { + if (Flags.protectScreenTimeoutWithAuth() + && !mScreenTimeoutSettings.isUserAuthenticated() + && !isChecked() + && mTimeoutMs > getTimeoutFromKey(mScreenTimeoutSettings.getDefaultKey())) { + WifiDppUtils.showLockScreen( + getContext(), + () -> { + mScreenTimeoutSettings.setUserAuthenticated(true); + super.onClick(); + }); + } else { + super.onClick(); + } + } + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.screen_timeout_settings) { - public List getRawDataToIndex(Context context, - boolean enabled) { + public List getRawDataToIndex( + Context context, boolean enabled) { if (!isScreenAttentionAvailable(context)) { return null; } diff --git a/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java b/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java index 9e193ffc97e..1a6a1128068 100644 --- a/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java +++ b/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; @@ -41,31 +42,44 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.SearchIndexableResource; import android.provider.Settings; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.flags.Flags; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.widget.CandidateInfo; import com.android.settingslib.widget.FooterPreference; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowKeyguardManager; import java.util.List; @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, + ShadowKeyguardManager.class }) public class ScreenTimeoutSettingsTest { + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final String[] TIMEOUT_ENTRIES = new String[]{"15 secs", "30 secs"}; private static final String[] TIMEOUT_VALUES = new String[]{"15000", "30000"}; @@ -218,4 +232,85 @@ public class ScreenTimeoutSettingsTest { assertThat(Long.toString(timeout)).isEqualTo(TIMEOUT_VALUES[0]); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH) + public void onClick_whenUserAlreadyAuthenticated_buttonChecked() { + String key = "222"; + String defaultKey = "1"; + mSettings.setDefaultKey(defaultKey); + CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false); + ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref = + new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference( + mContext, info.getKey(), mSettings); + mSettings.bindPreference(pref, info.getKey(), info, defaultKey); + mSettings.setUserAuthenticated(true); + + pref.onClick(); + + assertThat(mSettings.getDefaultKey()).isEqualTo(key); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH) + public void onClick_whenButtonAlreadyChecked_noAuthNeeded() { + String key = "222"; + mSettings.setDefaultKey(key); + CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false); + ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref = + new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference( + mContext, info.getKey(), mSettings); + mSettings.bindPreference(pref, info.getKey(), info, key); + mSettings.setUserAuthenticated(false); + setAuthPassesAutomatically(); + + pref.onClick(); + + assertThat(mSettings.isUserAuthenticated()).isFalse(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH) + public void onClick_whenReducingTimeout_noAuthNeeded() { + String key = "1"; + String defaultKey = "222"; + mSettings.setDefaultKey(defaultKey); + CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false); + ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref = + new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference( + mContext, info.getKey(), mSettings); + mSettings.bindPreference(pref, info.getKey(), info, defaultKey); + mSettings.setUserAuthenticated(false); + setAuthPassesAutomatically(); + + pref.onClick(); + + assertThat(mSettings.isUserAuthenticated()).isFalse(); + assertThat(mSettings.getDefaultKey()).isEqualTo(key); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH) + public void onClick_whenIncreasingTimeout_authNeeded() { + String key = "222"; + String defaultKey = "1"; + mSettings.setDefaultKey(defaultKey); + CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false); + ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref = + new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference( + mContext, info.getKey(), mSettings); + mSettings.bindPreference(pref, info.getKey(), info, defaultKey); + mSettings.setUserAuthenticated(false); + setAuthPassesAutomatically(); + + pref.onClick(); + + assertThat(mSettings.getDefaultKey()).isEqualTo(key); + assertThat(mSettings.isUserAuthenticated()).isTrue(); + } + + private void setAuthPassesAutomatically() { + Shadows.shadowOf(mContext.getSystemService(KeyguardManager.class)) + .setIsKeyguardSecure(false); + } }