• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.settingslib.display;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.hardware.display.DisplayManager;
22 import android.os.AsyncTask;
23 import android.os.RemoteException;
24 import android.os.UserHandle;
25 import android.util.DisplayMetrics;
26 import android.util.Log;
27 import android.util.MathUtils;
28 import android.view.Display;
29 import android.view.DisplayInfo;
30 import android.view.IWindowManager;
31 import android.view.WindowManagerGlobal;
32 import android.window.ConfigurationChangeSetting;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 
37 import com.android.settingslib.R;
38 
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.function.Predicate;
43 
44 /**
45  * Utility methods for working with display density.
46  */
47 public class DisplayDensityUtils {
48     private static final String LOG_TAG = "DisplayDensityUtils";
49 
50     /** Summary used for "default" scale. */
51     public static final int SUMMARY_DEFAULT = R.string.screen_zoom_summary_default;
52 
53     /** Summary used for "custom" scale. */
54     private static final int SUMMARY_CUSTOM = R.string.screen_zoom_summary_custom;
55 
56     /**
57      * Summaries for scales smaller than "default" in order of smallest to
58      * largest.
59      */
60     private static final int[] SUMMARIES_SMALLER = new int[]{
61             R.string.screen_zoom_summary_small
62     };
63 
64     /**
65      * Summaries for scales larger than "default" in order of smallest to
66      * largest.
67      */
68     private static final int[] SUMMARIES_LARGER = new int[]{
69             R.string.screen_zoom_summary_large,
70             R.string.screen_zoom_summary_very_large,
71             R.string.screen_zoom_summary_extremely_large,
72     };
73 
74     /**
75      * Minimum allowed screen dimension, corresponds to resource qualifiers
76      * "small" or "sw320dp". This value must be at least the minimum screen
77      * size required by the CDD so that we meet developer expectations.
78      */
79     private static final int MIN_DIMENSION_DP = 320;
80 
81     private static final Predicate<DisplayInfo> INTERNAL_ONLY =
82             (info) -> info.type == Display.TYPE_INTERNAL;
83 
84     private final Predicate<DisplayInfo> mPredicate;
85 
86     private final DisplayManager mDisplayManager;
87 
88     /**
89      * The text description of the density values.
90      */
91     @Nullable
92     private final String[] mEntries;
93 
94     /**
95      * The density values.
96      */
97     @Nullable
98     private final int[] mValues;
99 
100     /**
101      * The density values before rounding to an integer.
102      */
103     @Nullable
104     private final float[] mFloatValues;
105 
106     private final int mDefaultDensity;
107     private final int mCurrentIndex;
108 
DisplayDensityUtils(@onNull Context context)109     public DisplayDensityUtils(@NonNull Context context) {
110         this(context, INTERNAL_ONLY);
111     }
112 
113     /**
114      * Creates an instance that stores the density values for the smallest display that satisfies
115      * the predicate. It is enough to store the values for one display because the same density
116      * should be set to all the displays that satisfy the predicate.
117      *
118      * @param context   The context
119      * @param predicate Determines what displays the density should be set for. The default display
120      *                  must satisfy this predicate.
121      */
DisplayDensityUtils(@onNull Context context, @NonNull Predicate<DisplayInfo> predicate)122     public DisplayDensityUtils(@NonNull Context context,
123             @NonNull Predicate<DisplayInfo> predicate) {
124         mPredicate = predicate;
125         mDisplayManager = context.getSystemService(DisplayManager.class);
126 
127         Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
128         DisplayInfo defaultDisplayInfo = new DisplayInfo();
129         if (!defaultDisplay.getDisplayInfo(defaultDisplayInfo)) {
130             Log.w(LOG_TAG, "Cannot fetch display info for the default display");
131             mEntries = null;
132             mValues = null;
133             mFloatValues = null;
134             mDefaultDensity = 0;
135             mCurrentIndex = -1;
136             return;
137         }
138 
139         int idOfSmallestDisplay = Display.INVALID_DISPLAY;
140         int minDimensionPx = Integer.MAX_VALUE;
141         DisplayInfo smallestDisplayInfo = null;
142         for (Display display : mDisplayManager.getDisplays(
143                 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
144             DisplayInfo info = new DisplayInfo();
145             if (!display.getDisplayInfo(info)) {
146                 Log.w(LOG_TAG, "Cannot fetch display info for display " + display.getDisplayId());
147                 continue;
148             }
149             if (!mPredicate.test(info)) {
150                 continue;
151             }
152             int minDimension = Math.min(info.logicalWidth, info.logicalHeight);
153             if (minDimension < minDimensionPx) {
154                 minDimensionPx = minDimension;
155                 idOfSmallestDisplay = display.getDisplayId();
156                 smallestDisplayInfo = info;
157             }
158         }
159 
160         if (smallestDisplayInfo == null) {
161             Log.w(LOG_TAG, "No display satisfies the predicate");
162             mEntries = null;
163             mValues = null;
164             mFloatValues = null;
165             mDefaultDensity = 0;
166             mCurrentIndex = -1;
167             return;
168         }
169 
170         final int defaultDensity =
171                 DisplayDensityUtils.getDefaultDensityForDisplay(idOfSmallestDisplay);
172         if (defaultDensity <= 0) {
173             Log.w(LOG_TAG, "Cannot fetch default density for display " + idOfSmallestDisplay);
174             mEntries = null;
175             mValues = null;
176             mFloatValues = null;
177             mDefaultDensity = 0;
178             mCurrentIndex = -1;
179             return;
180         }
181 
182         final Resources res = context.getResources();
183 
184         int currentDensity;
185         if (mPredicate.test(defaultDisplayInfo)) {
186             currentDensity = defaultDisplayInfo.logicalDensityDpi;
187         } else {
188             currentDensity = smallestDisplayInfo.logicalDensityDpi;
189         }
190         int currentDensityIndex = -1;
191 
192         // Compute number of "larger" and "smaller" scales for this display.
193         final int maxDensity =
194                 DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
195         final float maxScaleDimen = context.getResources().getFraction(
196                 R.fraction.display_density_max_scale, 1, 1);
197         final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
198         final float minScale = context.getResources().getFraction(
199                 R.fraction.display_density_min_scale, 1, 1);
200         final float minScaleInterval = context.getResources().getFraction(
201                 R.fraction.display_density_min_scale_interval, 1, 1);
202         final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
203                 0, SUMMARIES_LARGER.length);
204         final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
205                 0, SUMMARIES_SMALLER.length);
206 
207         String[] entries = new String[1 + numSmaller + numLarger];
208         int[] values = new int[entries.length];
209         float[] valuesFloat = new float[entries.length];
210         int curIndex = 0;
211 
212         if (numSmaller > 0) {
213             final float interval = (1 - minScale) / numSmaller;
214             for (int i = numSmaller - 1; i >= 0; i--) {
215                 // Save the float density value before rounding to be used to set the density ratio
216                 // of overridden density to default density in WM.
217                 final float densityFloat = defaultDensity * (1 - (i + 1) * interval);
218                 // Round down to a multiple of 2 by truncating the low bit.
219                 // LINT.IfChange
220                 final int density = ((int) densityFloat) & ~1;
221                 // LINT.ThenChange(/services/core/java/com/android/server/wm/DisplayContent.java:getBaseDensityFromRatio)
222                 if (currentDensity == density) {
223                     currentDensityIndex = curIndex;
224                 }
225                 values[curIndex] = density;
226                 valuesFloat[curIndex] = densityFloat;
227                 entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
228                 curIndex++;
229             }
230         }
231 
232         if (currentDensity == defaultDensity) {
233             currentDensityIndex = curIndex;
234         }
235         values[curIndex] = defaultDensity;
236         valuesFloat[curIndex] = (float) defaultDensity;
237         entries[curIndex] = res.getString(SUMMARY_DEFAULT);
238         curIndex++;
239 
240         if (numLarger > 0) {
241             final float interval = (maxScale - 1) / numLarger;
242             for (int i = 0; i < numLarger; i++) {
243                 // Save the float density value before rounding to be used to set the density ratio
244                 // of overridden density to default density in WM.
245                 final float densityFloat = defaultDensity * (1 + (i + 1) * interval);
246                 // Round down to a multiple of 2 by truncating the low bit.
247                 // LINT.IfChange
248                 final int density = ((int) densityFloat) & ~1;
249                 // LINT.ThenChange(/services/core/java/com/android/server/wm/DisplayContent.java:getBaseDensityFromRatio)
250                 if (currentDensity == density) {
251                     currentDensityIndex = curIndex;
252                 }
253                 values[curIndex] = density;
254                 valuesFloat[curIndex] = densityFloat;
255                 entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
256                 curIndex++;
257             }
258         }
259 
260         final int displayIndex;
261         if (currentDensityIndex >= 0) {
262             displayIndex = currentDensityIndex;
263         } else {
264             // We don't understand the current density. Must have been set by
265             // someone else. Make room for another entry...
266             int newLength = values.length + 1;
267             values = Arrays.copyOf(values, newLength);
268             values[curIndex] = currentDensity;
269 
270             valuesFloat = Arrays.copyOf(valuesFloat, newLength);
271             valuesFloat[curIndex] = (float) currentDensity;
272 
273             entries = Arrays.copyOf(entries, newLength);
274             entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
275 
276             displayIndex = curIndex;
277         }
278 
279         mDefaultDensity = defaultDensity;
280         mCurrentIndex = displayIndex;
281         mEntries = entries;
282         mValues = values;
283         mFloatValues = valuesFloat;
284     }
285 
286     @Nullable
getEntries()287     public String[] getEntries() {
288         return mEntries;
289     }
290 
291     @Nullable
getValues()292     public int[] getValues() {
293         return mValues;
294     }
295 
getCurrentIndex()296     public int getCurrentIndex() {
297         return mCurrentIndex;
298     }
299 
getDefaultDensity()300     public int getDefaultDensity() {
301         return mDefaultDensity;
302     }
303 
304     /**
305      * Returns the default density for the specified display.
306      *
307      * @param displayId the identifier of the display
308      * @return the default density of the specified display, or {@code -1} if the display does not
309      * exist or the density could not be obtained
310      */
getDefaultDensityForDisplay(int displayId)311     private static int getDefaultDensityForDisplay(int displayId) {
312         try {
313             final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
314             return wm.getInitialDisplayDensity(displayId);
315         } catch (RemoteException exc) {
316             return -1;
317         }
318     }
319 
320     /**
321      * Asynchronously applies display density changes to the displays that satisfy the predicate.
322      * <p>
323      * The change will be applied to the user specified by the value of
324      * {@link UserHandle#myUserId()} at the time the method is called.
325      */
clearForcedDisplayDensity()326     public void clearForcedDisplayDensity() {
327         final int userId = UserHandle.myUserId();
328         AsyncTask.execute(() -> {
329             try {
330                 for (Display display : mDisplayManager.getDisplays(
331                         DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
332                     int displayId = display.getDisplayId();
333                     DisplayInfo info = new DisplayInfo();
334                     if (!display.getDisplayInfo(info)) {
335                         Log.w(LOG_TAG, "Unable to clear forced display density setting "
336                                 + "for display " + displayId);
337                         continue;
338                     }
339                     if (!mPredicate.test(info)) {
340                         continue;
341                     }
342 
343                     final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
344                     wm.clearForcedDisplayDensityForUser(displayId, userId);
345                 }
346             } catch (RemoteException exc) {
347                 Log.w(LOG_TAG, "Unable to clear forced display density setting");
348             }
349         });
350     }
351 
352     /**
353      * Asynchronously applies display density changes to the displays that satisfy the predicate.
354      * <p>
355      * The change will be applied to the user specified by the value of
356      * {@link UserHandle#myUserId()} at the time the method is called.
357      *
358      * @param index The index of the density value
359      */
setForcedDisplayDensity(final int index)360     public void setForcedDisplayDensity(final int index) {
361         final int userId = UserHandle.myUserId();
362         AsyncTask.execute(() -> {
363             try {
364                 for (Display display : mDisplayManager.getDisplays(
365                         DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
366                     int displayId = display.getDisplayId();
367                     DisplayInfo info = new DisplayInfo();
368                     if (!display.getDisplayInfo(info)) {
369                         Log.w(LOG_TAG, "Unable to save forced display density setting "
370                                 + "for display " + displayId);
371                         continue;
372                     }
373                     if (!mPredicate.test(info)) {
374                         continue;
375                     }
376 
377                     final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
378                     // Only set the ratio for external displays as Settings uses
379                     // ScreenResolutionFragment to handle density update for internal display.
380                     if (info.type == Display.TYPE_EXTERNAL) {
381                         wm.setForcedDisplayDensityRatio(displayId,
382                                 mFloatValues[index] / mDefaultDensity, userId);
383                     } else {
384                         wm.setForcedDisplayDensityForUser(displayId, mValues[index], userId);
385                     }
386                 }
387             } catch (RemoteException exc) {
388                 Log.w(LOG_TAG, "Unable to save forced display density setting");
389             }
390         });
391     }
392 
393     /**
394      * Returns a list of {@link ConfigurationChangeSetting} object representing the forced display
395      * density settings for the displays that satisfy the predicate.
396      *
397      * @param index the index of the density value in the available density values array.
398      * @return a list of {@link ConfigurationChangeSetting} objects.
399      * @see IWindowManager#setConfigurationChangeSettingsForUser
400      */
401     @NonNull
getForcedDisplayDensitySetting(final int index)402     public List<ConfigurationChangeSetting> getForcedDisplayDensitySetting(final int index) {
403         final ArrayList<ConfigurationChangeSetting> settings = new ArrayList<>();
404         for (final Display display : mDisplayManager.getDisplays(
405                 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
406             final int displayId = display.getDisplayId();
407             final DisplayInfo info = new DisplayInfo();
408             if (!display.getDisplayInfo(info)) {
409                 Log.w(LOG_TAG, "Unable to get display info for display " + displayId);
410                 continue;
411             }
412             if (!mPredicate.test(info)) {
413                 continue;
414             }
415             settings.add(new ConfigurationChangeSetting.DensitySetting(displayId, mValues[index]));
416         }
417         return settings;
418     }
419 }
420