ResourcesManager.java revision fb302ccd8e0610a09691ea5503ff8111dc7a2e41
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 17package android.app; 18 19import static android.app.ActivityThread.DEBUG_CONFIGURATION; 20 21import android.content.pm.ActivityInfo; 22import android.content.res.AssetManager; 23import android.content.res.CompatibilityInfo; 24import android.content.res.Configuration; 25import android.content.res.Resources; 26import android.content.res.ResourcesImpl; 27import android.content.res.ResourcesKey; 28import android.hardware.display.DisplayManagerGlobal; 29import android.util.ArrayMap; 30import android.util.DisplayMetrics; 31import android.util.LocaleList; 32import android.util.Log; 33import android.util.Pair; 34import android.util.Slog; 35import android.view.Display; 36import android.view.DisplayAdjustments; 37 38import java.lang.ref.WeakReference; 39import java.util.Arrays; 40import java.util.HashSet; 41import java.util.List; 42 43/** @hide */ 44public class ResourcesManager { 45 static final String TAG = "ResourcesManager"; 46 private static final boolean DEBUG = false; 47 48 private static ResourcesManager sResourcesManager; 49 private final ArrayMap<ResourcesKey, WeakReference<Resources>> mActiveResources = 50 new ArrayMap<>(); 51 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays = 52 new ArrayMap<>(); 53 54 private String[] mSystemLocales = {}; 55 private final HashSet<String> mNonSystemLocales = new HashSet<String>(); 56 private boolean mHasNonSystemLocales = false; 57 58 CompatibilityInfo mResCompatibilityInfo; 59 60 Configuration mResConfiguration; 61 62 public static ResourcesManager getInstance() { 63 synchronized (ResourcesManager.class) { 64 if (sResourcesManager == null) { 65 sResourcesManager = new ResourcesManager(); 66 } 67 return sResourcesManager; 68 } 69 } 70 71 public Configuration getConfiguration() { 72 return mResConfiguration; 73 } 74 75 DisplayMetrics getDisplayMetricsLocked() { 76 return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); 77 } 78 79 DisplayMetrics getDisplayMetricsLocked(int displayId) { 80 DisplayMetrics dm = new DisplayMetrics(); 81 final Display display = 82 getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 83 if (display != null) { 84 display.getMetrics(dm); 85 } else { 86 dm.setToDefaults(); 87 } 88 return dm; 89 } 90 91 final void applyNonDefaultDisplayMetricsToConfigurationLocked( 92 DisplayMetrics dm, Configuration config) { 93 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 94 config.densityDpi = dm.densityDpi; 95 config.screenWidthDp = (int)(dm.widthPixels / dm.density); 96 config.screenHeightDp = (int)(dm.heightPixels / dm.density); 97 int sl = Configuration.resetScreenLayout(config.screenLayout); 98 if (dm.widthPixels > dm.heightPixels) { 99 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 100 config.screenLayout = Configuration.reduceScreenLayout(sl, 101 config.screenWidthDp, config.screenHeightDp); 102 } else { 103 config.orientation = Configuration.ORIENTATION_PORTRAIT; 104 config.screenLayout = Configuration.reduceScreenLayout(sl, 105 config.screenHeightDp, config.screenWidthDp); 106 } 107 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate 108 config.compatScreenWidthDp = config.screenWidthDp; 109 config.compatScreenHeightDp = config.screenHeightDp; 110 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 111 } 112 113 public boolean applyCompatConfiguration(int displayDensity, 114 Configuration compatConfiguration) { 115 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 116 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 117 return true; 118 } 119 return false; 120 } 121 122 /** 123 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 124 * available. 125 * 126 * @param displayId display Id. 127 * @param displayAdjustments display adjustments. 128 */ 129 public Display getAdjustedDisplay(final int displayId, DisplayAdjustments displayAdjustments) { 130 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) 131 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); 132 final Pair<Integer, DisplayAdjustments> key = 133 Pair.create(displayId, displayAdjustmentsCopy); 134 synchronized (this) { 135 WeakReference<Display> wd = mDisplays.get(key); 136 if (wd != null) { 137 final Display display = wd.get(); 138 if (display != null) { 139 return display; 140 } 141 } 142 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 143 if (dm == null) { 144 // may be null early in system startup 145 return null; 146 } 147 final Display display = dm.getCompatibleDisplay(displayId, key.second); 148 if (display != null) { 149 mDisplays.put(key, new WeakReference<>(display)); 150 } 151 return display; 152 } 153 } 154 155 /** 156 * Creates the top level Resources for applications with the given compatibility info. 157 * 158 * @param resDir the resource directory. 159 * @param splitResDirs split resource directories. 160 * @param overlayDirs the resource overlay directories. 161 * @param libDirs the shared library resource dirs this app references. 162 * @param displayId display Id. 163 * @param overrideConfiguration override configurations. 164 * @param compatInfo the compatibility info. Must not be null. 165 * @param classLoader the class loader for the resource package 166 */ 167 Resources getTopLevelResources(String resDir, String[] splitResDirs, 168 String[] overlayDirs, String[] libDirs, int displayId, 169 Configuration overrideConfiguration, CompatibilityInfo compatInfo, 170 ClassLoader classLoader) { 171 final float scale = compatInfo.applicationScale; 172 Configuration overrideConfigCopy = (overrideConfiguration != null) 173 ? new Configuration(overrideConfiguration) : null; 174 ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale); 175 Resources r; 176 final boolean findSystemLocales; 177 final boolean hasNonSystemLocales; 178 synchronized (this) { 179 // Resources is app scale dependent. 180 if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); 181 182 WeakReference<Resources> wr = mActiveResources.get(key); 183 r = wr != null ? wr.get() : null; 184 //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); 185 if (r != null && r.getAssets().isUpToDate()) { 186 if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir 187 + ": appScale=" + r.getCompatibilityInfo().applicationScale 188 + " key=" + key + " overrideConfig=" + overrideConfiguration); 189 return r; 190 } 191 findSystemLocales = (mSystemLocales.length == 0); 192 hasNonSystemLocales = mHasNonSystemLocales; 193 } 194 195 //if (r != null) { 196 // Log.w(TAG, "Throwing away out-of-date resources!!!! " 197 // + r + " " + resDir); 198 //} 199 200 AssetManager assets = new AssetManager(); 201 // resDir can be null if the 'android' package is creating a new Resources object. 202 // This is fine, since each AssetManager automatically loads the 'android' package 203 // already. 204 if (resDir != null) { 205 if (assets.addAssetPath(resDir) == 0) { 206 return null; 207 } 208 } 209 210 if (splitResDirs != null) { 211 for (String splitResDir : splitResDirs) { 212 if (assets.addAssetPath(splitResDir) == 0) { 213 return null; 214 } 215 } 216 } 217 218 if (overlayDirs != null) { 219 for (String idmapPath : overlayDirs) { 220 assets.addOverlayPath(idmapPath); 221 } 222 } 223 224 if (libDirs != null) { 225 for (String libDir : libDirs) { 226 if (libDir.endsWith(".apk")) { 227 // Avoid opening files we know do not have resources, 228 // like code-only .jar files. 229 if (assets.addAssetPath(libDir) == 0) { 230 Log.w(TAG, "Asset path '" + libDir + 231 "' does not exist or contains no resources."); 232 } 233 } 234 } 235 } 236 237 //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); 238 DisplayMetrics dm = getDisplayMetricsLocked(displayId); 239 Configuration config; 240 final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 241 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 242 if (!isDefaultDisplay || hasOverrideConfig) { 243 config = new Configuration(getConfiguration()); 244 if (!isDefaultDisplay) { 245 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); 246 } 247 if (hasOverrideConfig) { 248 config.updateFrom(key.mOverrideConfiguration); 249 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 250 } 251 } else { 252 config = getConfiguration(); 253 } 254 r = new Resources(classLoader); 255 r.setImpl(new ResourcesImpl(assets, dm, config, compatInfo)); 256 if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " 257 + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); 258 259 final String[] systemLocales = ( 260 findSystemLocales ? 261 AssetManager.getSystem().getLocales() : 262 null); 263 final String[] nonSystemLocales = assets.getNonSystemLocales(); 264 // Avoid checking for non-pseudo-locales if we already know there were some from a previous 265 // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, 266 // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be 267 // able to affect mHasNonSystemLocales. 268 final boolean isPseudoLocalesOnly = hasNonSystemLocales || 269 LocaleList.isPseudoLocalesOnly(nonSystemLocales); 270 271 synchronized (this) { 272 WeakReference<Resources> wr = mActiveResources.get(key); 273 Resources existing = wr != null ? wr.get() : null; 274 if (existing != null && existing.getAssets().isUpToDate()) { 275 // Someone else already created the resources while we were 276 // unlocked; go ahead and use theirs. 277 r.getAssets().close(); 278 return existing; 279 } 280 281 // XXX need to remove entries when weak references go away 282 mActiveResources.put(key, new WeakReference<>(r)); 283 if (mSystemLocales.length == 0) { 284 mSystemLocales = systemLocales; 285 } 286 mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); 287 mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; 288 if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size()); 289 return r; 290 } 291 } 292 293 /* package */ void setDefaultLocalesLocked(LocaleList locales) { 294 final int bestLocale; 295 if (mHasNonSystemLocales) { 296 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); 297 } else { 298 // We fallback to system locales if there was no locale specifically supported by the 299 // assets. This is to properly support apps that only rely on the shared system assets 300 // and don't need assets of their own. 301 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales); 302 } 303 // set it for Java, this also affects newly created Resources 304 LocaleList.setDefault(locales, bestLocale); 305 } 306 307 final boolean applyConfigurationToResourcesLocked(Configuration config, 308 CompatibilityInfo compat) { 309 if (mResConfiguration == null) { 310 mResConfiguration = new Configuration(); 311 } 312 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 313 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 314 + mResConfiguration.seq + ", newSeq=" + config.seq); 315 return false; 316 } 317 int changes = mResConfiguration.updateFrom(config); 318 // Things might have changed in display manager, so clear the cached displays. 319 mDisplays.clear(); 320 DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(); 321 322 if (compat != null && (mResCompatibilityInfo == null || 323 !mResCompatibilityInfo.equals(compat))) { 324 mResCompatibilityInfo = compat; 325 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 326 | ActivityInfo.CONFIG_SCREEN_SIZE 327 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 328 } 329 330 Configuration localeAdjustedConfig = config; 331 final LocaleList configLocales = config.getLocales(); 332 if (!configLocales.isEmpty()) { 333 setDefaultLocalesLocked(configLocales); 334 final LocaleList adjustedLocales = LocaleList.getAdjustedDefault(); 335 if (adjustedLocales != configLocales) { // has the same result as .equals() in this case 336 // The first locale in the list was not chosen. So we create a modified 337 // configuration with the adjusted locales (which moves the chosen locale to the 338 // front). 339 localeAdjustedConfig = new Configuration(); 340 localeAdjustedConfig.setTo(config); 341 localeAdjustedConfig.setLocales(adjustedLocales); 342 // Also adjust the locale list in mResConfiguration, so that the Resources created 343 // later would have the same locale list. 344 if (!mResConfiguration.getLocales().equals(adjustedLocales)) { 345 mResConfiguration.setLocales(adjustedLocales); 346 changes |= ActivityInfo.CONFIG_LOCALE; 347 } 348 } 349 } 350 351 Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat); 352 353 ApplicationPackageManager.configurationChanged(); 354 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 355 356 Configuration tmpConfig = null; 357 358 for (int i = mActiveResources.size() - 1; i >= 0; i--) { 359 ResourcesKey key = mActiveResources.keyAt(i); 360 Resources r = mActiveResources.valueAt(i).get(); 361 if (r != null) { 362 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 363 + r + " config to: " + localeAdjustedConfig); 364 int displayId = key.mDisplayId; 365 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 366 DisplayMetrics dm = defaultDisplayMetrics; 367 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 368 if (!isDefaultDisplay || hasOverrideConfiguration) { 369 if (tmpConfig == null) { 370 tmpConfig = new Configuration(); 371 } 372 tmpConfig.setTo(localeAdjustedConfig); 373 if (!isDefaultDisplay) { 374 dm = getDisplayMetricsLocked(displayId); 375 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); 376 } 377 if (hasOverrideConfiguration) { 378 tmpConfig.updateFrom(key.mOverrideConfiguration); 379 } 380 r.updateConfiguration(tmpConfig, dm, compat); 381 } else { 382 r.updateConfiguration(localeAdjustedConfig, dm, compat); 383 } 384 //Slog.i(TAG, "Updated app resources " + v.getKey() 385 // + " " + r + ": " + r.getConfiguration()); 386 } else { 387 //Slog.i(TAG, "Removing old resources " + v.getKey()); 388 mActiveResources.removeAt(i); 389 } 390 } 391 392 return changes != 0; 393 } 394 395} 396