ResourcesManager.java revision 082614c6a57a115ee0c5975e3579bf34a178c0f8
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.annotation.NonNull; 22import android.annotation.Nullable; 23import android.content.pm.ActivityInfo; 24import android.content.res.AssetManager; 25import android.content.res.CompatibilityInfo; 26import android.content.res.Configuration; 27import android.content.res.Resources; 28import android.content.res.ResourcesImpl; 29import android.content.res.ResourcesKey; 30import android.hardware.display.DisplayManagerGlobal; 31import android.os.IBinder; 32import android.util.ArrayMap; 33import android.util.DisplayMetrics; 34import android.util.LocaleList; 35import android.util.Log; 36import android.util.Pair; 37import android.util.Slog; 38import android.view.Display; 39import android.view.DisplayAdjustments; 40import com.android.internal.annotations.VisibleForTesting; 41import com.android.internal.util.ArrayUtils; 42 43import java.lang.ref.WeakReference; 44import java.util.ArrayList; 45import java.util.Arrays; 46import java.util.HashSet; 47import java.util.Objects; 48import java.util.WeakHashMap; 49import java.util.function.Predicate; 50 51/** @hide */ 52public class ResourcesManager { 53 static final String TAG = "ResourcesManager"; 54 private static final boolean DEBUG = false; 55 56 private static ResourcesManager sResourcesManager; 57 58 /** 59 * Predicate that returns true if a WeakReference is gc'ed. 60 */ 61 private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = 62 new Predicate<WeakReference<Resources>>() { 63 @Override 64 public boolean test(WeakReference<Resources> weakRef) { 65 return weakRef == null || weakRef.get() == null; 66 } 67 }; 68 69 private String[] mSystemLocales = {}; 70 private final HashSet<String> mNonSystemLocales = new HashSet<>(); 71 private boolean mHasNonSystemLocales = false; 72 73 /** 74 * The global compatibility settings. 75 */ 76 private CompatibilityInfo mResCompatibilityInfo; 77 78 /** 79 * The global configuration upon which all Resources are based. Multi-window Resources 80 * apply their overrides to this configuration. 81 */ 82 private final Configuration mResConfiguration = new Configuration(); 83 84 /** 85 * A mapping of ResourceImpls and their configurations. These are heavy weight objects 86 * which should be reused as much as possible. 87 */ 88 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = 89 new ArrayMap<>(); 90 91 /** 92 * A list of Resource references that can be reused. 93 */ 94 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); 95 96 /** 97 * Each Activity may have only one Resources object. 98 */ 99 private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences = 100 new WeakHashMap<>(); 101 102 /** 103 * A cache of DisplayId to DisplayAdjustments. 104 */ 105 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays = 106 new ArrayMap<>(); 107 108 public static ResourcesManager getInstance() { 109 synchronized (ResourcesManager.class) { 110 if (sResourcesManager == null) { 111 sResourcesManager = new ResourcesManager(); 112 } 113 return sResourcesManager; 114 } 115 } 116 117 public Configuration getConfiguration() { 118 return mResConfiguration; 119 } 120 121 DisplayMetrics getDisplayMetricsLocked() { 122 return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); 123 } 124 125 /** 126 * Protected so that tests can override and returns something a fixed value. 127 */ 128 @VisibleForTesting 129 protected DisplayMetrics getDisplayMetricsLocked(int displayId) { 130 DisplayMetrics dm = new DisplayMetrics(); 131 final Display display = 132 getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 133 if (display != null) { 134 display.getMetrics(dm); 135 } else { 136 dm.setToDefaults(); 137 } 138 return dm; 139 } 140 141 private static void applyNonDefaultDisplayMetricsToConfiguration( 142 @NonNull DisplayMetrics dm, @NonNull Configuration config) { 143 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 144 config.densityDpi = dm.densityDpi; 145 config.screenWidthDp = (int) (dm.widthPixels / dm.density); 146 config.screenHeightDp = (int) (dm.heightPixels / dm.density); 147 int sl = Configuration.resetScreenLayout(config.screenLayout); 148 if (dm.widthPixels > dm.heightPixels) { 149 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 150 config.screenLayout = Configuration.reduceScreenLayout(sl, 151 config.screenWidthDp, config.screenHeightDp); 152 } else { 153 config.orientation = Configuration.ORIENTATION_PORTRAIT; 154 config.screenLayout = Configuration.reduceScreenLayout(sl, 155 config.screenHeightDp, config.screenWidthDp); 156 } 157 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate 158 config.compatScreenWidthDp = config.screenWidthDp; 159 config.compatScreenHeightDp = config.screenHeightDp; 160 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 161 } 162 163 public boolean applyCompatConfigurationLocked(int displayDensity, 164 @NonNull Configuration compatConfiguration) { 165 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 166 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 167 return true; 168 } 169 return false; 170 } 171 172 /** 173 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 174 * available. 175 * 176 * @param displayId display Id. 177 * @param displayAdjustments display adjustments. 178 */ 179 public Display getAdjustedDisplay(final int displayId, 180 @Nullable DisplayAdjustments displayAdjustments) { 181 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) 182 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); 183 final Pair<Integer, DisplayAdjustments> key = 184 Pair.create(displayId, displayAdjustmentsCopy); 185 synchronized (this) { 186 WeakReference<Display> wd = mDisplays.get(key); 187 if (wd != null) { 188 final Display display = wd.get(); 189 if (display != null) { 190 return display; 191 } 192 } 193 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 194 if (dm == null) { 195 // may be null early in system startup 196 return null; 197 } 198 final Display display = dm.getCompatibleDisplay(displayId, key.second); 199 if (display != null) { 200 mDisplays.put(key, new WeakReference<>(display)); 201 } 202 return display; 203 } 204 } 205 206 /** 207 * Creates an AssetManager from the paths within the ResourcesKey. 208 * 209 * This can be overridden in tests so as to avoid creating a real AssetManager with 210 * real APK paths. 211 * @param key The key containing the resource paths to add to the AssetManager. 212 * @return a new AssetManager. 213 */ 214 @VisibleForTesting 215 protected AssetManager createAssetManager(@NonNull final ResourcesKey key) { 216 AssetManager assets = new AssetManager(); 217 218 // resDir can be null if the 'android' package is creating a new Resources object. 219 // This is fine, since each AssetManager automatically loads the 'android' package 220 // already. 221 if (key.mResDir != null) { 222 if (assets.addAssetPath(key.mResDir) == 0) { 223 return null; 224 } 225 } 226 227 if (key.mSplitResDirs != null) { 228 for (final String splitResDir : key.mSplitResDirs) { 229 if (assets.addAssetPath(splitResDir) == 0) { 230 return null; 231 } 232 } 233 } 234 235 if (key.mOverlayDirs != null) { 236 for (final String idmapPath : key.mOverlayDirs) { 237 assets.addOverlayPath(idmapPath); 238 } 239 } 240 241 if (key.mLibDirs != null) { 242 for (final String libDir : key.mLibDirs) { 243 if (libDir.endsWith(".apk")) { 244 // Avoid opening files we know do not have resources, 245 // like code-only .jar files. 246 if (assets.addAssetPath(libDir) == 0) { 247 Log.w(TAG, "Asset path '" + libDir + 248 "' does not exist or contains no resources."); 249 } 250 } 251 } 252 } 253 return assets; 254 } 255 256 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { 257 Configuration config; 258 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); 259 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 260 if (!isDefaultDisplay || hasOverrideConfig) { 261 config = new Configuration(getConfiguration()); 262 if (!isDefaultDisplay) { 263 applyNonDefaultDisplayMetricsToConfiguration(dm, config); 264 } 265 if (hasOverrideConfig) { 266 config.updateFrom(key.mOverrideConfiguration); 267 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 268 } 269 } else { 270 config = getConfiguration(); 271 } 272 return config; 273 } 274 275 276 private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { 277 AssetManager assets = createAssetManager(key); 278 DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId); 279 Configuration config = generateConfig(key, dm); 280 ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo); 281 if (DEBUG) { 282 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 283 } 284 return impl; 285 } 286 287 /** 288 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 289 * 290 * @param key The key to match. 291 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 292 */ 293 private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) { 294 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 295 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 296 if (impl != null && impl.getAssets().isUpToDate()) { 297 return impl; 298 } 299 return null; 300 } 301 302 /** 303 * Find the ResourcesKey that this ResourcesImpl object is associated with. 304 * @return the ResourcesKey or null if none was found. 305 */ 306 private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) { 307 final int refCount = mResourceImpls.size(); 308 for (int i = 0; i < refCount; i++) { 309 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 310 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 311 if (impl != null && resourceImpl == impl) { 312 return mResourceImpls.keyAt(i); 313 } 314 } 315 return null; 316 } 317 318 /** 319 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist 320 * or the class loader is different. 321 */ 322 private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, 323 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) { 324 // This is a request tied to an Activity, meaning we will need to update all 325 // Activity related Resources to match this configuration. 326 WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken); 327 Resources resources = weakResourceRef != null ? weakResourceRef.get() : null; 328 if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) { 329 resources = new Resources(classLoader); 330 mActivityResourceReferences.put(activityToken, new WeakReference<>(resources)); 331 if (DEBUG) { 332 Slog.d(TAG, "- creating new ref=" + resources); 333 } 334 } else { 335 if (DEBUG) { 336 Slog.d(TAG, "- using existing ref=" + resources); 337 } 338 } 339 340 if (resources.getImpl() != impl) { 341 if (DEBUG) { 342 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 343 } 344 345 // Setting an impl is expensive because we update all ThemeImpl references. 346 // too. 347 resources.setImpl(impl); 348 } 349 return resources; 350 } 351 352 /** 353 * Gets an existing Resources object if the class loader and ResourcesImpl are the same, 354 * otherwise creates a new Resources object. 355 */ 356 private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, 357 @NonNull ResourcesImpl impl) { 358 // Find an existing Resources that has this ResourcesImpl set. 359 final int refCount = mResourceReferences.size(); 360 for (int i = 0; i < refCount; i++) { 361 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); 362 Resources resources = weakResourceRef != null ? weakResourceRef.get() : null; 363 if (resources != null && 364 Objects.equals(resources.getClassLoader(), classLoader) && 365 resources.getImpl() == impl) { 366 if (DEBUG) { 367 Slog.d(TAG, "- using existing ref=" + resources); 368 } 369 return resources; 370 } 371 } 372 373 // Create a new Resources reference and use the existing ResourcesImpl object. 374 Resources resources = new Resources(classLoader); 375 resources.setImpl(impl); 376 mResourceReferences.add(new WeakReference<>(resources)); 377 if (DEBUG) { 378 Slog.d(TAG, "- creating new ref=" + resources); 379 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 380 } 381 return resources; 382 } 383 384 /** 385 * Gets or creates a new Resources object associated with the IBinder token. References returned 386 * by this method live as long as the Activity, meaning they can be cached and used by the 387 * Activity even after a configuration change. If any other parameter is changed 388 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 389 * is updated and handed back to the caller. However, changing the class loader will result in a 390 * new Resources object. 391 * <p/> 392 * If activityToken is null, a cached Resources object will be returned if it matches the 393 * input parameters. Otherwise a new Resources object that satisfies these parameters is 394 * returned. 395 * 396 * @param activityToken Represents an Activity. If null, global resources are assumed. 397 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 398 * @param splitResDirs An array of split resource paths. Can be null. 399 * @param overlayDirs An array of overlay paths. Can be null. 400 * @param libDirs An array of resource library paths. Can be null. 401 * @param displayId The ID of the display for which to create the resources. 402 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 403 * null. Mostly used with Activities that are in multi-window which may override width and 404 * height properties from the base config. 405 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 406 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 407 * @param classLoader The class loader to use when inflating Resources. If null, the 408 * {@link ClassLoader#getSystemClassLoader()} is used. 409 * @return a Resources object from which to access resources. 410 */ 411 public Resources getResources(@Nullable IBinder activityToken, 412 @Nullable String resDir, 413 @Nullable String[] splitResDirs, 414 @Nullable String[] overlayDirs, 415 @Nullable String[] libDirs, 416 int displayId, 417 @Nullable Configuration overrideConfig, 418 @NonNull CompatibilityInfo compatInfo, 419 @Nullable ClassLoader classLoader) { 420 final ResourcesKey key = new ResourcesKey( 421 resDir, 422 splitResDirs, 423 overlayDirs, 424 libDirs, 425 displayId, 426 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 427 compatInfo); 428 429 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 430 431 final boolean findSystemLocales; 432 final boolean hasNonSystemLocales; 433 synchronized (this) { 434 findSystemLocales = (mSystemLocales.length == 0); 435 hasNonSystemLocales = mHasNonSystemLocales; 436 437 if (DEBUG) { 438 Throwable here = new Throwable(); 439 here.fillInStackTrace(); 440 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 441 } 442 443 if (activityToken != null) { 444 ResourcesImpl resourcesImpl = findResourcesImplForKey(key); 445 if (resourcesImpl != null) { 446 if (DEBUG) { 447 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 448 } 449 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, 450 resourcesImpl); 451 } 452 453 // We will create the ResourcesImpl object outside of holding this lock. 454 455 } else { 456 // Clean up any dead references so they don't pile up. 457 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 458 459 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 460 ResourcesImpl resourcesImpl = findResourcesImplForKey(key); 461 if (resourcesImpl != null) { 462 if (DEBUG) { 463 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 464 } 465 return getOrCreateResourcesLocked(classLoader, resourcesImpl); 466 } 467 468 // We will create the ResourcesImpl object outside of holding this lock. 469 } 470 } 471 472 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 473 ResourcesImpl resourcesImpl = createResourcesImpl(key); 474 475 final String[] systemLocales = findSystemLocales 476 ? AssetManager.getSystem().getLocales() : null; 477 final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales(); 478 // Avoid checking for non-pseudo-locales if we already know there were some from a previous 479 // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, 480 // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be 481 // able to affect mHasNonSystemLocales. 482 final boolean isPseudoLocalesOnly = hasNonSystemLocales || 483 LocaleList.isPseudoLocalesOnly(nonSystemLocales); 484 485 synchronized (this) { 486 if (mSystemLocales.length == 0) { 487 mSystemLocales = systemLocales; 488 } 489 mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); 490 mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; 491 492 ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key); 493 if (existingResourcesImpl != null) { 494 if (DEBUG) { 495 Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl 496 + " new impl=" + resourcesImpl); 497 } 498 resourcesImpl.getAssets().close(); 499 resourcesImpl = existingResourcesImpl; 500 } else { 501 // Add this ResourcesImpl to the cache. 502 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); 503 } 504 505 final Resources resources; 506 if (activityToken != null) { 507 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, 508 resourcesImpl); 509 } else { 510 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); 511 } 512 return resources; 513 } 514 } 515 516 /** 517 * Updates an Activity's Resources object with overrideConfig. The Resources object 518 * that was previously returned by 519 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 520 * CompatibilityInfo, ClassLoader)} is 521 * still valid and will have the updated configuration. 522 * @param activityToken The Activity token. 523 * @param overrideConfig The configuration override to update. 524 */ 525 public void updateResourcesForActivity(@NonNull IBinder activityToken, 526 @Nullable Configuration overrideConfig) { 527 final ClassLoader classLoader; 528 final ResourcesKey oldKey; 529 synchronized (this) { 530 // Extract the ResourcesKey that was last used to create the Resources for this 531 // activity. 532 WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken); 533 final Resources resources = weakResRef != null ? weakResRef.get() : null; 534 if (resources == null) { 535 Slog.e(TAG, "can't update resources for uncached activity " + activityToken); 536 return; 537 } 538 539 classLoader = resources.getClassLoader(); 540 oldKey = findKeyForResourceImpl(resources.getImpl()); 541 if (oldKey == null) { 542 Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl()); 543 return; 544 } 545 } 546 547 // Update the Resources object with the new override config and all of the existing 548 // settings. 549 getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs, 550 oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo, 551 classLoader); 552 } 553 554 /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) { 555 final int bestLocale; 556 if (mHasNonSystemLocales) { 557 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); 558 } else { 559 // We fallback to system locales if there was no locale specifically supported by the 560 // assets. This is to properly support apps that only rely on the shared system assets 561 // and don't need assets of their own. 562 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales); 563 } 564 // set it for Java, this also affects newly created Resources 565 LocaleList.setDefault(locales, bestLocale); 566 } 567 568 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, 569 @Nullable CompatibilityInfo compat) { 570 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 571 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 572 + mResConfiguration.seq + ", newSeq=" + config.seq); 573 return false; 574 } 575 int changes = mResConfiguration.updateFrom(config); 576 // Things might have changed in display manager, so clear the cached displays. 577 mDisplays.clear(); 578 DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(); 579 580 if (compat != null && (mResCompatibilityInfo == null || 581 !mResCompatibilityInfo.equals(compat))) { 582 mResCompatibilityInfo = compat; 583 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 584 | ActivityInfo.CONFIG_SCREEN_SIZE 585 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 586 } 587 588 Configuration localeAdjustedConfig = config; 589 final LocaleList configLocales = config.getLocales(); 590 if (!configLocales.isEmpty()) { 591 setDefaultLocalesLocked(configLocales); 592 final LocaleList adjustedLocales = LocaleList.getAdjustedDefault(); 593 if (adjustedLocales != configLocales) { // has the same result as .equals() in this case 594 // The first locale in the list was not chosen. So we create a modified 595 // configuration with the adjusted locales (which moves the chosen locale to the 596 // front). 597 localeAdjustedConfig = new Configuration(); 598 localeAdjustedConfig.setTo(config); 599 localeAdjustedConfig.setLocales(adjustedLocales); 600 // Also adjust the locale list in mResConfiguration, so that the Resources created 601 // later would have the same locale list. 602 if (!mResConfiguration.getLocales().equals(adjustedLocales)) { 603 mResConfiguration.setLocales(adjustedLocales); 604 changes |= ActivityInfo.CONFIG_LOCALE; 605 } 606 } 607 } 608 609 Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat); 610 611 ApplicationPackageManager.configurationChanged(); 612 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 613 614 Configuration tmpConfig = null; 615 616 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 617 ResourcesKey key = mResourceImpls.keyAt(i); 618 ResourcesImpl r = mResourceImpls.valueAt(i).get(); 619 if (r != null) { 620 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 621 + r + " config to: " + localeAdjustedConfig); 622 int displayId = key.mDisplayId; 623 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 624 DisplayMetrics dm = defaultDisplayMetrics; 625 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 626 if (!isDefaultDisplay || hasOverrideConfiguration) { 627 if (tmpConfig == null) { 628 tmpConfig = new Configuration(); 629 } 630 tmpConfig.setTo(localeAdjustedConfig); 631 if (!isDefaultDisplay) { 632 dm = getDisplayMetricsLocked(displayId); 633 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); 634 } 635 if (hasOverrideConfiguration) { 636 tmpConfig.updateFrom(key.mOverrideConfiguration); 637 } 638 r.updateConfiguration(tmpConfig, dm, compat); 639 } else { 640 r.updateConfiguration(localeAdjustedConfig, dm, compat); 641 } 642 //Slog.i(TAG, "Updated app resources " + v.getKey() 643 // + " " + r + ": " + r.getConfiguration()); 644 } else { 645 //Slog.i(TAG, "Removing old resources " + v.getKey()); 646 mResourceImpls.removeAt(i); 647 } 648 } 649 650 return changes != 0; 651 } 652}