Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b3e3fcb0 authored by Jan Tomljanovic's avatar Jan Tomljanovic Committed by Android (Google) Code Review
Browse files

Merge "Add auth challenge for increasing screen timeout." into main

parents 1d0cadea 6b4c754f
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
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"
}
+103 −47
Original line number Diff line number Diff line
@@ -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;
                    }
+95 −0
Original line number Diff line number Diff line
@@ -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);
    }
}