1 /* 2 * Copyright (C) 2013 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 android.app; 18 19 import static android.app.ActivityThread.DEBUG_CONFIGURATION; 20 21 import android.content.pm.ActivityInfo; 22 import android.content.res.AssetManager; 23 import android.content.res.CompatibilityInfo; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.content.res.ResourcesKey; 27 import android.hardware.display.DisplayManagerGlobal; 28 import android.os.IBinder; 29 import android.util.ArrayMap; 30 import android.util.DisplayMetrics; 31 import android.util.Slog; 32 import android.view.Display; 33 import android.view.DisplayAdjustments; 34 35 import java.lang.ref.WeakReference; 36 import java.util.Locale; 37 38 /** @hide */ 39 public class ResourcesManager { 40 static final String TAG = "ResourcesManager"; 41 static final boolean DEBUG_CACHE = false; 42 static final boolean DEBUG_STATS = true; 43 44 private static ResourcesManager sResourcesManager; 45 final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources 46 = new ArrayMap<ResourcesKey, WeakReference<Resources> >(); 47 48 final ArrayMap<DisplayAdjustments, DisplayMetrics> mDefaultDisplayMetrics 49 = new ArrayMap<DisplayAdjustments, DisplayMetrics>(); 50 51 CompatibilityInfo mResCompatibilityInfo; 52 53 Configuration mResConfiguration; 54 final Configuration mTmpConfig = new Configuration(); 55 56 public static ResourcesManager getInstance() { 57 synchronized (ResourcesManager.class) { 58 if (sResourcesManager == null) { 59 sResourcesManager = new ResourcesManager(); 60 } 61 return sResourcesManager; 62 } 63 } 64 65 public Configuration getConfiguration() { 66 return mResConfiguration; 67 } 68 69 public void flushDisplayMetricsLocked() { 70 mDefaultDisplayMetrics.clear(); 71 } 72 73 public DisplayMetrics getDisplayMetricsLocked(int displayId) { 74 return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 75 } 76 77 public DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) { 78 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 79 DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null; 80 if (dm != null) { 81 return dm; 82 } 83 dm = new DisplayMetrics(); 84 85 DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance(); 86 if (displayManager == null) { 87 // may be null early in system startup 88 dm.setToDefaults(); 89 return dm; 90 } 91 92 if (isDefaultDisplay) { 93 mDefaultDisplayMetrics.put(daj, dm); 94 } 95 96 Display d = displayManager.getCompatibleDisplay(displayId, daj); 97 if (d != null) { 98 d.getMetrics(dm); 99 } else { 100 // Display no longer exists 101 // FIXME: This would not be a problem if we kept the Display object around 102 // instead of using the raw display id everywhere. The Display object caches 103 // its information even after the display has been removed. 104 dm.setToDefaults(); 105 } 106 //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" 107 // + metrics.heightPixels + " den=" + metrics.density 108 // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); 109 return dm; 110 } 111 112 final void applyNonDefaultDisplayMetricsToConfigurationLocked( 113 DisplayMetrics dm, Configuration config) { 114 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 115 config.densityDpi = dm.densityDpi; 116 config.screenWidthDp = (int)(dm.widthPixels / dm.density); 117 config.screenHeightDp = (int)(dm.heightPixels / dm.density); 118 int sl = Configuration.resetScreenLayout(config.screenLayout); 119 if (dm.widthPixels > dm.heightPixels) { 120 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 121 config.screenLayout = Configuration.reduceScreenLayout(sl, 122 config.screenWidthDp, config.screenHeightDp); 123 } else { 124 config.orientation = Configuration.ORIENTATION_PORTRAIT; 125 config.screenLayout = Configuration.reduceScreenLayout(sl, 126 config.screenHeightDp, config.screenWidthDp); 127 } 128 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate 129 config.compatScreenWidthDp = config.screenWidthDp; 130 config.compatScreenHeightDp = config.screenHeightDp; 131 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 132 } 133 134 public boolean applyCompatConfiguration(int displayDensity, 135 Configuration compatConfiguration) { 136 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 137 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 138 return true; 139 } 140 return false; 141 } 142 143 /** 144 * Creates the top level Resources for applications with the given compatibility info. 145 * 146 * @param resDir the resource directory. 147 * @param overlayDirs the resource overlay directories. 148 * @param libDirs the shared library resource dirs this app references. 149 * @param compatInfo the compability info. Must not be null. 150 * @param token the application token for determining stack bounds. 151 */ 152 public Resources getTopLevelResources(String resDir, String[] splitResDirs, 153 String[] overlayDirs, String[] libDirs, int displayId, 154 Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { 155 final float scale = compatInfo.applicationScale; 156 ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token); 157 Resources r; 158 synchronized (this) { 159 // Resources is app scale dependent. 160 if (false) { 161 Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); 162 } 163 WeakReference<Resources> wr = mActiveResources.get(key); 164 r = wr != null ? wr.get() : null; 165 //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); 166 if (r != null && r.getAssets().isUpToDate()) { 167 if (false) { 168 Slog.w(TAG, "Returning cached resources " + r + " " + resDir 169 + ": appScale=" + r.getCompatibilityInfo().applicationScale); 170 } 171 return r; 172 } 173 } 174 175 //if (r != null) { 176 // Slog.w(TAG, "Throwing away out-of-date resources!!!! " 177 // + r + " " + resDir); 178 //} 179 180 AssetManager assets = new AssetManager(); 181 // resDir can be null if the 'android' package is creating a new Resources object. 182 // This is fine, since each AssetManager automatically loads the 'android' package 183 // already. 184 if (resDir != null) { 185 if (assets.addAssetPath(resDir) == 0) { 186 return null; 187 } 188 } 189 190 if (splitResDirs != null) { 191 for (String splitResDir : splitResDirs) { 192 if (assets.addAssetPath(splitResDir) == 0) { 193 return null; 194 } 195 } 196 } 197 198 if (overlayDirs != null) { 199 for (String idmapPath : overlayDirs) { 200 assets.addOverlayPath(idmapPath); 201 } 202 } 203 204 if (libDirs != null) { 205 for (String libDir : libDirs) { 206 if (assets.addAssetPath(libDir) == 0) { 207 Slog.w(TAG, "Asset path '" + libDir + 208 "' does not exist or contains no resources."); 209 } 210 } 211 } 212 213 //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); 214 DisplayMetrics dm = getDisplayMetricsLocked(displayId); 215 Configuration config; 216 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 217 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 218 if (!isDefaultDisplay || hasOverrideConfig) { 219 config = new Configuration(getConfiguration()); 220 if (!isDefaultDisplay) { 221 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); 222 } 223 if (hasOverrideConfig) { 224 config.updateFrom(key.mOverrideConfiguration); 225 } 226 } else { 227 config = getConfiguration(); 228 } 229 r = new Resources(assets, dm, config, compatInfo, token); 230 if (false) { 231 Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " 232 + r.getConfiguration() + " appScale=" 233 + r.getCompatibilityInfo().applicationScale); 234 } 235 236 synchronized (this) { 237 WeakReference<Resources> wr = mActiveResources.get(key); 238 Resources existing = wr != null ? wr.get() : null; 239 if (existing != null && existing.getAssets().isUpToDate()) { 240 // Someone else already created the resources while we were 241 // unlocked; go ahead and use theirs. 242 r.getAssets().close(); 243 return existing; 244 } 245 246 // XXX need to remove entries when weak references go away 247 mActiveResources.put(key, new WeakReference<Resources>(r)); 248 return r; 249 } 250 } 251 252 public final boolean applyConfigurationToResourcesLocked(Configuration config, 253 CompatibilityInfo compat) { 254 if (mResConfiguration == null) { 255 mResConfiguration = new Configuration(); 256 } 257 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 258 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 259 + mResConfiguration.seq + ", newSeq=" + config.seq); 260 return false; 261 } 262 int changes = mResConfiguration.updateFrom(config); 263 flushDisplayMetricsLocked(); 264 DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); 265 266 if (compat != null && (mResCompatibilityInfo == null || 267 !mResCompatibilityInfo.equals(compat))) { 268 mResCompatibilityInfo = compat; 269 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 270 | ActivityInfo.CONFIG_SCREEN_SIZE 271 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 272 } 273 274 // set it for java, this also affects newly created Resources 275 if (config.locale != null) { 276 Locale.setDefault(config.locale); 277 } 278 279 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); 280 281 ApplicationPackageManager.configurationChanged(); 282 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 283 284 Configuration tmpConfig = null; 285 286 for (int i=mActiveResources.size()-1; i>=0; i--) { 287 ResourcesKey key = mActiveResources.keyAt(i); 288 Resources r = mActiveResources.valueAt(i).get(); 289 if (r != null) { 290 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 291 + r + " config to: " + config); 292 int displayId = key.mDisplayId; 293 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 294 DisplayMetrics dm = defaultDisplayMetrics; 295 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 296 if (!isDefaultDisplay || hasOverrideConfiguration) { 297 if (tmpConfig == null) { 298 tmpConfig = new Configuration(); 299 } 300 tmpConfig.setTo(config); 301 if (!isDefaultDisplay) { 302 dm = getDisplayMetricsLocked(displayId); 303 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); 304 } 305 if (hasOverrideConfiguration) { 306 tmpConfig.updateFrom(key.mOverrideConfiguration); 307 } 308 r.updateConfiguration(tmpConfig, dm, compat); 309 } else { 310 r.updateConfiguration(config, dm, compat); 311 } 312 //Slog.i(TAG, "Updated app resources " + v.getKey() 313 // + " " + r + ": " + r.getConfiguration()); 314 } else { 315 //Slog.i(TAG, "Removing old resources " + v.getKey()); 316 mActiveResources.removeAt(i); 317 } 318 } 319 320 return changes != 0; 321 } 322 323 } 324