Merge "Add ScreenResolutionController to Settings app" into tm-dev
diff --git a/res/drawable/screen_resolution_1080p.xml b/res/drawable/screen_resolution_1080p.xml
new file mode 100644
index 0000000..a9d89ee
--- /dev/null
+++ b/res/drawable/screen_resolution_1080p.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<shape>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/screen_resolution_1440p.xml b/res/drawable/screen_resolution_1440p.xml
new file mode 100644
index 0000000..a9d89ee
--- /dev/null
+++ b/res/drawable/screen_resolution_1440p.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<shape>
+</shape>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 216d9e0..8933305 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -580,6 +580,18 @@
-->
</string-array>
+ <!-- The option list for switch screen resolution -->
+ <string-array name="config_screen_resolution_options_strings" translatable="false">
+ <item>@string/screen_resolution_option_high</item>
+ <item>@string/screen_resolution_option_highest</item>
+ </string-array>
+
+ <!-- The option summary list for screen resolution -->
+ <string-array name="config_screen_resolution_summaries_strings" translatable="false">
+ <item>@string/screen_resolution_summary_high</item>
+ <item>@string/screen_resolution_summary_highest</item>
+ </string-array>
+
<!-- Whether to aggregate for network selection list-->
<bool name="config_network_selection_list_aggregation_enabled">false</bool>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dc95d93..02bc5dd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2908,6 +2908,18 @@
<string name="auto_rotate_option_face_based">On - Face-based</string>
<!-- SmartAutoRotatePreferenceFragment settings screen, face-based rotation switch label [CHAR LIMIT=30] -->
<string name="auto_rotate_switch_face_based">Enable Face Detection</string>
+
+ <!-- Display settings screen, screen resolution settings title [CHAR LIMIT=30] -->
+ <string name="screen_resolution_title">Screen resolution</string>
+ <!-- Display settings screen, screen resolution option for "FHD+" [CHAR LIMIT=45] -->
+ <string name="screen_resolution_option_high">High resolution</string>
+ <!-- Display settings screen, screen resolution option for "QHD+" [CHAR LIMIT=45] -->
+ <string name="screen_resolution_option_highest">Highest resolution</string>
+ <!-- Display settings screen, "FHD+" screen resolution summary [CHAR LIMIT=NONE] -->
+ <string name="screen_resolution_summary_high">1080p FHD+</string>
+ <!-- Display settings screen, "QHD+" screen resolution summary [CHAR LIMIT=NONE] -->
+ <string name="screen_resolution_summary_highest">1440p QHD+</string>
+
<!-- Display settings screen, Color mode settings title [CHAR LIMIT=30] -->
<string name="color_mode_title">Colors</string>
<!-- Display settings screen, Color mode option for "natural(sRGB) color" [CHAR LIMIT=45] -->
@@ -8269,6 +8281,8 @@
<string name="keywords_default_apps">apps, default</string>
<string name="keywords_ignore_optimizations">ignore optimizations, doze, app standby</string>
<string name="keywords_color_mode">vibrant, RGB, sRGB, color, natural, standard</string>
+ <!-- Search keyword for "screen resolution" settings [CHAR_LIMIT=NONE]-->
+ <string name="keywords_screen_resolution">FHD, QHD, resolution, 1080p, 1440p</string>
<string name="keywords_color_temperature">color, temperature, D65, D73, white, yellow, blue, warm, cool</string>
<string name="keywords_lockscreen">slide to unlock, password, pattern, PIN</string>
<!-- Search keyword for App pinning Settings [CHAR LIMIT=NONE] -->
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index ba52a30..03d073b 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -117,6 +117,13 @@
settings:keywords="@string/keywords_auto_rotate"
settings:controller="com.android.settings.display.DeviceStateAutoRotateOverviewController"/>
+ <Preference
+ android:key="screen_resolution"
+ android:title="@string/screen_resolution_title"
+ android:fragment="com.android.settings.display.ScreenResolutionFragment"
+ settings:keywords="@string/keywords_screen_resolution"
+ settings:controller="com.android.settings.display.ScreenResolutionController"/>
+
<SwitchPreference
android:key="display_white_balance"
android:title="@string/display_white_balance_title"
diff --git a/res/xml/screen_resolution_settings.xml b/res/xml/screen_resolution_settings.xml
new file mode 100644
index 0000000..a305488
--- /dev/null
+++ b/res/xml/screen_resolution_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/screen_resolution_title"
+ android:key="screen_resolution" />
diff --git a/src/com/android/settings/display/ScreenResolutionController.java b/src/com/android/settings/display/ScreenResolutionController.java
new file mode 100644
index 0000000..dca1275
--- /dev/null
+++ b/src/com/android/settings/display/ScreenResolutionController.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.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/** Controller that switch the screen resolution. */
+public class ScreenResolutionController extends BasePreferenceController {
+
+ static final int FHD_WIDTH = 1080;
+ static final int QHD_WIDTH = 1440;
+
+ private Display mDisplay;
+
+ public ScreenResolutionController(Context context, String key) {
+ super(context, key);
+
+ mDisplay =
+ mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY);
+ }
+
+ /** Check if the width is supported by the display. */
+ private boolean isSupportedMode(int width) {
+ for (Display.Mode mode : getSupportedModes()) {
+ if (mode.getPhysicalWidth() == width) return true;
+ }
+ return false;
+ }
+
+ /** Return true if the device contains two (or more) resolutions. */
+ protected boolean checkSupportedResolutions() {
+ return isSupportedMode(FHD_WIDTH) && isSupportedMode(QHD_WIDTH);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return (checkSupportedResolutions()) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ String summary = null;
+ switch (getDisplayWidth()) {
+ case FHD_WIDTH:
+ summary = mContext.getString(R.string.screen_resolution_summary_high);
+ break;
+ case QHD_WIDTH:
+ summary = mContext.getString(R.string.screen_resolution_summary_highest);
+ break;
+ default:
+ summary = mContext.getString(R.string.screen_resolution_title);
+ }
+
+ return summary;
+ }
+
+ @VisibleForTesting
+ public int getDisplayWidth() {
+ return mDisplay.getMode().getPhysicalWidth();
+ }
+
+ @VisibleForTesting
+ public Display.Mode[] getSupportedModes() {
+ return mDisplay.getSupportedModes();
+ }
+}
diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java
new file mode 100644
index 0000000..3195772
--- /dev/null
+++ b/src/com/android/settings/display/ScreenResolutionFragment.java
@@ -0,0 +1,217 @@
+/*
+ * 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.display;
+
+import static com.android.settings.display.ScreenResolutionController.FHD_WIDTH;
+import static com.android.settings.display.ScreenResolutionController.QHD_WIDTH;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.text.TextUtils;
+import android.view.Display;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.CandidateInfo;
+import com.android.settingslib.widget.IllustrationPreference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Preference fragment used for switch screen resolution */
+@SearchIndexable
+public class ScreenResolutionFragment extends RadioButtonPickerFragment {
+
+ private static final String TAG = "ScreenResolution";
+
+ private Resources mResources;
+ private static final int FHD_INDEX = 0;
+ private static final int QHD_INDEX = 1;
+ private Display mDefaultDisplay;
+ private String[] mScreenResolutionOptions;
+ private Set<Point> mResolutions;
+
+ private IllustrationPreference mImagePreference;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ mDefaultDisplay =
+ context.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY);
+ mResources = context.getResources();
+ mScreenResolutionOptions =
+ mResources.getStringArray(R.array.config_screen_resolution_options_strings);
+ mResolutions = getAllSupportedResolution();
+ mImagePreference = new IllustrationPreference(context);
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.screen_resolution_settings;
+ }
+
+ @Override
+ protected void addStaticPreferences(PreferenceScreen screen) {
+ updateIllustrationImage(mImagePreference);
+ screen.addPreference(mImagePreference);
+ }
+
+ @Override
+ protected List<? extends CandidateInfo> getCandidates() {
+ final List<ScreenResolutionCandidateInfo> candidates = new ArrayList<>();
+
+ for (int i = 0; i < mScreenResolutionOptions.length; i++) {
+ candidates.add(
+ new ScreenResolutionCandidateInfo(
+ mScreenResolutionOptions[i],
+ mScreenResolutionOptions[i],
+ true /* enabled */));
+ }
+
+ return candidates;
+ }
+
+ /** Get all supported resolutions on the device. */
+ private Set<Point> getAllSupportedResolution() {
+ Set<Point> resolutions = new HashSet<>();
+ for (Display.Mode mode : mDefaultDisplay.getSupportedModes()) {
+ resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
+ }
+
+ return resolutions;
+ }
+
+ /** Get prefer display mode. */
+ private Display.Mode getPreferMode(int width) {
+ for (Point resolution : mResolutions) {
+ if (resolution.x == width) {
+ return new Display.Mode(
+ resolution.x, resolution.y, getDisplayMode().getRefreshRate());
+ }
+ }
+
+ return getDisplayMode();
+ }
+
+ /** Get current display mode. */
+ @VisibleForTesting
+ public Display.Mode getDisplayMode() {
+ return mDefaultDisplay.getMode();
+ }
+
+ /** Using display manager to set the display mode. */
+ @VisibleForTesting
+ public void setDisplayMode(int width) {
+ mDefaultDisplay.setUserPreferredDisplayMode(getPreferMode(width));
+ }
+
+ /** Get the key corresponding to the resolution. */
+ @VisibleForTesting
+ String getKeyForResolution(int width) {
+ return width == FHD_WIDTH
+ ? mScreenResolutionOptions[FHD_INDEX]
+ : width == QHD_WIDTH ? mScreenResolutionOptions[QHD_INDEX] : null;
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ int physicalWidth = getDisplayMode().getPhysicalWidth();
+
+ return getKeyForResolution(physicalWidth);
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ if (mScreenResolutionOptions[FHD_INDEX].equals(key)) {
+ setDisplayMode(FHD_WIDTH);
+
+ } else if (mScreenResolutionOptions[QHD_INDEX].equals(key)) {
+ setDisplayMode(QHD_WIDTH);
+ }
+
+ updateIllustrationImage(mImagePreference);
+ return true;
+ }
+
+ /** Update the resolution image according display mode. */
+ private void updateIllustrationImage(IllustrationPreference preference) {
+ String key = getDefaultKey();
+
+ if (TextUtils.equals(mScreenResolutionOptions[FHD_INDEX], key)) {
+ preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p);
+ } else if (TextUtils.equals(mScreenResolutionOptions[QHD_INDEX], key)) {
+ preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SCREEN_RESOLUTION;
+ }
+
+ static class ScreenResolutionCandidateInfo extends CandidateInfo {
+ private final CharSequence mLabel;
+ private final String mKey;
+
+ ScreenResolutionCandidateInfo(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;
+ }
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.screen_resolution_settings) {
+
+ boolean mIsFHDSupport = false;
+ boolean mIsQHDSupport = false;
+
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ ScreenResolutionController mController =
+ new ScreenResolutionController(context, "fragment");
+ return mController.checkSupportedResolutions();
+ }
+ };
+}
diff --git a/tests/unit/src/com/android/settings/display/ScreenResolutionControllerTest.java b/tests/unit/src/com/android/settings/display/ScreenResolutionControllerTest.java
new file mode 100644
index 0000000..a32904e
--- /dev/null
+++ b/tests/unit/src/com/android/settings/display/ScreenResolutionControllerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.display;
+
+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 android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ScreenResolutionControllerTest {
+
+ private static final int FHD_WIDTH = 1080;
+ private static final int QHD_WIDTH = 1440;
+
+ private ScreenResolutionController mController;
+
+ @Before
+ public void setUp() {
+ Context context = spy(ApplicationProvider.getApplicationContext());
+ mController = spy(new ScreenResolutionController(context, "test"));
+ }
+
+ @Test
+ public void getAvailabilityStatus_hasFhdAndQhdModes_returnAvailable() {
+ Display.Mode modeA = new Display.Mode(0, FHD_WIDTH, 0, 0);
+ Display.Mode modeB = new Display.Mode(0, QHD_WIDTH, 0, 0);
+ Display.Mode[] modes = {modeA, modeB};
+ doReturn(modes).when(mController).getSupportedModes();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_hasOneMode_returnUnsupported() {
+ Display.Mode modeA = new Display.Mode(0, FHD_WIDTH, 0, 0);
+ Display.Mode[] modes = {modeA};
+ doReturn(modes).when(mController).getSupportedModes();
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void updateState_screenResolutionFHD_shouldSetSummaryToFHD() {
+ int width = FHD_WIDTH;
+ doReturn(width).when(mController).getDisplayWidth();
+
+ assertThat(mController.getSummary().toString()).isEqualTo("1080p FHD+");
+ }
+
+ @Test
+ public void updateState_screenResolutionQHD_shouldSetSummaryToQHD() {
+ int width = QHD_WIDTH;
+ doReturn(width).when(mController).getDisplayWidth();
+
+ assertThat(mController.getSummary().toString()).isEqualTo("1440p QHD+");
+ }
+}
diff --git a/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java b/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java
new file mode 100644
index 0000000..225a1d9
--- /dev/null
+++ b/tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.display;
+
+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.verify;
+
+import android.content.Context;
+import android.view.Display;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ScreenResolutionFragmentTest {
+
+ private Context mContext;
+ private ScreenResolutionFragment mFragment;
+
+ private static final int FHD_WIDTH = 1080;
+ private static final int QHD_WIDTH = 1440;
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mFragment = spy(new ScreenResolutionFragment());
+ }
+
+ @Test
+ @UiThreadTest
+ public void getDefaultKey_FHD() {
+ Display.Mode mode = new Display.Mode(0, FHD_WIDTH, 0, 0);
+ doReturn(mode).when(mFragment).getDisplayMode();
+
+ mFragment.onAttach(mContext);
+ assertThat(mFragment.getDefaultKey()).isEqualTo(mFragment.getKeyForResolution(FHD_WIDTH));
+ }
+
+ @Test
+ @UiThreadTest
+ public void getDefaultKey_QHD() {
+ Display.Mode mode = new Display.Mode(0, QHD_WIDTH, 0, 0);
+ doReturn(mode).when(mFragment).getDisplayMode();
+
+ mFragment.onAttach(mContext);
+ assertThat(mFragment.getDefaultKey()).isEqualTo(mFragment.getKeyForResolution(QHD_WIDTH));
+ }
+
+ @Test
+ @UiThreadTest
+ public void setDefaultKey_FHD() {
+ mFragment.onAttach(mContext);
+
+ mFragment.setDefaultKey(mFragment.getKeyForResolution(FHD_WIDTH));
+
+ verify(mFragment).setDisplayMode(FHD_WIDTH);
+ }
+
+ @Test
+ @UiThreadTest
+ public void setDefaultKey_QHD() {
+ mFragment.onAttach(mContext);
+
+ mFragment.setDefaultKey(mFragment.getKeyForResolution(QHD_WIDTH));
+
+ verify(mFragment).setDisplayMode(QHD_WIDTH);
+ }
+}