• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.display;
18 
19 import static com.android.settings.display.ScreenResolutionController.FHD_WIDTH;
20 import static com.android.settings.display.ScreenResolutionController.QHD_WIDTH;
21 
22 import android.annotation.Nullable;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.graphics.Point;
27 import android.graphics.drawable.Drawable;
28 import android.hardware.display.DisplayManager;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 import android.view.Display;
32 import android.view.accessibility.AccessibilityEvent;
33 import android.view.accessibility.AccessibilityManager;
34 
35 import androidx.annotation.VisibleForTesting;
36 import androidx.preference.PreferenceScreen;
37 
38 import com.android.settings.R;
39 import com.android.settings.search.BaseSearchIndexProvider;
40 import com.android.settings.widget.RadioButtonPickerFragment;
41 import com.android.settingslib.display.DisplayDensityUtils;
42 import com.android.settingslib.search.SearchIndexable;
43 import com.android.settingslib.widget.CandidateInfo;
44 import com.android.settingslib.widget.FooterPreference;
45 import com.android.settingslib.widget.IllustrationPreference;
46 import com.android.settingslib.widget.SelectorWithWidgetPreference;
47 
48 import java.util.ArrayList;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.concurrent.atomic.AtomicInteger;
53 
54 /** Preference fragment used for switch screen resolution */
55 @SearchIndexable
56 public class ScreenResolutionFragment extends RadioButtonPickerFragment {
57     private static final String TAG = "ScreenResolution";
58 
59     private Resources mResources;
60     private static final int FHD_INDEX = 0;
61     private static final int QHD_INDEX = 1;
62     private static final String RESOLUTION_METRIC_SETTING_KEY = "user_selected_resolution";
63     private Display mDefaultDisplay;
64     private String[] mScreenResolutionOptions;
65     private Set<Point> mResolutions;
66     private String[] mScreenResolutionSummaries;
67 
68     private IllustrationPreference mImagePreference;
69     private DisplayObserver mDisplayObserver;
70     private AccessibilityManager mAccessibilityManager;
71 
72     @Override
onAttach(Context context)73     public void onAttach(Context context) {
74         super.onAttach(context);
75 
76         mDefaultDisplay =
77                 context.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY);
78         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
79         mResources = context.getResources();
80         mScreenResolutionOptions =
81                 mResources.getStringArray(R.array.config_screen_resolution_options_strings);
82         mScreenResolutionSummaries =
83                 mResources.getStringArray(R.array.config_screen_resolution_summaries_strings);
84         mResolutions = getAllSupportedResolution();
85         mImagePreference = new IllustrationPreference(context);
86         mDisplayObserver = new DisplayObserver(context);
87     }
88 
89     @Override
getPreferenceScreenResId()90     protected int getPreferenceScreenResId() {
91         return R.xml.screen_resolution_settings;
92     }
93 
94     @Override
addStaticPreferences(PreferenceScreen screen)95     protected void addStaticPreferences(PreferenceScreen screen) {
96         updateIllustrationImage(mImagePreference);
97         screen.addPreference(mImagePreference);
98 
99         final FooterPreference footerPreference = new FooterPreference(screen.getContext());
100         footerPreference.setTitle(R.string.screen_resolution_footer);
101         footerPreference.setSelectable(false);
102         footerPreference.setLayoutResource(R.layout.preference_footer);
103         screen.addPreference(footerPreference);
104     }
105 
106     @Override
bindPreferenceExtra( SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)107     public void bindPreferenceExtra(
108             SelectorWithWidgetPreference pref,
109             String key,
110             CandidateInfo info,
111             String defaultKey,
112             String systemDefaultKey) {
113         final ScreenResolutionCandidateInfo candidateInfo = (ScreenResolutionCandidateInfo) info;
114         final CharSequence summary = candidateInfo.loadSummary();
115         if (summary != null) pref.setSummary(summary);
116     }
117 
118     @Override
getCandidates()119     protected List<? extends CandidateInfo> getCandidates() {
120         final List<ScreenResolutionCandidateInfo> candidates = new ArrayList<>();
121 
122         for (int i = 0; i < mScreenResolutionOptions.length; i++) {
123             candidates.add(
124                     new ScreenResolutionCandidateInfo(
125                             mScreenResolutionOptions[i],
126                             mScreenResolutionSummaries[i],
127                             mScreenResolutionOptions[i],
128                             true /* enabled */));
129         }
130 
131         return candidates;
132     }
133 
134     /** Get all supported resolutions on the device. */
getAllSupportedResolution()135     private Set<Point> getAllSupportedResolution() {
136         Set<Point> resolutions = new HashSet<>();
137         for (Display.Mode mode : mDefaultDisplay.getSupportedModes()) {
138             resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
139         }
140 
141         return resolutions;
142     }
143 
144     /** Get prefer display mode. */
getPreferMode(int width)145     private Display.Mode getPreferMode(int width) {
146         for (Point resolution : mResolutions) {
147             if (resolution.x == width) {
148                 return new Display.Mode(
149                         resolution.x, resolution.y, getDisplayMode().getRefreshRate());
150             }
151         }
152 
153         return getDisplayMode();
154     }
155 
156     /** Get current display mode. */
157     @VisibleForTesting
getDisplayMode()158     public Display.Mode getDisplayMode() {
159         return mDefaultDisplay.getMode();
160     }
161 
162     /** Using display manager to set the display mode. */
163     @VisibleForTesting
setDisplayMode(final int width)164     public void setDisplayMode(final int width) {
165         mDisplayObserver.startObserve();
166 
167         /** For store settings globally. */
168         /** TODO(b/238061217): Moving to an atom with the same string */
169         Settings.System.putString(
170                 getContext().getContentResolver(),
171                 RESOLUTION_METRIC_SETTING_KEY,
172                 getPreferMode(width).getPhysicalWidth()
173                         + "x"
174                         + getPreferMode(width).getPhysicalHeight());
175 
176         /** Apply the resolution change. */
177         mDefaultDisplay.setUserPreferredDisplayMode(getPreferMode(width));
178     }
179 
180     /** Get the key corresponding to the resolution. */
181     @VisibleForTesting
getKeyForResolution(int width)182     String getKeyForResolution(int width) {
183         return width == FHD_WIDTH
184                 ? mScreenResolutionOptions[FHD_INDEX]
185                 : width == QHD_WIDTH ? mScreenResolutionOptions[QHD_INDEX] : null;
186     }
187 
188     /** Get the width corresponding to the resolution key. */
getWidthForResoluitonKey(String key)189     int getWidthForResoluitonKey(String key) {
190         return mScreenResolutionOptions[FHD_INDEX].equals(key)
191                 ? FHD_WIDTH
192                 : mScreenResolutionOptions[QHD_INDEX].equals(key) ? QHD_WIDTH : -1;
193     }
194 
195     @Override
getDefaultKey()196     protected String getDefaultKey() {
197         int physicalWidth = getDisplayMode().getPhysicalWidth();
198 
199         return getKeyForResolution(physicalWidth);
200     }
201 
202     @Override
setDefaultKey(final String key)203     protected boolean setDefaultKey(final String key) {
204         int width = getWidthForResoluitonKey(key);
205         if (width < 0) {
206             return false;
207         }
208 
209         setDisplayMode(width);
210         updateIllustrationImage(mImagePreference);
211 
212         return true;
213     }
214 
215     @Override
onRadioButtonClicked(SelectorWithWidgetPreference selected)216     public void onRadioButtonClicked(SelectorWithWidgetPreference selected) {
217         String selectedKey = selected.getKey();
218         int selectedWidth = getWidthForResoluitonKey(selectedKey);
219         if (!mDisplayObserver.setPendingResolutionChange(selectedWidth)) {
220             return;
221         }
222 
223         if (mAccessibilityManager.isEnabled()) {
224             AccessibilityEvent event = AccessibilityEvent.obtain();
225             event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
226             event.getText().add(mResources.getString(R.string.screen_resolution_selected_a11y));
227             mAccessibilityManager.sendAccessibilityEvent(event);
228         }
229 
230         super.onRadioButtonClicked(selected);
231     }
232 
233     /** Update the resolution image according display mode. */
updateIllustrationImage(IllustrationPreference preference)234     private void updateIllustrationImage(IllustrationPreference preference) {
235         String key = getDefaultKey();
236 
237         if (TextUtils.equals(mScreenResolutionOptions[FHD_INDEX], key)) {
238             preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p);
239         } else if (TextUtils.equals(mScreenResolutionOptions[QHD_INDEX], key)) {
240             preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p);
241         }
242     }
243 
244     @Override
getMetricsCategory()245     public int getMetricsCategory() {
246         return SettingsEnums.SCREEN_RESOLUTION;
247     }
248 
249     /** This is an extension of the CandidateInfo class, which adds summary information. */
250     public static class ScreenResolutionCandidateInfo extends CandidateInfo {
251         private final CharSequence mLabel;
252         private final CharSequence mSummary;
253         private final String mKey;
254 
ScreenResolutionCandidateInfo( CharSequence label, CharSequence summary, String key, boolean enabled)255         ScreenResolutionCandidateInfo(
256                 CharSequence label, CharSequence summary, String key, boolean enabled) {
257             super(enabled);
258             mLabel = label;
259             mSummary = summary;
260             mKey = key;
261         }
262 
263         @Override
loadLabel()264         public CharSequence loadLabel() {
265             return mLabel;
266         }
267 
268         /** It is the summary for radio options. */
loadSummary()269         public CharSequence loadSummary() {
270             return mSummary;
271         }
272 
273         @Override
loadIcon()274         public Drawable loadIcon() {
275             return null;
276         }
277 
278         @Override
getKey()279         public String getKey() {
280             return mKey;
281         }
282     }
283 
284     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
285             new BaseSearchIndexProvider(R.xml.screen_resolution_settings) {
286                 @Override
287                 protected boolean isPageSearchEnabled(Context context) {
288                     ScreenResolutionController mController =
289                             new ScreenResolutionController(context, "fragment");
290                     return mController.checkSupportedResolutions();
291                 }
292             };
293 
294     private static final class DisplayObserver implements DisplayManager.DisplayListener {
295         private final @Nullable Context mContext;
296         private int mDefaultDensity;
297         private int mCurrentIndex;
298         private AtomicInteger mPreviousWidth = new AtomicInteger(-1);
299 
DisplayObserver(Context context)300         DisplayObserver(Context context) {
301             mContext = context;
302         }
303 
startObserve()304         public void startObserve() {
305             if (mContext == null) {
306                 return;
307             }
308 
309             final DisplayDensityUtils density = new DisplayDensityUtils(mContext);
310             final int currentIndex = density.getCurrentIndexForDefaultDisplay();
311             final int defaultDensity = density.getDefaultDensityForDefaultDisplay();
312 
313             if (density.getDefaultDisplayDensityValues()[mCurrentIndex]
314                     == density.getDefaultDensityForDefaultDisplay()) {
315                 return;
316             }
317 
318             mDefaultDensity = defaultDensity;
319             mCurrentIndex = currentIndex;
320             final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
321             dm.registerDisplayListener(this, null);
322         }
323 
stopObserve()324         public void stopObserve() {
325             if (mContext == null) {
326                 return;
327             }
328 
329             final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
330             dm.unregisterDisplayListener(this);
331         }
332 
333         @Override
onDisplayAdded(int displayId)334         public void onDisplayAdded(int displayId) {}
335 
336         @Override
onDisplayRemoved(int displayId)337         public void onDisplayRemoved(int displayId) {}
338 
339         @Override
onDisplayChanged(int displayId)340         public void onDisplayChanged(int displayId) {
341             if (displayId != Display.DEFAULT_DISPLAY) {
342                 return;
343             }
344 
345             if (!isDensityChanged() || !isResolutionChangeApplied()) {
346                 return;
347             }
348 
349             restoreDensity();
350             stopObserve();
351         }
352 
restoreDensity()353         private void restoreDensity() {
354             final DisplayDensityUtils density = new DisplayDensityUtils(mContext);
355             if (density.getDefaultDisplayDensityValues()[mCurrentIndex]
356                     != density.getDefaultDensityForDefaultDisplay()) {
357                 density.setForcedDisplayDensity(mCurrentIndex);
358             }
359 
360             mDefaultDensity = density.getDefaultDensityForDefaultDisplay();
361         }
362 
isDensityChanged()363         private boolean isDensityChanged() {
364             final DisplayDensityUtils density = new DisplayDensityUtils(mContext);
365             if (density.getDefaultDensityForDefaultDisplay() == mDefaultDensity) {
366                 return false;
367             }
368 
369             return true;
370         }
371 
getCurrentWidth()372         private int getCurrentWidth() {
373             final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
374             return dm.getDisplay(Display.DEFAULT_DISPLAY).getMode().getPhysicalWidth();
375         }
376 
setPendingResolutionChange(int selectedWidth)377         private boolean setPendingResolutionChange(int selectedWidth) {
378             int currentWidth = getCurrentWidth();
379 
380             if (selectedWidth == currentWidth) {
381                 return false;
382             }
383             if (mPreviousWidth.get() != -1 && !isResolutionChangeApplied()) {
384                 return false;
385             }
386 
387             mPreviousWidth.set(currentWidth);
388 
389             return true;
390         }
391 
isResolutionChangeApplied()392         private boolean isResolutionChangeApplied() {
393             if (mPreviousWidth.get() == getCurrentWidth()) {
394                 return false;
395             }
396 
397             return true;
398         }
399     }
400 }
401