From 547416749e6779df8f821b53ca8c6f03dde3e5d9 Mon Sep 17 00:00:00 2001 From: Darrell Shi Date: Fri, 28 Jan 2022 01:39:03 +0000 Subject: [PATCH] Settings for timeout to user 0 when docked. Test: atest SettingsRoboTests:TimeoutToUserZeroSettingsTest Test: atest SettingsRoboTests:TimeoutToUserZeroPreferenceControllerTest Test: manually on device, video: http://shortn/_elZS1CdDtE Bug: 213906356 Change-Id: I519345cdf44eaf452e092adee78ccf9d79130d33 --- res/values/arrays.xml | 18 +++ res/values/strings.xml | 3 + res/xml/user_settings.xml | 7 + .../user_timeout_to_user_zero_settings.xml | 20 +++ ...TimeoutToUserZeroPreferenceController.java | 87 +++++++++++ .../users/TimeoutToUserZeroSettings.java | 122 +++++++++++++++ .../android/settings/users/UserSettings.java | 8 + ...outToUserZeroPreferenceControllerTest.java | 145 ++++++++++++++++++ .../users/TimeoutToUserZeroSettingsTest.java | 98 ++++++++++++ 9 files changed, 508 insertions(+) create mode 100644 res/xml/user_timeout_to_user_zero_settings.xml create mode 100644 src/com/android/settings/users/TimeoutToUserZeroPreferenceController.java create mode 100644 src/com/android/settings/users/TimeoutToUserZeroSettings.java create mode 100644 tests/robotests/src/com/android/settings/users/TimeoutToUserZeroPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/users/TimeoutToUserZeroSettingsTest.java diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 0e39d6dd9a5..a3e53f55d5b 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1619,4 +1619,22 @@ 0 1 + + + + Never + After 1 minute + After 5 minutes + + + + + + 0 + + 60000 + + 300000 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 7f26c8d8f6f..aeb44f2bed3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7543,6 +7543,9 @@ Lock screen settings Add users from lock screen + + Switch to admin user when docked Delete yourself? diff --git a/res/xml/user_settings.xml b/res/xml/user_settings.xml index b3dc2ea583f..d5e61d022ee 100644 --- a/res/xml/user_settings.xml +++ b/res/xml/user_settings.xml @@ -56,4 +56,11 @@ android:order="105" settings:allowDividerAbove="true"/> + + diff --git a/res/xml/user_timeout_to_user_zero_settings.xml b/res/xml/user_timeout_to_user_zero_settings.xml new file mode 100644 index 00000000000..54121a3b2d9 --- /dev/null +++ b/res/xml/user_timeout_to_user_zero_settings.xml @@ -0,0 +1,20 @@ + + + + diff --git a/src/com/android/settings/users/TimeoutToUserZeroPreferenceController.java b/src/com/android/settings/users/TimeoutToUserZeroPreferenceController.java new file mode 100644 index 00000000000..c332726f617 --- /dev/null +++ b/src/com/android/settings/users/TimeoutToUserZeroPreferenceController.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.users; + +import static android.provider.Settings.Secure.TIMEOUT_TO_USER_ZERO; + +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; + +import java.util.Arrays; + +/** + * Controls the preference which launches a settings screen for user to configure whether to + * automatically switch to the admin user when the device is docked. + */ +public class TimeoutToUserZeroPreferenceController extends BasePreferenceController { + private final String[] mEntries; + private final String[] mValues; + + public TimeoutToUserZeroPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + + mEntries = mContext.getResources().getStringArray( + com.android.settings.R.array.switch_to_user_zero_when_docked_timeout_entries); + mValues = mContext.getResources().getStringArray( + com.android.settings.R.array.switch_to_user_zero_when_docked_timeout_values); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + updateState(screen.findPreference(getPreferenceKey())); + } + + @Override + public int getAvailabilityStatus() { + // Feature not available on device. + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableTimeoutToUserZeroWhenDocked)) { + return UNSUPPORTED_ON_DEVICE; + } + + // Multi-user feature disabled by user. + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_SWITCHER_ENABLED, 0) != 1) { + return CONDITIONALLY_UNAVAILABLE; + } + + // Is currently user zero. Only non user zero can have this setting. + if (UserHandle.myUserId() == UserHandle.USER_SYSTEM) { + return DISABLED_FOR_USER; + } + + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final String key = Settings.Secure.getStringForUser(mContext.getContentResolver(), + TIMEOUT_TO_USER_ZERO, UserHandle.myUserId()); + final int index = Arrays.asList(mValues).indexOf(key != null ? key : + mValues[TimeoutToUserZeroSettings.DEFAULT_TIMEOUT_SETTING_VALUE_INDEX]); + + return mEntries[index]; + } +} diff --git a/src/com/android/settings/users/TimeoutToUserZeroSettings.java b/src/com/android/settings/users/TimeoutToUserZeroSettings.java new file mode 100644 index 00000000000..7621ce54969 --- /dev/null +++ b/src/com/android/settings/users/TimeoutToUserZeroSettings.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.users; + +import static android.provider.Settings.Secure.TIMEOUT_TO_USER_ZERO; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.widget.RadioButtonPickerFragment; +import com.android.settingslib.widget.CandidateInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Setting screen that lists options for users to configure whether to automatically switch to the + * admin user when the device is docked, and if so duration of the timeout. + */ +public class TimeoutToUserZeroSettings extends RadioButtonPickerFragment { + // Index of the default key of the timeout setting if it hasn't been changed by the user. + public static final int DEFAULT_TIMEOUT_SETTING_VALUE_INDEX = 0; + + // Labels of the options, for example, "never", "after 5 minutes". + private String[] mEntries; + + // Values and keys of the options. + private String[] mValues; + + @Override + public int getMetricsCategory() { + return SettingsEnums.TIMEOUT_TO_USER_ZERO; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.user_timeout_to_user_zero_settings; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + mEntries = getContext().getResources().getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_entries); + mValues = getContext().getResources().getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_values); + } + + @Override + protected List getCandidates() { + final List candidates = new ArrayList<>(); + + if (mEntries == null || mValues == null) { + return candidates; + } + + for (int i = 0; i < mValues.length; i++) { + candidates.add(new TimeoutCandidateInfo(mEntries[i], mValues[i], true)); + } + + return candidates; + } + + @Override + protected String getDefaultKey() { + final String defaultKey = Settings.Secure.getStringForUser( + getContext().getContentResolver(), TIMEOUT_TO_USER_ZERO, UserHandle.myUserId()); + return defaultKey != null ? defaultKey : mValues[DEFAULT_TIMEOUT_SETTING_VALUE_INDEX]; + } + + @Override + protected boolean setDefaultKey(String key) { + Settings.Secure.putStringForUser(getContext().getContentResolver(), TIMEOUT_TO_USER_ZERO, + key, UserHandle.myUserId()); + return true; + } + + private static class TimeoutCandidateInfo extends CandidateInfo { + private final CharSequence mLabel; + private final String mKey; + + TimeoutCandidateInfo(CharSequence label, String key, boolean enabled) { + super(enabled); + mLabel = label; + mKey = key; + } + + @Override + public CharSequence loadLabel() { + return mLabel; + } + + @Override + public Drawable loadIcon() { + return null; + } + + @Override + public String getKey() { + return mKey; + } + } +} diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 9398d20e7ad..3ea15026bcd 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -118,6 +118,7 @@ public class UserSettings extends SettingsPreferenceFragment private static final String KEY_ADD_SUPERVISED_USER = "supervised_user_add"; private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked"; private static final String KEY_MULTIUSER_TOP_INTRO = "multiuser_top_intro"; + private static final String KEY_TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero_preference"; private static final int MENU_REMOVE_USER = Menu.FIRST; @@ -185,6 +186,7 @@ public class UserSettings extends SettingsPreferenceFragment new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY); private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; private MultiUserTopIntroPreferenceController mMultiUserTopIntroPreferenceController; + private TimeoutToUserZeroPreferenceController mTimeoutToUserZeroPreferenceController; private UserCreatingDialog mUserCreatingDialog; private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean(); private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @@ -264,9 +266,13 @@ public class UserSettings extends SettingsPreferenceFragment mMultiUserTopIntroPreferenceController = new MultiUserTopIntroPreferenceController(activity, KEY_MULTIUSER_TOP_INTRO); + mTimeoutToUserZeroPreferenceController = new TimeoutToUserZeroPreferenceController(activity, + KEY_TIMEOUT_TO_USER_ZERO); + final PreferenceScreen screen = getPreferenceScreen(); mAddUserWhenLockedPreferenceController.displayPreference(screen); mMultiUserTopIntroPreferenceController.displayPreference(screen); + mTimeoutToUserZeroPreferenceController.displayPreference(screen); screen.findPreference(mAddUserWhenLockedPreferenceController.getPreferenceKey()) .setOnPreferenceChangeListener(mAddUserWhenLockedPreferenceController); @@ -327,6 +333,8 @@ public class UserSettings extends SettingsPreferenceFragment mAddUserWhenLockedPreferenceController.updateState(screen.findPreference( mAddUserWhenLockedPreferenceController.getPreferenceKey())); + mTimeoutToUserZeroPreferenceController.updateState(screen.findPreference( + mTimeoutToUserZeroPreferenceController.getPreferenceKey())); if (mShouldUpdateUserList) { updateUI(); diff --git a/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroPreferenceControllerTest.java new file mode 100644 index 00000000000..e284d906847 --- /dev/null +++ b/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroPreferenceControllerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.users; + +import static android.provider.Settings.Secure.TIMEOUT_TO_USER_ZERO; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.fuelgauge.BatteryBackupHelperTest.ShadowUserHandle; +import com.android.settings.testutils.shadow.ShadowSecureSettings; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSecureSettings.class, ShadowUserHandle.class}) +public class TimeoutToUserZeroPreferenceControllerTest { + private Context mContext; + private Resources mResources; + private TimeoutToUserZeroPreferenceController mController; + + private static final String FAKE_PREFERENCE_KEY = "timeout_to_user_zero_preference"; + + private String[] mEntries; + private String[] mValues; + + @Before + public void setUp() { + mContext = spy(ApplicationProvider.getApplicationContext()); + mResources = spy(mContext.getResources()); + doReturn(mResources).when(mContext).getResources(); + + mEntries = mResources.getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_entries); + mValues = mResources.getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_values); + + mController = new TimeoutToUserZeroPreferenceController(mContext, FAKE_PREFERENCE_KEY); + + // Feature enabled. + when(mResources.getBoolean( + com.android.internal.R.bool.config_enableTimeoutToUserZeroWhenDocked)).thenReturn( + true); + + // Multi-user feature enabled. + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, + 1); + + // Set to user 1; + ShadowUserHandle.setUid(1); + } + + @After + public void tearDown() { + ShadowUserHandle.reset(); + } + + @Test + public void getAvailabilityStatus_featureFlagDisabled_returnUnsupportedOnDevice() { + when(mResources.getBoolean( + com.android.internal.R.bool.config_enableTimeoutToUserZeroWhenDocked)).thenReturn( + false); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_multiUserDisabled_returnConditionallyUnavailable() { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, + 0); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_isCurrentlyUserZero_returnDisabledForUser() { + ShadowUserHandle.setUid(UserHandle.USER_SYSTEM); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_featureAndMultiUserEnabledAndNonUserZero_returnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void getSummary_settingNotSet() { + Settings.Secure.putStringForUser(mContext.getContentResolver(), TIMEOUT_TO_USER_ZERO, + null, UserHandle.myUserId()); + + assertThat(mController.getSummary().toString()).isEqualTo(mEntries[0]); + } + + @Test + public void getSummary_setToNever() { + Settings.Secure.putStringForUser(mContext.getContentResolver(), TIMEOUT_TO_USER_ZERO, + mValues[0], UserHandle.myUserId()); + + assertThat(mController.getSummary().toString()).isEqualTo(mEntries[0]); + } + + @Test + public void getSummary_setToOneMinute() { + Settings.Secure.putStringForUser(mContext.getContentResolver(), TIMEOUT_TO_USER_ZERO, + mValues[1], UserHandle.myUserId()); + + assertThat(mController.getSummary().toString()).isEqualTo(mEntries[1]); + } +} diff --git a/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroSettingsTest.java b/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroSettingsTest.java new file mode 100644 index 00000000000..0bd5b601170 --- /dev/null +++ b/tests/robotests/src/com/android/settings/users/TimeoutToUserZeroSettingsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.users; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowFragment; +import com.android.settingslib.widget.CandidateInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowFragment.class}) +public class TimeoutToUserZeroSettingsTest { + @Mock + private FragmentActivity mActivity; + + private TimeoutToUserZeroSettings mSettings; + + private String[] mEntries; + private String[] mValues; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final Context context = spy(ApplicationProvider.getApplicationContext()); + mEntries = context.getResources().getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_entries); + mValues = context.getResources().getStringArray( + R.array.switch_to_user_zero_when_docked_timeout_values); + mSettings = spy(new TimeoutToUserZeroSettings()); + + doReturn(context).when(mSettings).getContext(); + doReturn(mActivity).when(mSettings).getActivity(); + + mSettings.onAttach(context); + } + + @Test + public void getCandidates_returnsACandidateForEachEntry() { + final List candidates = mSettings.getCandidates(); + + for (int i = 0; i < mEntries.length; i++) { + assertThat(candidates.get(i).loadLabel().toString()).isEqualTo(mEntries[i]); + } + } + + @Test + public void defaultKey_settingNotSet_shouldReturnFirstValueAsDefault() { + assertThat(mSettings.getDefaultKey()).isEqualTo(mValues[0]); + } + + @Test + public void defaultKey_setToFirstValue_shouldSaveToSettings() { + final String expectedKey = mValues[0]; + mSettings.setDefaultKey(expectedKey); + assertThat(mSettings.getDefaultKey()).isEqualTo(expectedKey); + } + + @Test + public void defaultKey_setToSecondValue_shouldSaveToSettings() { + final String expectedKey = mValues[1]; + mSettings.setDefaultKey(expectedKey); + assertThat(mSettings.getDefaultKey()).isEqualTo(expectedKey); + } +}