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 com.android.settingslib.R; 20 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.os.AsyncTask; 24 import android.os.RemoteException; 25 import android.util.DisplayMetrics; 26 import android.util.Log; 27 import android.util.MathUtils; 28 import android.view.Display; 29 import android.view.IWindowManager; 30 import android.view.WindowManagerGlobal; 31 32 import java.util.Arrays; 33 34 /** 35 * Utility methods for working with display density. 36 */ 37 public class DisplayDensityUtils { 38 private static final String LOG_TAG = "DisplayDensityUtils"; 39 40 /** Minimum increment between density scales. */ 41 private static final float MIN_SCALE_INTERVAL = 0.09f; 42 43 /** Minimum density scale. This is available on all devices. */ 44 private static final float MIN_SCALE = 0.85f; 45 46 /** Maximum density scale. The actual scale used depends on the device. */ 47 private static final float MAX_SCALE = 1.50f; 48 49 /** Summary used for "default" scale. */ 50 public static final int SUMMARY_DEFAULT = R.string.screen_zoom_summary_default; 51 52 /** Summary used for "custom" scale. */ 53 private static final int SUMMARY_CUSTOM = R.string.screen_zoom_summary_custom; 54 55 /** 56 * Summaries for scales smaller than "default" in order of smallest to 57 * largest. 58 */ 59 private static final int[] SUMMARIES_SMALLER = new int[] { 60 R.string.screen_zoom_summary_small 61 }; 62 63 /** 64 * Summaries for scales larger than "default" in order of smallest to 65 * largest. 66 */ 67 private static final int[] SUMMARIES_LARGER = new int[] { 68 R.string.screen_zoom_summary_large, 69 R.string.screen_zoom_summary_very_large, 70 R.string.screen_zoom_summary_extremely_large, 71 }; 72 73 /** 74 * Minimum allowed screen dimension, corresponds to resource qualifiers 75 * "small" or "sw320dp". This value must be at least the minimum screen 76 * size required by the CDD so that we meet developer expectations. 77 */ 78 private static final int MIN_DIMENSION_DP = 320; 79 80 private final String[] mEntries; 81 private final int[] mValues; 82 83 private final int mDefaultDensity; 84 private final int mCurrentIndex; 85 86 public DisplayDensityUtils(Context context) { 87 final int defaultDensity = DisplayDensityUtils.getDefaultDisplayDensity( 88 Display.DEFAULT_DISPLAY); 89 if (defaultDensity <= 0) { 90 mEntries = null; 91 mValues = null; 92 mDefaultDensity = 0; 93 mCurrentIndex = -1; 94 return; 95 } 96 97 final Resources res = context.getResources(); 98 final DisplayMetrics metrics = res.getDisplayMetrics(); 99 final int currentDensity = metrics.densityDpi; 100 int currentDensityIndex = -1; 101 102 // Compute number of "larger" and "smaller" scales for this display. 103 final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels); 104 final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP; 105 final float maxScale = Math.min(MAX_SCALE, maxDensity / (float) defaultDensity); 106 final float minScale = MIN_SCALE; 107 final int numLarger = (int) MathUtils.constrain((maxScale - 1) / MIN_SCALE_INTERVAL, 108 0, SUMMARIES_LARGER.length); 109 final int numSmaller = (int) MathUtils.constrain((1 - minScale) / MIN_SCALE_INTERVAL, 110 0, SUMMARIES_SMALLER.length); 111 112 String[] entries = new String[1 + numSmaller + numLarger]; 113 int[] values = new int[entries.length]; 114 int curIndex = 0; 115 116 if (numSmaller > 0) { 117 final float interval = (1 - minScale) / numSmaller; 118 for (int i = numSmaller - 1; i >= 0; i--) { 119 // Round down to a multiple of 2 by truncating the low bit. 120 final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1; 121 if (currentDensity == density) { 122 currentDensityIndex = curIndex; 123 } 124 entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]); 125 values[curIndex] = density; 126 curIndex++; 127 } 128 } 129 130 if (currentDensity == defaultDensity) { 131 currentDensityIndex = curIndex; 132 } 133 values[curIndex] = defaultDensity; 134 entries[curIndex] = res.getString(SUMMARY_DEFAULT); 135 curIndex++; 136 137 if (numLarger > 0) { 138 final float interval = (maxScale - 1) / numLarger; 139 for (int i = 0; i < numLarger; i++) { 140 // Round down to a multiple of 2 by truncating the low bit. 141 final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1; 142 if (currentDensity == density) { 143 currentDensityIndex = curIndex; 144 } 145 values[curIndex] = density; 146 entries[curIndex] = res.getString(SUMMARIES_LARGER[i]); 147 curIndex++; 148 } 149 } 150 151 final int displayIndex; 152 if (currentDensityIndex >= 0) { 153 displayIndex = currentDensityIndex; 154 } else { 155 // We don't understand the current density. Must have been set by 156 // someone else. Make room for another entry... 157 int newLength = values.length + 1; 158 values = Arrays.copyOf(values, newLength); 159 values[curIndex] = currentDensity; 160 161 entries = Arrays.copyOf(entries, newLength); 162 entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity); 163 164 displayIndex = curIndex; 165 } 166 167 mDefaultDensity = defaultDensity; 168 mCurrentIndex = displayIndex; 169 mEntries = entries; 170 mValues = values; 171 } 172 173 public String[] getEntries() { 174 return mEntries; 175 } 176 177 public int[] getValues() { 178 return mValues; 179 } 180 181 public int getCurrentIndex() { 182 return mCurrentIndex; 183 } 184 185 public int getDefaultDensity() { 186 return mDefaultDensity; 187 } 188 189 /** 190 * Returns the default density for the specified display. 191 * 192 * @param displayId the identifier of the display 193 * @return the default density of the specified display, or {@code -1} if 194 * the display does not exist or the density could not be obtained 195 */ 196 private static int getDefaultDisplayDensity(int displayId) { 197 try { 198 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 199 return wm.getInitialDisplayDensity(displayId); 200 } catch (RemoteException exc) { 201 return -1; 202 } 203 } 204 205 /** 206 * Asynchronously applies display density changes to the specified display. 207 * 208 * @param displayId the identifier of the display to modify 209 */ 210 public static void clearForcedDisplayDensity(final int displayId) { 211 AsyncTask.execute(new Runnable() { 212 @Override 213 public void run() { 214 try { 215 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 216 wm.clearForcedDisplayDensity(displayId); 217 } catch (RemoteException exc) { 218 Log.w(LOG_TAG, "Unable to clear forced display density setting"); 219 } 220 } 221 }); 222 } 223 224 /** 225 * Asynchronously applies display density changes to the specified display. 226 * 227 * @param displayId the identifier of the display to modify 228 * @param density the density to force for the specified display 229 */ 230 public static void setForcedDisplayDensity(final int displayId, final int density) { 231 AsyncTask.execute(new Runnable() { 232 @Override 233 public void run() { 234 try { 235 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 236 wm.setForcedDisplayDensity(displayId, density); 237 } catch (RemoteException exc) { 238 Log.w(LOG_TAG, "Unable to save forced display density setting"); 239 } 240 } 241 }); 242 } 243 } 244