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
This commit is contained in:
Amy Hsu
2022-02-09 13:58:16 +08:00
committed by Tsung-Mao Fang
parent c08c6c5c16
commit 0dea418d57
10 changed files with 572 additions and 0 deletions

View File

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

View File

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

View File

@@ -583,6 +583,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>

View File

@@ -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] -->
@@ -8265,6 +8277,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] -->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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