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