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