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