From 0dea418d575db2d7b141ac4a93094e5533d3b59c Mon Sep 17 00:00:00 2001 From: Amy Hsu Date: Wed, 9 Feb 2022 13:58:16 +0800 Subject: [PATCH] Add ScreenResolutionController to Settings app Settings for resolution switch Bug: b/199559703 Test: Check resolution switch UI in Settings app atest SettingsUnitTests:ScreenResolutionControllerTest atest SettingsUnitTests:ScreenResolutionFragmentTest Change-Id: I46d3be3b82ca512b8672efaa489df2cdaab26d6d Merged-In: I46d3be3b82ca512b8672efaa489df2cdaab26d6d --- res/drawable/screen_resolution_1080p.xml | 19 ++ res/drawable/screen_resolution_1440p.xml | 19 ++ res/values/config.xml | 12 + res/values/strings.xml | 14 ++ res/xml/display_settings.xml | 7 + res/xml/screen_resolution_settings.xml | 21 ++ .../display/ScreenResolutionController.java | 87 +++++++ .../display/ScreenResolutionFragment.java | 217 ++++++++++++++++++ .../ScreenResolutionControllerTest.java | 86 +++++++ .../display/ScreenResolutionFragmentTest.java | 90 ++++++++ 10 files changed, 572 insertions(+) create mode 100644 res/drawable/screen_resolution_1080p.xml create mode 100644 res/drawable/screen_resolution_1440p.xml create mode 100644 res/xml/screen_resolution_settings.xml create mode 100644 src/com/android/settings/display/ScreenResolutionController.java create mode 100644 src/com/android/settings/display/ScreenResolutionFragment.java create mode 100644 tests/unit/src/com/android/settings/display/ScreenResolutionControllerTest.java create mode 100644 tests/unit/src/com/android/settings/display/ScreenResolutionFragmentTest.java diff --git a/res/drawable/screen_resolution_1080p.xml b/res/drawable/screen_resolution_1080p.xml new file mode 100644 index 00000000000..a9d89ee9997 --- /dev/null +++ b/res/drawable/screen_resolution_1080p.xml @@ -0,0 +1,19 @@ + + + + + \ 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 00000000000..a9d89ee9997 --- /dev/null +++ b/res/drawable/screen_resolution_1440p.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/res/values/config.xml b/res/values/config.xml index bf78fd7fc98..415d1fa9afa 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -583,6 +583,18 @@ --> + + + @string/screen_resolution_option_high + @string/screen_resolution_option_highest + + + + + @string/screen_resolution_summary_high + @string/screen_resolution_summary_highest + + false diff --git a/res/values/strings.xml b/res/values/strings.xml index 9454b5ce789..c7b71b18e55 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2908,6 +2908,18 @@ On - Face-based Enable Face Detection + + + Screen resolution + + High resolution + + Highest resolution + + 1080p FHD+ + + 1440p QHD+ + Colors @@ -8265,6 +8277,8 @@ apps, default ignore optimizations, doze, app standby vibrant, RGB, sRGB, color, natural, standard + + FHD, QHD, resolution, 1080p, 1440p color, temperature, D65, D73, white, yellow, blue, warm, cool slide to unlock, password, pattern, PIN diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml index ba52a301666..03d073b2e67 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"/> + + + + + diff --git a/src/com/android/settings/display/ScreenResolutionController.java b/src/com/android/settings/display/ScreenResolutionController.java new file mode 100644 index 00000000000..dca12757e18 --- /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 00000000000..31957723752 --- /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 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 getCandidates() { + final List 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 getAllSupportedResolution() { + Set 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 00000000000..a32904ecb35 --- /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 00000000000..225a1d9a216 --- /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); + } +}