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.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.pm.ActivityInfo; 24 import android.content.res.ApkAssets; 25 import android.content.res.AssetManager; 26 import android.content.res.CompatResources; 27 import android.content.res.CompatibilityInfo; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.content.res.ResourcesImpl; 31 import android.content.res.ResourcesKey; 32 import android.hardware.display.DisplayManagerGlobal; 33 import android.os.IBinder; 34 import android.os.Trace; 35 import android.util.ArrayMap; 36 import android.util.DisplayMetrics; 37 import android.util.Log; 38 import android.util.LruCache; 39 import android.util.Pair; 40 import android.util.Slog; 41 import android.view.Display; 42 import android.view.DisplayAdjustments; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.util.ArrayUtils; 46 import com.android.internal.util.IndentingPrintWriter; 47 48 import java.io.IOException; 49 import java.io.PrintWriter; 50 import java.lang.ref.WeakReference; 51 import java.util.ArrayList; 52 import java.util.Collection; 53 import java.util.Objects; 54 import java.util.WeakHashMap; 55 import java.util.function.Predicate; 56 57 /** @hide */ 58 public class ResourcesManager { 59 static final String TAG = "ResourcesManager"; 60 private static final boolean DEBUG = false; 61 62 private static ResourcesManager sResourcesManager; 63 64 /** 65 * Predicate that returns true if a WeakReference is gc'ed. 66 */ 67 private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = 68 weakRef -> weakRef == null || weakRef.get() == null; 69 70 /** 71 * The global compatibility settings. 72 */ 73 private CompatibilityInfo mResCompatibilityInfo; 74 75 /** 76 * The global configuration upon which all Resources are based. Multi-window Resources 77 * apply their overrides to this configuration. 78 */ 79 private final Configuration mResConfiguration = new Configuration(); 80 81 /** 82 * A mapping of ResourceImpls and their configurations. These are heavy weight objects 83 * which should be reused as much as possible. 84 */ 85 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = 86 new ArrayMap<>(); 87 88 /** 89 * A list of Resource references that can be reused. 90 */ 91 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); 92 93 private static class ApkKey { 94 public final String path; 95 public final boolean sharedLib; 96 public final boolean overlay; 97 ApkKey(String path, boolean sharedLib, boolean overlay)98 ApkKey(String path, boolean sharedLib, boolean overlay) { 99 this.path = path; 100 this.sharedLib = sharedLib; 101 this.overlay = overlay; 102 } 103 104 @Override hashCode()105 public int hashCode() { 106 int result = 1; 107 result = 31 * result + this.path.hashCode(); 108 result = 31 * result + Boolean.hashCode(this.sharedLib); 109 result = 31 * result + Boolean.hashCode(this.overlay); 110 return result; 111 } 112 113 @Override equals(Object obj)114 public boolean equals(Object obj) { 115 if (!(obj instanceof ApkKey)) { 116 return false; 117 } 118 ApkKey other = (ApkKey) obj; 119 return this.path.equals(other.path) && this.sharedLib == other.sharedLib 120 && this.overlay == other.overlay; 121 } 122 } 123 124 /** 125 * The ApkAssets we are caching and intend to hold strong references to. 126 */ 127 private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(3); 128 129 /** 130 * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't 131 * in our LRU cache. Bonus resources :) 132 */ 133 private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); 134 135 /** 136 * Resources and base configuration override associated with an Activity. 137 */ 138 private static class ActivityResources { 139 public final Configuration overrideConfig = new Configuration(); 140 public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>(); 141 } 142 143 /** 144 * Each Activity may has a base override configuration that is applied to each Resources object, 145 * which in turn may have their own override configuration specified. 146 */ 147 private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = 148 new WeakHashMap<>(); 149 150 /** 151 * A cache of DisplayId, DisplayAdjustments to Display. 152 */ 153 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> 154 mAdjustedDisplays = new ArrayMap<>(); 155 getInstance()156 public static ResourcesManager getInstance() { 157 synchronized (ResourcesManager.class) { 158 if (sResourcesManager == null) { 159 sResourcesManager = new ResourcesManager(); 160 } 161 return sResourcesManager; 162 } 163 } 164 165 /** 166 * Invalidate and destroy any resources that reference content under the 167 * given filesystem path. Typically used when unmounting a storage device to 168 * try as hard as possible to release any open FDs. 169 */ invalidatePath(String path)170 public void invalidatePath(String path) { 171 synchronized (this) { 172 int count = 0; 173 for (int i = 0; i < mResourceImpls.size();) { 174 final ResourcesKey key = mResourceImpls.keyAt(i); 175 if (key.isPathReferenced(path)) { 176 cleanupResourceImpl(key); 177 count++; 178 } else { 179 i++; 180 } 181 } 182 Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); 183 } 184 } 185 getConfiguration()186 public Configuration getConfiguration() { 187 synchronized (this) { 188 return mResConfiguration; 189 } 190 } 191 getDisplayMetrics()192 DisplayMetrics getDisplayMetrics() { 193 return getDisplayMetrics(Display.DEFAULT_DISPLAY, 194 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 195 } 196 197 /** 198 * Protected so that tests can override and returns something a fixed value. 199 */ 200 @VisibleForTesting getDisplayMetrics(int displayId, DisplayAdjustments da)201 protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) { 202 DisplayMetrics dm = new DisplayMetrics(); 203 final Display display = getAdjustedDisplay(displayId, da); 204 if (display != null) { 205 display.getMetrics(dm); 206 } else { 207 dm.setToDefaults(); 208 } 209 return dm; 210 } 211 applyNonDefaultDisplayMetricsToConfiguration( @onNull DisplayMetrics dm, @NonNull Configuration config)212 private static void applyNonDefaultDisplayMetricsToConfiguration( 213 @NonNull DisplayMetrics dm, @NonNull Configuration config) { 214 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 215 config.densityDpi = dm.densityDpi; 216 config.screenWidthDp = (int) (dm.widthPixels / dm.density); 217 config.screenHeightDp = (int) (dm.heightPixels / dm.density); 218 int sl = Configuration.resetScreenLayout(config.screenLayout); 219 if (dm.widthPixels > dm.heightPixels) { 220 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 221 config.screenLayout = Configuration.reduceScreenLayout(sl, 222 config.screenWidthDp, config.screenHeightDp); 223 } else { 224 config.orientation = Configuration.ORIENTATION_PORTRAIT; 225 config.screenLayout = Configuration.reduceScreenLayout(sl, 226 config.screenHeightDp, config.screenWidthDp); 227 } 228 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate 229 config.compatScreenWidthDp = config.screenWidthDp; 230 config.compatScreenHeightDp = config.screenHeightDp; 231 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 232 } 233 applyCompatConfigurationLocked(int displayDensity, @NonNull Configuration compatConfiguration)234 public boolean applyCompatConfigurationLocked(int displayDensity, 235 @NonNull Configuration compatConfiguration) { 236 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 237 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 238 return true; 239 } 240 return false; 241 } 242 243 /** 244 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 245 * available. This method is only used within {@link ResourcesManager} to calculate display 246 * metrics based on a set {@link DisplayAdjustments}. All other usages should instead call 247 * {@link ResourcesManager#getAdjustedDisplay(int, Resources)}. 248 * 249 * @param displayId display Id. 250 * @param displayAdjustments display adjustments. 251 */ getAdjustedDisplay(final int displayId, @Nullable DisplayAdjustments displayAdjustments)252 private Display getAdjustedDisplay(final int displayId, 253 @Nullable DisplayAdjustments displayAdjustments) { 254 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) 255 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); 256 final Pair<Integer, DisplayAdjustments> key = 257 Pair.create(displayId, displayAdjustmentsCopy); 258 synchronized (this) { 259 WeakReference<Display> wd = mAdjustedDisplays.get(key); 260 if (wd != null) { 261 final Display display = wd.get(); 262 if (display != null) { 263 return display; 264 } 265 } 266 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 267 if (dm == null) { 268 // may be null early in system startup 269 return null; 270 } 271 final Display display = dm.getCompatibleDisplay(displayId, key.second); 272 if (display != null) { 273 mAdjustedDisplays.put(key, new WeakReference<>(display)); 274 } 275 return display; 276 } 277 } 278 279 /** 280 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 281 * available. 282 * 283 * @param displayId display Id. 284 * @param resources The {@link Resources} backing the display adjustments. 285 */ getAdjustedDisplay(final int displayId, Resources resources)286 public Display getAdjustedDisplay(final int displayId, Resources resources) { 287 synchronized (this) { 288 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 289 if (dm == null) { 290 // may be null early in system startup 291 return null; 292 } 293 return dm.getCompatibleDisplay(displayId, resources); 294 } 295 } 296 cleanupResourceImpl(ResourcesKey removedKey)297 private void cleanupResourceImpl(ResourcesKey removedKey) { 298 // Remove resource key to resource impl mapping and flush cache 299 final ResourcesImpl res = mResourceImpls.remove(removedKey).get(); 300 301 if (res != null) { 302 res.flushLayoutCache(); 303 } 304 } 305 overlayPathToIdmapPath(String path)306 private static String overlayPathToIdmapPath(String path) { 307 return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; 308 } 309 loadApkAssets(String path, boolean sharedLib, boolean overlay)310 private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) 311 throws IOException { 312 final ApkKey newKey = new ApkKey(path, sharedLib, overlay); 313 ApkAssets apkAssets = mLoadedApkAssets.get(newKey); 314 if (apkAssets != null) { 315 return apkAssets; 316 } 317 318 // Optimistically check if this ApkAssets exists somewhere else. 319 final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); 320 if (apkAssetsRef != null) { 321 apkAssets = apkAssetsRef.get(); 322 if (apkAssets != null) { 323 mLoadedApkAssets.put(newKey, apkAssets); 324 return apkAssets; 325 } else { 326 // Clean up the reference. 327 mCachedApkAssets.remove(newKey); 328 } 329 } 330 331 // We must load this from disk. 332 if (overlay) { 333 apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 334 false /*system*/); 335 } else { 336 apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); 337 } 338 mLoadedApkAssets.put(newKey, apkAssets); 339 mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); 340 return apkAssets; 341 } 342 343 /** 344 * Creates an AssetManager from the paths within the ResourcesKey. 345 * 346 * This can be overridden in tests so as to avoid creating a real AssetManager with 347 * real APK paths. 348 * @param key The key containing the resource paths to add to the AssetManager. 349 * @return a new AssetManager. 350 */ 351 @VisibleForTesting createAssetManager(@onNull final ResourcesKey key)352 protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { 353 final AssetManager.Builder builder = new AssetManager.Builder(); 354 355 // resDir can be null if the 'android' package is creating a new Resources object. 356 // This is fine, since each AssetManager automatically loads the 'android' package 357 // already. 358 if (key.mResDir != null) { 359 try { 360 builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/, 361 false /*overlay*/)); 362 } catch (IOException e) { 363 Log.e(TAG, "failed to add asset path " + key.mResDir); 364 return null; 365 } 366 } 367 368 if (key.mSplitResDirs != null) { 369 for (final String splitResDir : key.mSplitResDirs) { 370 try { 371 builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/, 372 false /*overlay*/)); 373 } catch (IOException e) { 374 Log.e(TAG, "failed to add split asset path " + splitResDir); 375 return null; 376 } 377 } 378 } 379 380 if (key.mOverlayDirs != null) { 381 for (final String idmapPath : key.mOverlayDirs) { 382 try { 383 builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, 384 true /*overlay*/)); 385 } catch (IOException e) { 386 Log.w(TAG, "failed to add overlay path " + idmapPath); 387 388 // continue. 389 } 390 } 391 } 392 393 if (key.mLibDirs != null) { 394 for (final String libDir : key.mLibDirs) { 395 if (libDir.endsWith(".apk")) { 396 // Avoid opening files we know do not have resources, 397 // like code-only .jar files. 398 try { 399 builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/, 400 false /*overlay*/)); 401 } catch (IOException e) { 402 Log.w(TAG, "Asset path '" + libDir + 403 "' does not exist or contains no resources."); 404 405 // continue. 406 } 407 } 408 } 409 } 410 411 return builder.build(); 412 } 413 countLiveReferences(Collection<WeakReference<T>> collection)414 private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { 415 int count = 0; 416 for (WeakReference<T> ref : collection) { 417 final T value = ref != null ? ref.get() : null; 418 if (value != null) { 419 count++; 420 } 421 } 422 return count; 423 } 424 425 /** 426 * @hide 427 */ dump(String prefix, PrintWriter printWriter)428 public void dump(String prefix, PrintWriter printWriter) { 429 synchronized (this) { 430 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 431 for (int i = 0; i < prefix.length() / 2; i++) { 432 pw.increaseIndent(); 433 } 434 435 pw.println("ResourcesManager:"); 436 pw.increaseIndent(); 437 pw.print("cached apks: total="); 438 pw.print(mLoadedApkAssets.size()); 439 pw.print(" created="); 440 pw.print(mLoadedApkAssets.createCount()); 441 pw.print(" evicted="); 442 pw.print(mLoadedApkAssets.evictionCount()); 443 pw.print(" hit="); 444 pw.print(mLoadedApkAssets.hitCount()); 445 pw.print(" miss="); 446 pw.print(mLoadedApkAssets.missCount()); 447 pw.print(" max="); 448 pw.print(mLoadedApkAssets.maxSize()); 449 pw.println(); 450 451 pw.print("total apks: "); 452 pw.println(countLiveReferences(mCachedApkAssets.values())); 453 454 pw.print("resources: "); 455 456 int references = countLiveReferences(mResourceReferences); 457 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 458 references += countLiveReferences(activityResources.activityResources); 459 } 460 pw.println(references); 461 462 pw.print("resource impls: "); 463 pw.println(countLiveReferences(mResourceImpls.values())); 464 } 465 } 466 generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)467 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { 468 Configuration config; 469 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); 470 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 471 if (!isDefaultDisplay || hasOverrideConfig) { 472 config = new Configuration(getConfiguration()); 473 if (!isDefaultDisplay) { 474 applyNonDefaultDisplayMetricsToConfiguration(dm, config); 475 } 476 if (hasOverrideConfig) { 477 config.updateFrom(key.mOverrideConfiguration); 478 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 479 } 480 } else { 481 config = getConfiguration(); 482 } 483 return config; 484 } 485 createResourcesImpl(@onNull ResourcesKey key)486 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { 487 final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); 488 daj.setCompatibilityInfo(key.mCompatInfo); 489 490 final AssetManager assets = createAssetManager(key); 491 if (assets == null) { 492 return null; 493 } 494 495 final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); 496 final Configuration config = generateConfig(key, dm); 497 final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); 498 499 if (DEBUG) { 500 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 501 } 502 return impl; 503 } 504 505 /** 506 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 507 * 508 * @param key The key to match. 509 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 510 */ findResourcesImplForKeyLocked(@onNull ResourcesKey key)511 private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) { 512 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 513 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 514 if (impl != null && impl.getAssets().isUpToDate()) { 515 return impl; 516 } 517 return null; 518 } 519 520 /** 521 * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or 522 * creates a new one and caches it for future use. 523 * @param key The key to match. 524 * @return a ResourcesImpl object matching the key. 525 */ findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)526 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( 527 @NonNull ResourcesKey key) { 528 ResourcesImpl impl = findResourcesImplForKeyLocked(key); 529 if (impl == null) { 530 impl = createResourcesImpl(key); 531 if (impl != null) { 532 mResourceImpls.put(key, new WeakReference<>(impl)); 533 } 534 } 535 return impl; 536 } 537 538 /** 539 * Find the ResourcesKey that this ResourcesImpl object is associated with. 540 * @return the ResourcesKey or null if none was found. 541 */ findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)542 private @Nullable ResourcesKey findKeyForResourceImplLocked( 543 @NonNull ResourcesImpl resourceImpl) { 544 final int refCount = mResourceImpls.size(); 545 for (int i = 0; i < refCount; i++) { 546 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 547 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 548 if (impl != null && resourceImpl == impl) { 549 return mResourceImpls.keyAt(i); 550 } 551 } 552 return null; 553 } 554 555 /** 556 * Check if activity resources have same override config as the provided on. 557 * @param activityToken The Activity that resources should be associated with. 558 * @param overrideConfig The override configuration to be checked for equality with. 559 * @return true if activity resources override config matches the provided one or they are both 560 * null, false otherwise. 561 */ isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)562 boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken, 563 @Nullable Configuration overrideConfig) { 564 synchronized (this) { 565 final ActivityResources activityResources 566 = activityToken != null ? mActivityResourceReferences.get(activityToken) : null; 567 if (activityResources == null) { 568 return overrideConfig == null; 569 } else { 570 // The two configurations must either be equal or publicly equivalent to be 571 // considered the same. 572 return Objects.equals(activityResources.overrideConfig, overrideConfig) 573 || (overrideConfig != null && activityResources.overrideConfig != null 574 && 0 == overrideConfig.diffPublicOnly( 575 activityResources.overrideConfig)); 576 } 577 } 578 } 579 getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)580 private ActivityResources getOrCreateActivityResourcesStructLocked( 581 @NonNull IBinder activityToken) { 582 ActivityResources activityResources = mActivityResourceReferences.get(activityToken); 583 if (activityResources == null) { 584 activityResources = new ActivityResources(); 585 mActivityResourceReferences.put(activityToken, activityResources); 586 } 587 return activityResources; 588 } 589 590 /** 591 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist 592 * or the class loader is different. 593 */ getOrCreateResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)594 private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, 595 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, 596 @NonNull CompatibilityInfo compatInfo) { 597 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 598 activityToken); 599 600 final int refCount = activityResources.activityResources.size(); 601 for (int i = 0; i < refCount; i++) { 602 WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i); 603 Resources resources = weakResourceRef.get(); 604 605 if (resources != null 606 && Objects.equals(resources.getClassLoader(), classLoader) 607 && resources.getImpl() == impl) { 608 if (DEBUG) { 609 Slog.d(TAG, "- using existing ref=" + resources); 610 } 611 return resources; 612 } 613 } 614 615 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) 616 : new Resources(classLoader); 617 resources.setImpl(impl); 618 activityResources.activityResources.add(new WeakReference<>(resources)); 619 if (DEBUG) { 620 Slog.d(TAG, "- creating new ref=" + resources); 621 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 622 } 623 return resources; 624 } 625 626 /** 627 * Gets an existing Resources object if the class loader and ResourcesImpl are the same, 628 * otherwise creates a new Resources object. 629 */ getOrCreateResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)630 private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, 631 @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) { 632 // Find an existing Resources that has this ResourcesImpl set. 633 final int refCount = mResourceReferences.size(); 634 for (int i = 0; i < refCount; i++) { 635 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); 636 Resources resources = weakResourceRef.get(); 637 if (resources != null && 638 Objects.equals(resources.getClassLoader(), classLoader) && 639 resources.getImpl() == impl) { 640 if (DEBUG) { 641 Slog.d(TAG, "- using existing ref=" + resources); 642 } 643 return resources; 644 } 645 } 646 647 // Create a new Resources reference and use the existing ResourcesImpl object. 648 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) 649 : new Resources(classLoader); 650 resources.setImpl(impl); 651 mResourceReferences.add(new WeakReference<>(resources)); 652 if (DEBUG) { 653 Slog.d(TAG, "- creating new ref=" + resources); 654 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 655 } 656 return resources; 657 } 658 659 /** 660 * Creates base resources for an Activity. Calls to 661 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 662 * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override 663 * configurations merged with the one specified here. 664 * 665 * @param activityToken Represents an Activity. 666 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 667 * @param splitResDirs An array of split resource paths. Can be null. 668 * @param overlayDirs An array of overlay paths. Can be null. 669 * @param libDirs An array of resource library paths. Can be null. 670 * @param displayId The ID of the display for which to create the resources. 671 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 672 * null. This provides the base override for this Activity. 673 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 674 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 675 * @param classLoader The class loader to use when inflating Resources. If null, the 676 * {@link ClassLoader#getSystemClassLoader()} is used. 677 * @return a Resources object from which to access resources. 678 */ createBaseActivityResources(@onNull IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader)679 public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken, 680 @Nullable String resDir, 681 @Nullable String[] splitResDirs, 682 @Nullable String[] overlayDirs, 683 @Nullable String[] libDirs, 684 int displayId, 685 @Nullable Configuration overrideConfig, 686 @NonNull CompatibilityInfo compatInfo, 687 @Nullable ClassLoader classLoader) { 688 try { 689 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 690 "ResourcesManager#createBaseActivityResources"); 691 final ResourcesKey key = new ResourcesKey( 692 resDir, 693 splitResDirs, 694 overlayDirs, 695 libDirs, 696 displayId, 697 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 698 compatInfo); 699 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 700 701 if (DEBUG) { 702 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken 703 + " with key=" + key); 704 } 705 706 synchronized (this) { 707 // Force the creation of an ActivityResourcesStruct. 708 getOrCreateActivityResourcesStructLocked(activityToken); 709 } 710 711 // Update any existing Activity Resources references. 712 updateResourcesForActivity(activityToken, overrideConfig, displayId, 713 false /* movedToDifferentDisplay */); 714 715 // Now request an actual Resources object. 716 return getOrCreateResources(activityToken, key, classLoader); 717 } finally { 718 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 719 } 720 } 721 722 /** 723 * Gets an existing Resources object set with a ResourcesImpl object matching the given key, 724 * or creates one if it doesn't exist. 725 * 726 * @param activityToken The Activity this Resources object should be associated with. 727 * @param key The key describing the parameters of the ResourcesImpl object. 728 * @param classLoader The classloader to use for the Resources object. 729 * If null, {@link ClassLoader#getSystemClassLoader()} is used. 730 * @return A Resources object that gets updated when 731 * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} 732 * is called. 733 */ getOrCreateResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader)734 private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken, 735 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { 736 synchronized (this) { 737 if (DEBUG) { 738 Throwable here = new Throwable(); 739 here.fillInStackTrace(); 740 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 741 } 742 743 if (activityToken != null) { 744 final ActivityResources activityResources = 745 getOrCreateActivityResourcesStructLocked(activityToken); 746 747 // Clean up any dead references so they don't pile up. 748 ArrayUtils.unstableRemoveIf(activityResources.activityResources, 749 sEmptyReferencePredicate); 750 751 // Rebase the key's override config on top of the Activity's base override. 752 if (key.hasOverrideConfiguration() 753 && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { 754 final Configuration temp = new Configuration(activityResources.overrideConfig); 755 temp.updateFrom(key.mOverrideConfiguration); 756 key.mOverrideConfiguration.setTo(temp); 757 } 758 759 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 760 if (resourcesImpl != null) { 761 if (DEBUG) { 762 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 763 } 764 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, 765 resourcesImpl, key.mCompatInfo); 766 } 767 768 // We will create the ResourcesImpl object outside of holding this lock. 769 770 } else { 771 // Clean up any dead references so they don't pile up. 772 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 773 774 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 775 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 776 if (resourcesImpl != null) { 777 if (DEBUG) { 778 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 779 } 780 return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); 781 } 782 783 // We will create the ResourcesImpl object outside of holding this lock. 784 } 785 786 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 787 ResourcesImpl resourcesImpl = createResourcesImpl(key); 788 if (resourcesImpl == null) { 789 return null; 790 } 791 792 // Add this ResourcesImpl to the cache. 793 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); 794 795 final Resources resources; 796 if (activityToken != null) { 797 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, 798 resourcesImpl, key.mCompatInfo); 799 } else { 800 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); 801 } 802 return resources; 803 } 804 } 805 806 /** 807 * Gets or creates a new Resources object associated with the IBinder token. References returned 808 * by this method live as long as the Activity, meaning they can be cached and used by the 809 * Activity even after a configuration change. If any other parameter is changed 810 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 811 * is updated and handed back to the caller. However, changing the class loader will result in a 812 * new Resources object. 813 * <p/> 814 * If activityToken is null, a cached Resources object will be returned if it matches the 815 * input parameters. Otherwise a new Resources object that satisfies these parameters is 816 * returned. 817 * 818 * @param activityToken Represents an Activity. If null, global resources are assumed. 819 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 820 * @param splitResDirs An array of split resource paths. Can be null. 821 * @param overlayDirs An array of overlay paths. Can be null. 822 * @param libDirs An array of resource library paths. Can be null. 823 * @param displayId The ID of the display for which to create the resources. 824 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 825 * null. Mostly used with Activities that are in multi-window which may override width and 826 * height properties from the base config. 827 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 828 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 829 * @param classLoader The class loader to use when inflating Resources. If null, the 830 * {@link ClassLoader#getSystemClassLoader()} is used. 831 * @return a Resources object from which to access resources. 832 */ getResources(@ullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader)833 public @Nullable Resources getResources(@Nullable IBinder activityToken, 834 @Nullable String resDir, 835 @Nullable String[] splitResDirs, 836 @Nullable String[] overlayDirs, 837 @Nullable String[] libDirs, 838 int displayId, 839 @Nullable Configuration overrideConfig, 840 @NonNull CompatibilityInfo compatInfo, 841 @Nullable ClassLoader classLoader) { 842 try { 843 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 844 final ResourcesKey key = new ResourcesKey( 845 resDir, 846 splitResDirs, 847 overlayDirs, 848 libDirs, 849 displayId, 850 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 851 compatInfo); 852 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 853 return getOrCreateResources(activityToken, key, classLoader); 854 } finally { 855 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 856 } 857 } 858 859 /** 860 * Updates an Activity's Resources object with overrideConfig. The Resources object 861 * that was previously returned by 862 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 863 * CompatibilityInfo, ClassLoader)} is 864 * still valid and will have the updated configuration. 865 * @param activityToken The Activity token. 866 * @param overrideConfig The configuration override to update. 867 * @param displayId Id of the display where activity currently resides. 868 * @param movedToDifferentDisplay Indicates if the activity was moved to different display. 869 */ updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig, int displayId, boolean movedToDifferentDisplay)870 public void updateResourcesForActivity(@NonNull IBinder activityToken, 871 @Nullable Configuration overrideConfig, int displayId, 872 boolean movedToDifferentDisplay) { 873 try { 874 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 875 "ResourcesManager#updateResourcesForActivity"); 876 synchronized (this) { 877 final ActivityResources activityResources = 878 getOrCreateActivityResourcesStructLocked(activityToken); 879 880 if (Objects.equals(activityResources.overrideConfig, overrideConfig) 881 && !movedToDifferentDisplay) { 882 // They are the same and no change of display id, no work to do. 883 return; 884 } 885 886 // Grab a copy of the old configuration so we can create the delta's of each 887 // Resources object associated with this Activity. 888 final Configuration oldConfig = new Configuration(activityResources.overrideConfig); 889 890 // Update the Activity's base override. 891 if (overrideConfig != null) { 892 activityResources.overrideConfig.setTo(overrideConfig); 893 } else { 894 activityResources.overrideConfig.unset(); 895 } 896 897 if (DEBUG) { 898 Throwable here = new Throwable(); 899 here.fillInStackTrace(); 900 Slog.d(TAG, "updating resources override for activity=" + activityToken 901 + " from oldConfig=" 902 + Configuration.resourceQualifierString(oldConfig) 903 + " to newConfig=" 904 + Configuration.resourceQualifierString( 905 activityResources.overrideConfig) + " displayId=" + displayId, 906 here); 907 } 908 909 final boolean activityHasOverrideConfig = 910 !activityResources.overrideConfig.equals(Configuration.EMPTY); 911 912 // Rebase each Resources associated with this Activity. 913 final int refCount = activityResources.activityResources.size(); 914 for (int i = 0; i < refCount; i++) { 915 WeakReference<Resources> weakResRef = activityResources.activityResources.get( 916 i); 917 Resources resources = weakResRef.get(); 918 if (resources == null) { 919 continue; 920 } 921 922 // Extract the ResourcesKey that was last used to create the Resources for this 923 // activity. 924 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 925 if (oldKey == null) { 926 Slog.e(TAG, "can't find ResourcesKey for resources impl=" 927 + resources.getImpl()); 928 continue; 929 } 930 931 // Build the new override configuration for this ResourcesKey. 932 final Configuration rebasedOverrideConfig = new Configuration(); 933 if (overrideConfig != null) { 934 rebasedOverrideConfig.setTo(overrideConfig); 935 } 936 937 if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) { 938 // Generate a delta between the old base Activity override configuration and 939 // the actual final override configuration that was used to figure out the 940 // real delta this Resources object wanted. 941 Configuration overrideOverrideConfig = Configuration.generateDelta( 942 oldConfig, oldKey.mOverrideConfiguration); 943 rebasedOverrideConfig.updateFrom(overrideOverrideConfig); 944 } 945 946 // Create the new ResourcesKey with the rebased override config. 947 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, 948 oldKey.mSplitResDirs, 949 oldKey.mOverlayDirs, oldKey.mLibDirs, displayId, 950 rebasedOverrideConfig, oldKey.mCompatInfo); 951 952 if (DEBUG) { 953 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey 954 + " to newKey=" + newKey + ", displayId=" + displayId); 955 } 956 957 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); 958 if (resourcesImpl == null) { 959 resourcesImpl = createResourcesImpl(newKey); 960 if (resourcesImpl != null) { 961 mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); 962 } 963 } 964 965 if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { 966 // Set the ResourcesImpl, updating it for all users of this Resources 967 // object. 968 resources.setImpl(resourcesImpl); 969 } 970 } 971 } 972 } finally { 973 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 974 } 975 } 976 applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)977 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, 978 @Nullable CompatibilityInfo compat) { 979 try { 980 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 981 "ResourcesManager#applyConfigurationToResourcesLocked"); 982 983 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 984 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 985 + mResConfiguration.seq + ", newSeq=" + config.seq); 986 return false; 987 } 988 int changes = mResConfiguration.updateFrom(config); 989 // Things might have changed in display manager, so clear the cached displays. 990 mAdjustedDisplays.clear(); 991 992 DisplayMetrics defaultDisplayMetrics = getDisplayMetrics(); 993 994 if (compat != null && (mResCompatibilityInfo == null || 995 !mResCompatibilityInfo.equals(compat))) { 996 mResCompatibilityInfo = compat; 997 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 998 | ActivityInfo.CONFIG_SCREEN_SIZE 999 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 1000 } 1001 1002 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); 1003 1004 ApplicationPackageManager.configurationChanged(); 1005 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 1006 1007 Configuration tmpConfig = null; 1008 1009 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 1010 ResourcesKey key = mResourceImpls.keyAt(i); 1011 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1012 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null; 1013 if (r != null) { 1014 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 1015 + r + " config to: " + config); 1016 int displayId = key.mDisplayId; 1017 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 1018 DisplayMetrics dm = defaultDisplayMetrics; 1019 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 1020 if (!isDefaultDisplay || hasOverrideConfiguration) { 1021 if (tmpConfig == null) { 1022 tmpConfig = new Configuration(); 1023 } 1024 tmpConfig.setTo(config); 1025 1026 // Get new DisplayMetrics based on the DisplayAdjustments given 1027 // to the ResourcesImpl. Update a copy if the CompatibilityInfo 1028 // changed, because the ResourcesImpl object will handle the 1029 // update internally. 1030 DisplayAdjustments daj = r.getDisplayAdjustments(); 1031 if (compat != null) { 1032 daj = new DisplayAdjustments(daj); 1033 daj.setCompatibilityInfo(compat); 1034 } 1035 dm = getDisplayMetrics(displayId, daj); 1036 1037 if (!isDefaultDisplay) { 1038 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); 1039 } 1040 1041 if (hasOverrideConfiguration) { 1042 tmpConfig.updateFrom(key.mOverrideConfiguration); 1043 } 1044 r.updateConfiguration(tmpConfig, dm, compat); 1045 } else { 1046 r.updateConfiguration(config, dm, compat); 1047 } 1048 //Slog.i(TAG, "Updated app resources " + v.getKey() 1049 // + " " + r + ": " + r.getConfiguration()); 1050 } else { 1051 //Slog.i(TAG, "Removing old resources " + v.getKey()); 1052 mResourceImpls.removeAt(i); 1053 } 1054 } 1055 1056 return changes != 0; 1057 } finally { 1058 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1059 } 1060 } 1061 1062 /** 1063 * Appends the library asset path to any ResourcesImpl object that contains the main 1064 * assetPath. 1065 * @param assetPath The main asset path for which to add the library asset path. 1066 * @param libAsset The library asset path to add. 1067 */ appendLibAssetForMainAssetPath(String assetPath, String libAsset)1068 public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { 1069 synchronized (this) { 1070 // Record which ResourcesImpl need updating 1071 // (and what ResourcesKey they should update to). 1072 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 1073 1074 final int implCount = mResourceImpls.size(); 1075 for (int i = 0; i < implCount; i++) { 1076 final ResourcesKey key = mResourceImpls.keyAt(i); 1077 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1078 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 1079 if (impl != null && Objects.equals(key.mResDir, assetPath)) { 1080 if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { 1081 final int newLibAssetCount = 1 + 1082 (key.mLibDirs != null ? key.mLibDirs.length : 0); 1083 final String[] newLibAssets = new String[newLibAssetCount]; 1084 if (key.mLibDirs != null) { 1085 System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length); 1086 } 1087 newLibAssets[newLibAssetCount - 1] = libAsset; 1088 1089 updatedResourceKeys.put(impl, new ResourcesKey( 1090 key.mResDir, 1091 key.mSplitResDirs, 1092 key.mOverlayDirs, 1093 newLibAssets, 1094 key.mDisplayId, 1095 key.mOverrideConfiguration, 1096 key.mCompatInfo)); 1097 } 1098 } 1099 } 1100 1101 redirectResourcesToNewImplLocked(updatedResourceKeys); 1102 } 1103 } 1104 1105 // TODO(adamlesinski): Make this accept more than just overlay directories. applyNewResourceDirsLocked(@onNull final String baseCodePath, @Nullable final String[] newResourceDirs)1106 final void applyNewResourceDirsLocked(@NonNull final String baseCodePath, 1107 @Nullable final String[] newResourceDirs) { 1108 try { 1109 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 1110 "ResourcesManager#applyNewResourceDirsLocked"); 1111 1112 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 1113 final int implCount = mResourceImpls.size(); 1114 for (int i = 0; i < implCount; i++) { 1115 final ResourcesKey key = mResourceImpls.keyAt(i); 1116 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1117 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 1118 if (impl != null && (key.mResDir == null || key.mResDir.equals(baseCodePath))) { 1119 updatedResourceKeys.put(impl, new ResourcesKey( 1120 key.mResDir, 1121 key.mSplitResDirs, 1122 newResourceDirs, 1123 key.mLibDirs, 1124 key.mDisplayId, 1125 key.mOverrideConfiguration, 1126 key.mCompatInfo)); 1127 } 1128 } 1129 1130 redirectResourcesToNewImplLocked(updatedResourceKeys); 1131 } finally { 1132 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1133 } 1134 } 1135 redirectResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1136 private void redirectResourcesToNewImplLocked( 1137 @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { 1138 // Bail early if there is no work to do. 1139 if (updatedResourceKeys.isEmpty()) { 1140 return; 1141 } 1142 1143 // Update any references to ResourcesImpl that require reloading. 1144 final int resourcesCount = mResourceReferences.size(); 1145 for (int i = 0; i < resourcesCount; i++) { 1146 final WeakReference<Resources> ref = mResourceReferences.get(i); 1147 final Resources r = ref != null ? ref.get() : null; 1148 if (r != null) { 1149 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 1150 if (key != null) { 1151 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 1152 if (impl == null) { 1153 throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); 1154 } 1155 r.setImpl(impl); 1156 } 1157 } 1158 } 1159 1160 // Update any references to ResourcesImpl that require reloading for each Activity. 1161 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 1162 final int resCount = activityResources.activityResources.size(); 1163 for (int i = 0; i < resCount; i++) { 1164 final WeakReference<Resources> ref = activityResources.activityResources.get(i); 1165 final Resources r = ref != null ? ref.get() : null; 1166 if (r != null) { 1167 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 1168 if (key != null) { 1169 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 1170 if (impl == null) { 1171 throw new Resources.NotFoundException( 1172 "failed to redirect ResourcesImpl"); 1173 } 1174 r.setImpl(impl); 1175 } 1176 } 1177 } 1178 } 1179 } 1180 } 1181