Add auth challenge for increasing screen timeout.

We only require one auth after onStart(), and only for increasing the
timeout.

Test: atest SettingsRoboTests:com.android.settings.display.ScreenTimeoutSettingsTest
Test: also manually tested
Bug: 315937886
Change-Id: If4aed67736cd7545d3a518aadd8253ea6a9fae43
This commit is contained in:
Jan Tomljanovic
2023-12-19 23:23:28 +00:00
parent 6ca4c0fb5a
commit 6b4c754f5e
3 changed files with 207 additions and 47 deletions

View File

@@ -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"
}

View File

@@ -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,7 +73,8 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements
private FooterPreference mPrivacyPreference;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private SensorPrivacyManager mPrivacyManager;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mAdaptiveSleepBatterySaverPreferenceController.updateVisibility();
@@ -82,15 +84,13 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements
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,18 +227,32 @@ 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 -> {
mDisableOptionsPreference.setLearnMoreAction(
v -> {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin);
});
mDisableOptionsPreference.setIcon(R.drawable.ic_info_outline_24dp);
@@ -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<SearchIndexableRaw> getRawDataToIndex(Context context,
boolean enabled) {
public List<SearchIndexableRaw> getRawDataToIndex(
Context context, boolean enabled) {
if (!isScreenAttentionAvailable(context)) {
return null;
}

View File

@@ -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);
}
}