• 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 
33 import com.android.settingslib.R;
34 
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.function.Predicate;
39 
40 /**
41  * Utility methods for working with display density.
42  */
43 public class DisplayDensityUtils {
44     private static final String LOG_TAG = "DisplayDensityUtils";
45 
46     /** Summary used for "default" scale. */
47     public static final int SUMMARY_DEFAULT = R.string.screen_zoom_summary_default;
48 
49     /** Summary used for "custom" scale. */
50     private static final int SUMMARY_CUSTOM = R.string.screen_zoom_summary_custom;
51 
52     /**
53      * Summaries for scales smaller than "default" in order of smallest to
54      * largest.
55      */
56     private static final int[] SUMMARIES_SMALLER = new int[] {
57             R.string.screen_zoom_summary_small
58     };
59 
60     /**
61      * Summaries for scales larger than "default" in order of smallest to
62      * largest.
63      */
64     private static final int[] SUMMARIES_LARGER = new int[] {
65             R.string.screen_zoom_summary_large,
66             R.string.screen_zoom_summary_very_large,
67             R.string.screen_zoom_summary_extremely_large,
68     };
69 
70     /**
71      * Minimum allowed screen dimension, corresponds to resource qualifiers
72      * "small" or "sw320dp". This value must be at least the minimum screen
73      * size required by the CDD so that we meet developer expectations.
74      */
75     private static final int MIN_DIMENSION_DP = 320;
76 
77     private static final Predicate<DisplayInfo> INTERNAL_ONLY =
78             (info) -> info.type == Display.TYPE_INTERNAL;
79 
80     private final Predicate<DisplayInfo> mPredicate;
81 
82     private final DisplayManager mDisplayManager;
83 
84     /**
85      * The text description of the density values of the default display.
86      */
87     private String[] mDefaultDisplayDensityEntries;
88 
89     /**
90      * The density values of the default display.
91      */
92     private int[] mDefaultDisplayDensityValues;
93 
94     /**
95      * The density values, indexed by display unique ID.
96      */
97     private final Map<String, int[]> mValuesPerDisplay = new HashMap();
98 
99     private int mDefaultDensityForDefaultDisplay;
100     private int mCurrentIndex = -1;
101 
DisplayDensityUtils(Context context)102     public DisplayDensityUtils(Context context) {
103         this(context, INTERNAL_ONLY);
104     }
105 
106     /**
107      * Creates an instance that stores the density values for the displays that satisfy
108      * the predicate.
109      * @param context The context
110      * @param predicate Determines what displays the density should be set for. The default display
111      *                  must satisfy this predicate.
112      */
DisplayDensityUtils(Context context, Predicate predicate)113     public DisplayDensityUtils(Context context, Predicate predicate) {
114         mPredicate = predicate;
115         mDisplayManager = context.getSystemService(DisplayManager.class);
116 
117         for (Display display : mDisplayManager.getDisplays(
118                 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
119             DisplayInfo info = new DisplayInfo();
120             if (!display.getDisplayInfo(info)) {
121                 Log.w(LOG_TAG, "Cannot fetch display info for display " + display.getDisplayId());
122                 continue;
123             }
124             if (!mPredicate.test(info)) {
125                 if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
126                     throw new IllegalArgumentException("Predicate must not filter out the default "
127                             + "display.");
128                 }
129                 continue;
130             }
131 
132             final int defaultDensity = DisplayDensityUtils.getDefaultDensityForDisplay(
133                     display.getDisplayId());
134             if (defaultDensity <= 0) {
135                 Log.w(LOG_TAG, "Cannot fetch default density for display "
136                         + display.getDisplayId());
137                 continue;
138             }
139 
140             final Resources res = context.getResources();
141 
142             final int currentDensity = info.logicalDensityDpi;
143             int currentDensityIndex = -1;
144 
145             // Compute number of "larger" and "smaller" scales for this display.
146             final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
147             final int maxDensity =
148                     DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
149             final float maxScaleDimen = context.getResources().getFraction(
150                     R.fraction.display_density_max_scale, 1, 1);
151             final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
152             final float minScale = context.getResources().getFraction(
153                     R.fraction.display_density_min_scale, 1, 1);
154             final float minScaleInterval = context.getResources().getFraction(
155                     R.fraction.display_density_min_scale_interval, 1, 1);
156             final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
157                     0, SUMMARIES_LARGER.length);
158             final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
159                     0, SUMMARIES_SMALLER.length);
160 
161             String[] entries = new String[1 + numSmaller + numLarger];
162             int[] values = new int[entries.length];
163             int curIndex = 0;
164 
165             if (numSmaller > 0) {
166                 final float interval = (1 - minScale) / numSmaller;
167                 for (int i = numSmaller - 1; i >= 0; i--) {
168                     // Round down to a multiple of 2 by truncating the low bit.
169                     final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
170                     if (currentDensity == density) {
171                         currentDensityIndex = curIndex;
172                     }
173                     entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
174                     values[curIndex] = density;
175                     curIndex++;
176                 }
177             }
178 
179             if (currentDensity == defaultDensity) {
180                 currentDensityIndex = curIndex;
181             }
182             values[curIndex] = defaultDensity;
183             entries[curIndex] = res.getString(SUMMARY_DEFAULT);
184             curIndex++;
185 
186             if (numLarger > 0) {
187                 final float interval = (maxScale - 1) / numLarger;
188                 for (int i = 0; i < numLarger; i++) {
189                     // Round down to a multiple of 2 by truncating the low bit.
190                     final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
191                     if (currentDensity == density) {
192                         currentDensityIndex = curIndex;
193                     }
194                     values[curIndex] = density;
195                     entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
196                     curIndex++;
197                 }
198             }
199 
200             final int displayIndex;
201             if (currentDensityIndex >= 0) {
202                 displayIndex = currentDensityIndex;
203             } else {
204                 // We don't understand the current density. Must have been set by
205                 // someone else. Make room for another entry...
206                 int newLength = values.length + 1;
207                 values = Arrays.copyOf(values, newLength);
208                 values[curIndex] = currentDensity;
209 
210                 entries = Arrays.copyOf(entries, newLength);
211                 entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
212 
213                 displayIndex = curIndex;
214             }
215 
216             if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
217                 mDefaultDensityForDefaultDisplay = defaultDensity;
218                 mCurrentIndex = displayIndex;
219                 mDefaultDisplayDensityEntries = entries;
220                 mDefaultDisplayDensityValues = values;
221             }
222             mValuesPerDisplay.put(info.uniqueId, values);
223         }
224     }
225 
getDefaultDisplayDensityEntries()226     public String[] getDefaultDisplayDensityEntries() {
227         return mDefaultDisplayDensityEntries;
228     }
229 
getDefaultDisplayDensityValues()230     public int[] getDefaultDisplayDensityValues() {
231         return mDefaultDisplayDensityValues;
232     }
233 
getCurrentIndexForDefaultDisplay()234     public int getCurrentIndexForDefaultDisplay() {
235         return mCurrentIndex;
236     }
237 
getDefaultDensityForDefaultDisplay()238     public int getDefaultDensityForDefaultDisplay() {
239         return mDefaultDensityForDefaultDisplay;
240     }
241 
242     /**
243      * Returns the default density for the specified display.
244      *
245      * @param displayId the identifier of the display
246      * @return the default density of the specified display, or {@code -1} if
247      *         the display does not exist or the density could not be obtained
248      */
getDefaultDensityForDisplay(int displayId)249     private static int getDefaultDensityForDisplay(int displayId) {
250        try {
251            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
252            return wm.getInitialDisplayDensity(displayId);
253        } catch (RemoteException exc) {
254            return -1;
255        }
256     }
257 
258     /**
259      * Asynchronously applies display density changes to the displays that satisfy the predicate.
260      * <p>
261      * The change will be applied to the user specified by the value of
262      * {@link UserHandle#myUserId()} at the time the method is called.
263      */
clearForcedDisplayDensity()264     public void clearForcedDisplayDensity() {
265         final int userId = UserHandle.myUserId();
266         AsyncTask.execute(() -> {
267             try {
268                 for (Display display : mDisplayManager.getDisplays(
269                         DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
270                     int displayId = display.getDisplayId();
271                     DisplayInfo info = new DisplayInfo();
272                     if (!display.getDisplayInfo(info)) {
273                         Log.w(LOG_TAG, "Unable to clear forced display density setting "
274                                 + "for display " + displayId);
275                         continue;
276                     }
277                     if (!mPredicate.test(info)) {
278                         continue;
279                     }
280 
281                     final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
282                     wm.clearForcedDisplayDensityForUser(displayId, userId);
283                 }
284             } catch (RemoteException exc) {
285                 Log.w(LOG_TAG, "Unable to clear forced display density setting");
286             }
287         });
288     }
289 
290     /**
291      * Asynchronously applies display density changes to the displays that satisfy the predicate.
292      * <p>
293      * The change will be applied to the user specified by the value of
294      * {@link UserHandle#myUserId()} at the time the method is called.
295      *
296      * @param index The index of the density value
297      */
setForcedDisplayDensity(final int index)298     public void setForcedDisplayDensity(final int index) {
299         final int userId = UserHandle.myUserId();
300         AsyncTask.execute(() -> {
301             try {
302                 for (Display display : mDisplayManager.getDisplays(
303                         DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
304                     int displayId = display.getDisplayId();
305                     DisplayInfo info = new DisplayInfo();
306                     if (!display.getDisplayInfo(info)) {
307                         Log.w(LOG_TAG, "Unable to save forced display density setting "
308                                 + "for display " + displayId);
309                         continue;
310                     }
311                     if (!mPredicate.test(info)) {
312                         continue;
313                     }
314                     if (!mValuesPerDisplay.containsKey(info.uniqueId)) {
315                         Log.w(LOG_TAG, "Unable to save forced display density setting "
316                                 + "for display " + info.uniqueId);
317                         continue;
318                     }
319 
320                     final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
321                     wm.setForcedDisplayDensityForUser(displayId,
322                             mValuesPerDisplay.get(info.uniqueId)[index], userId);
323                 }
324             } catch (RemoteException exc) {
325                 Log.w(LOG_TAG, "Unable to save forced display density setting");
326             }
327         });
328     }
329 }
330