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 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.view.Display.INVALID_DISPLAY; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.pm.ActivityInfo; 27 import android.content.pm.ApplicationInfo; 28 import android.content.res.ApkAssets; 29 import android.content.res.AssetManager; 30 import android.content.res.CompatResources; 31 import android.content.res.CompatibilityInfo; 32 import android.content.res.Configuration; 33 import android.content.res.Flags; 34 import android.content.res.Resources; 35 import android.content.res.ResourcesImpl; 36 import android.content.res.ResourcesKey; 37 import android.content.res.loader.ResourcesLoader; 38 import android.hardware.display.DisplayManagerGlobal; 39 import android.os.IBinder; 40 import android.os.LocaleList; 41 import android.os.Process; 42 import android.os.Trace; 43 import android.util.ArrayMap; 44 import android.util.ArraySet; 45 import android.util.DisplayMetrics; 46 import android.util.IndentingPrintWriter; 47 import android.util.Log; 48 import android.util.Pair; 49 import android.util.Slog; 50 import android.view.Display; 51 import android.view.DisplayAdjustments; 52 import android.view.DisplayInfo; 53 import android.window.WindowContext; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.util.ArrayUtils; 57 58 import java.io.IOException; 59 import java.io.PrintWriter; 60 import java.lang.ref.Reference; 61 import java.lang.ref.ReferenceQueue; 62 import java.lang.ref.WeakReference; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Collection; 66 import java.util.HashSet; 67 import java.util.List; 68 import java.util.Objects; 69 import java.util.WeakHashMap; 70 import java.util.function.Function; 71 72 /** @hide */ 73 public class ResourcesManager { 74 static final String TAG = "ResourcesManager"; 75 private static final boolean DEBUG = false; 76 77 private static ResourcesManager sResourcesManager; 78 79 /** 80 * Internal lock object 81 */ 82 private final Object mLock = new Object(); 83 84 /** 85 * The global compatibility settings. 86 */ 87 private CompatibilityInfo mResCompatibilityInfo; 88 89 /** 90 * The global configuration upon which all Resources are based. Multi-window Resources 91 * apply their overrides to this configuration. 92 */ 93 @UnsupportedAppUsage 94 private final Configuration mResConfiguration = new Configuration(); 95 96 /** 97 * The display upon which all Resources are based. Activity, window token, and display context 98 * resources apply their overrides to this display id. 99 */ 100 private int mResDisplayId = DEFAULT_DISPLAY; 101 102 /** 103 * ApplicationInfo changes that need to be applied to Resources when the next configuration 104 * change occurs. 105 */ 106 private ArrayList<Pair<String[], ApplicationInfo>> mPendingAppInfoUpdates; 107 108 /** 109 * A mapping of ResourceImpls and their configurations. These are heavy weight objects 110 * which should be reused as much as possible. 111 */ 112 @UnsupportedAppUsage 113 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = 114 new ArrayMap<>(); 115 116 /** 117 * A list of Resource references that can be reused. 118 */ 119 @UnsupportedAppUsage 120 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); 121 private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>(); 122 123 /** 124 * A list of Resources references for all Resources instances created through Resources public 125 * constructor, only system Resources created by the private constructor are excluded. 126 * This addition is necessary due to certain Application Resources created by constructor 127 * directly which are not managed by ResourcesManager, hence we require a comprehensive 128 * collection of all Resources references to help with asset paths appending tasks when shared 129 * libraries are registered. 130 */ 131 private final ArrayList<WeakReference<Resources>> mAllResourceReferences = new ArrayList<>(); 132 private final ReferenceQueue<Resources> mAllResourceReferencesQueue = new ReferenceQueue<>(); 133 134 /** 135 * The localeConfig of the app. 136 */ 137 private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList()); 138 139 private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = 140 new ArrayMap<>(); 141 142 @VisibleForTesting getRegisteredResourcePaths()143 public ArrayMap<String, SharedLibraryAssets> getRegisteredResourcePaths() { 144 return mSharedLibAssetsMap; 145 } 146 147 /** 148 * The internal function to register the resources paths of a package (e.g. a shared library). 149 * This will collect the package resources' paths from its ApplicationInfo and add them to all 150 * existing and future contexts while the application is running. 151 */ registerResourcePaths(@onNull String uniqueId, @NonNull ApplicationInfo appInfo)152 public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { 153 if (!Flags.registerResourcePaths()) { 154 return; 155 } 156 157 final var sharedLibAssets = new SharedLibraryAssets(appInfo); 158 synchronized (mLock) { 159 if (mSharedLibAssetsMap.containsKey(uniqueId)) { 160 Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId 161 + " has already been registered, this is a no-op."); 162 return; 163 } 164 mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); 165 appendLibAssetsLocked(sharedLibAssets); 166 Slog.v(TAG, "The following library key has been added: " 167 + sharedLibAssets.getResourcesKey()); 168 } 169 } 170 171 /** 172 * Apply the registered library paths to the passed impl object 173 * @return the hash code for the current version of the registered paths 174 */ updateResourceImplWithRegisteredLibs(@onNull ResourcesImpl impl)175 public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { 176 if (!Flags.registerResourcePaths()) { 177 return 0; 178 } 179 180 final var collector = new PathCollector(null); 181 final int size = mSharedLibAssetsMap.size(); 182 for (int i = 0; i < size; i++) { 183 final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); 184 collector.appendKey(libraryKey); 185 } 186 impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); 187 return size; 188 } 189 190 public static class ApkKey { 191 public final String path; 192 public final boolean sharedLib; 193 public final boolean overlay; 194 ApkKey(String path, boolean sharedLib, boolean overlay)195 public ApkKey(String path, boolean sharedLib, boolean overlay) { 196 this.path = path; 197 this.sharedLib = sharedLib; 198 this.overlay = overlay; 199 } 200 201 @Override hashCode()202 public int hashCode() { 203 int result = 1; 204 result = 31 * result + this.path.hashCode(); 205 result = 31 * result + Boolean.hashCode(this.sharedLib); 206 result = 31 * result + Boolean.hashCode(this.overlay); 207 return result; 208 } 209 210 @Override equals(@ullable Object obj)211 public boolean equals(@Nullable Object obj) { 212 if (!(obj instanceof ApkKey)) { 213 return false; 214 } 215 ApkKey other = (ApkKey) obj; 216 return this.path.equals(other.path) && this.sharedLib == other.sharedLib 217 && this.overlay == other.overlay; 218 } 219 220 @Override toString()221 public String toString() { 222 return "ApkKey[" + (sharedLib ? "lib" : "app") + (overlay ? ", overlay" : "") + ": " 223 + path + "]"; 224 } 225 } 226 227 /** 228 * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the 229 * instance is alive and reachable. 230 */ 231 @VisibleForTesting 232 protected class ApkAssetsSupplier { 233 final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>(); 234 235 /** 236 * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets 237 * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets} 238 * cache. 239 */ load(final ApkKey apkKey)240 ApkAssets load(final ApkKey apkKey) throws IOException { 241 ApkAssets apkAssets = mLocalCache.get(apkKey); 242 if (apkAssets == null) { 243 apkAssets = loadApkAssets(apkKey); 244 mLocalCache.put(apkKey, apkAssets); 245 } 246 return apkAssets; 247 } 248 } 249 250 /** 251 * The ApkAssets that are being referenced in the wild that we can reuse. 252 * Used as a lock for itself as well. 253 */ 254 private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); 255 256 /** 257 * Class containing the base configuration override and set of resources associated with an 258 * {@link Activity} or a {@link WindowContext}. 259 */ 260 private static class ActivityResources { 261 /** 262 * Override config to apply to all resources associated with the token this instance is 263 * based on. 264 * 265 * @see #activityResources 266 * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer, 267 * Configuration, CompatibilityInfo, ClassLoader, List) 268 */ 269 public final Configuration overrideConfig = new Configuration(); 270 271 /** 272 * The display to apply to all resources associated with the token this instance is based 273 * on. 274 */ 275 public int overrideDisplayId; 276 277 /** List of {@link ActivityResource} associated with the token this instance is based on. */ 278 public final ArrayList<ActivityResource> activityResources = new ArrayList<>(); 279 280 public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>(); 281 282 @UnsupportedAppUsage ActivityResources()283 private ActivityResources() {} 284 285 /** Returns the number of live resource references within {@code activityResources}. */ countLiveReferences()286 public int countLiveReferences() { 287 int count = 0; 288 for (int i = 0; i < activityResources.size(); i++) { 289 WeakReference<Resources> resources = activityResources.get(i).resources; 290 if (resources != null && resources.get() != null) { 291 count++; 292 } 293 } 294 return count; 295 } 296 } 297 298 /** 299 * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information 300 * about how this resource expects its configuration to differ from the token's. 301 * 302 * @see ActivityResources 303 */ 304 // TODO: Ideally this class should be called something token related, like TokenBasedResource. 305 private static class ActivityResource { 306 /** 307 * The override configuration applied on top of the token's override config for this 308 * resource. 309 */ 310 public final Configuration overrideConfig = new Configuration(); 311 312 /** 313 * If non-null this resource expects its configuration to override the display from the 314 * token's configuration. 315 * 316 * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) 317 */ 318 @Nullable 319 public Integer overrideDisplayId; 320 321 @Nullable 322 public WeakReference<Resources> resources; 323 ActivityResource()324 private ActivityResource() {} 325 } 326 327 /** 328 * Each Activity or WindowToken may has a base override configuration that is applied to each 329 * Resources object, which in turn may have their own override configuration specified. 330 */ 331 @UnsupportedAppUsage 332 private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = 333 new WeakHashMap<>(); 334 335 /** 336 * Callback implementation for handling updates to Resources objects. 337 */ 338 private final UpdateHandler mUpdateCallbacks = new UpdateHandler(); 339 340 /** 341 * The set of APK paths belonging to this process. This is used to disable incremental 342 * installation crash protections on these APKs so the app either behaves as expects or crashes. 343 */ 344 private final ArraySet<String> mApplicationOwnedApks = new ArraySet<>(); 345 346 @UnsupportedAppUsage ResourcesManager()347 public ResourcesManager() { 348 } 349 350 /** 351 * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager 352 * instance. 353 */ 354 @UnsupportedAppUsage 355 @VisibleForTesting setInstance(ResourcesManager resourcesManager)356 public static ResourcesManager setInstance(ResourcesManager resourcesManager) { 357 synchronized (ResourcesManager.class) { 358 ResourcesManager oldResourceManager = sResourcesManager; 359 sResourcesManager = resourcesManager; 360 return oldResourceManager; 361 } 362 363 } 364 365 @UnsupportedAppUsage getInstance()366 public static ResourcesManager getInstance() { 367 synchronized (ResourcesManager.class) { 368 if (sResourcesManager == null) { 369 sResourcesManager = new ResourcesManager(); 370 } 371 return sResourcesManager; 372 } 373 } 374 375 /** 376 * Invalidate and destroy any resources that reference content under the 377 * given filesystem path. Typically used when unmounting a storage device to 378 * try as hard as possible to release any open FDs. 379 */ invalidatePath(String path)380 public void invalidatePath(String path) { 381 final List<ResourcesImpl> implsToFlush = new ArrayList<>(); 382 synchronized (mLock) { 383 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 384 final ResourcesKey key = mResourceImpls.keyAt(i); 385 if (key.isPathReferenced(path)) { 386 ResourcesImpl resImpl = mResourceImpls.removeAt(i).get(); 387 if (resImpl != null) { 388 implsToFlush.add(resImpl); 389 } 390 } 391 } 392 } 393 for (int i = 0; i < implsToFlush.size(); i++) { 394 implsToFlush.get(i).flushLayoutCache(); 395 } 396 final List<ApkAssets> assetsToClose = new ArrayList<>(); 397 synchronized (mCachedApkAssets) { 398 for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) { 399 final ApkKey key = mCachedApkAssets.keyAt(i); 400 if (key.path.equals(path)) { 401 final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i); 402 final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null; 403 if (apkAssets != null) { 404 assetsToClose.add(apkAssets); 405 } 406 } 407 } 408 } 409 for (int i = 0; i < assetsToClose.size(); i++) { 410 assetsToClose.get(i).close(); 411 } 412 Log.i(TAG, 413 "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path); 414 } 415 getConfiguration()416 public Configuration getConfiguration() { 417 return mResConfiguration; 418 } 419 420 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getDisplayMetrics()421 public DisplayMetrics getDisplayMetrics() { 422 return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 423 } 424 425 /** 426 * Protected so that tests can override and returns something a fixed value. 427 */ 428 @VisibleForTesting getDisplayMetrics(int displayId, DisplayAdjustments da)429 protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) { 430 final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance(); 431 final DisplayMetrics dm = new DisplayMetrics(); 432 final DisplayInfo displayInfo = displayManagerGlobal != null 433 ? displayManagerGlobal.getDisplayInfo(displayId) : null; 434 if (displayInfo != null) { 435 displayInfo.getAppMetrics(dm, da); 436 } else { 437 dm.setToDefaults(); 438 } 439 return dm; 440 } 441 442 /** 443 * Like getDisplayMetrics, but will adjust the result based on the display information in 444 * config. This is used to make sure that the global configuration matches the activity's 445 * apparent display. 446 */ getDisplayMetrics(Configuration config)447 private DisplayMetrics getDisplayMetrics(Configuration config) { 448 final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance(); 449 final DisplayMetrics dm = new DisplayMetrics(); 450 final DisplayInfo displayInfo = displayManagerGlobal != null 451 ? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null; 452 if (displayInfo != null) { 453 displayInfo.getAppMetrics(dm, 454 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config); 455 } else { 456 dm.setToDefaults(); 457 } 458 return dm; 459 } 460 applyDisplayMetricsToConfiguration(@onNull DisplayMetrics dm, @NonNull Configuration config)461 private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm, 462 @NonNull Configuration config) { 463 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 464 config.densityDpi = dm.densityDpi; 465 config.screenWidthDp = (int) (dm.widthPixels / dm.density + 0.5f); 466 config.screenHeightDp = (int) (dm.heightPixels / dm.density + 0.5f); 467 int sl = Configuration.resetScreenLayout(config.screenLayout); 468 if (dm.widthPixels > dm.heightPixels) { 469 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 470 config.screenLayout = Configuration.reduceScreenLayout(sl, 471 config.screenWidthDp, config.screenHeightDp); 472 } else { 473 config.orientation = Configuration.ORIENTATION_PORTRAIT; 474 config.screenLayout = Configuration.reduceScreenLayout(sl, 475 config.screenHeightDp, config.screenWidthDp); 476 } 477 config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp); 478 config.compatScreenWidthDp = config.screenWidthDp; 479 config.compatScreenHeightDp = config.screenHeightDp; 480 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 481 } 482 applyCompatConfiguration(int displayDensity, @NonNull Configuration compatConfiguration)483 public boolean applyCompatConfiguration(int displayDensity, 484 @NonNull Configuration compatConfiguration) { 485 synchronized (mLock) { 486 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 487 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 488 return true; 489 } 490 return false; 491 } 492 } 493 494 /** 495 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 496 * available. 497 * 498 * @param displayId display Id. 499 * @param resources The {@link Resources} backing the display adjustments. 500 */ getAdjustedDisplay(final int displayId, Resources resources)501 public Display getAdjustedDisplay(final int displayId, Resources resources) { 502 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 503 if (dm == null) { 504 // may be null early in system startup 505 return null; 506 } 507 return dm.getCompatibleDisplay(displayId, resources); 508 } 509 510 /** 511 * Initializes the set of APKs owned by the application running in this process. 512 */ initializeApplicationPaths(@onNull String sourceDir, @Nullable String[] splitDirs)513 public void initializeApplicationPaths(@NonNull String sourceDir, 514 @Nullable String[] splitDirs) { 515 synchronized (mLock) { 516 if (mApplicationOwnedApks.isEmpty()) { 517 addApplicationPathsLocked(sourceDir, splitDirs); 518 } 519 } 520 } 521 522 /** 523 * Updates the set of APKs owned by the application running in this process. 524 * 525 * This method only appends to the set of APKs owned by this process because the previous APKs 526 * paths still belong to the application running in this process. 527 */ addApplicationPathsLocked(@onNull String sourceDir, @Nullable String[] splitDirs)528 private void addApplicationPathsLocked(@NonNull String sourceDir, 529 @Nullable String[] splitDirs) { 530 mApplicationOwnedApks.add(sourceDir); 531 if (splitDirs != null) { 532 mApplicationOwnedApks.addAll(Arrays.asList(splitDirs)); 533 } 534 } 535 overlayPathToIdmapPath(String path)536 private static String overlayPathToIdmapPath(String path) { 537 return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; 538 } 539 540 /** 541 * Loads the ApkAssets object for the passed key, or picks the one from the cache if available. 542 */ loadApkAssets(@onNull final ApkKey key)543 public @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { 544 ApkAssets apkAssets; 545 546 // Optimistically check if this ApkAssets exists somewhere else. 547 final WeakReference<ApkAssets> apkAssetsRef; 548 synchronized (mCachedApkAssets) { 549 apkAssetsRef = mCachedApkAssets.get(key); 550 } 551 if (apkAssetsRef != null) { 552 apkAssets = apkAssetsRef.get(); 553 if (apkAssets != null && apkAssets.isUpToDate()) { 554 return apkAssets; 555 } 556 } 557 558 int flags = 0; 559 if (key.sharedLib) { 560 flags |= ApkAssets.PROPERTY_DYNAMIC; 561 } 562 if (mApplicationOwnedApks.contains(key.path)) { 563 flags |= ApkAssets.PROPERTY_DISABLE_INCREMENTAL_HARDENING; 564 } 565 if (key.overlay) { 566 apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), flags); 567 } else { 568 apkAssets = ApkAssets.loadFromPath(key.path, flags); 569 } 570 571 synchronized (mCachedApkAssets) { 572 mCachedApkAssets.put(key, new WeakReference<>(apkAssets)); 573 } 574 575 return apkAssets; 576 } 577 578 /** 579 * Retrieves a list of apk keys representing the ApkAssets that should be loaded for 580 * AssetManagers mapped to the {@param key}. 581 */ extractApkKeys(@onNull final ResourcesKey key)582 private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) { 583 final ArrayList<ApkKey> apkKeys = new ArrayList<>(); 584 585 // resDir can be null if the 'android' package is creating a new Resources object. 586 // This is fine, since each AssetManager automatically loads the 'android' package 587 // already. 588 if (key.mResDir != null) { 589 apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/)); 590 } 591 592 if (key.mSplitResDirs != null) { 593 for (final String splitResDir : key.mSplitResDirs) { 594 apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/)); 595 } 596 } 597 598 if (key.mLibDirs != null) { 599 for (final String libDir : key.mLibDirs) { 600 // Avoid opening files we know do not have resources, like code-only .jar files. 601 if (libDir.endsWith(".apk")) { 602 apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/)); 603 } 604 } 605 } 606 607 if (key.mOverlayPaths != null) { 608 for (final String idmapPath : key.mOverlayPaths) { 609 apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/)); 610 } 611 } 612 613 return apkKeys; 614 } 615 616 /** 617 * Creates an AssetManager from the paths within the ResourcesKey. 618 * 619 * This can be overridden in tests so as to avoid creating a real AssetManager with 620 * real APK paths. 621 * @param key The key containing the resource paths to add to the AssetManager. 622 * @return a new AssetManager. 623 */ 624 @VisibleForTesting 625 @UnsupportedAppUsage createAssetManager(@onNull final ResourcesKey key)626 protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { 627 return createAssetManager(key, /* apkSupplier */ null); 628 } 629 630 /** 631 * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets 632 * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using 633 * {@link #loadApkAssets(ApkKey)}. 634 */ 635 636 @VisibleForTesting 637 @UnsupportedAppUsage createAssetManager(@onNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)638 protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key, 639 @Nullable ApkAssetsSupplier apkSupplier) { 640 final AssetManager.Builder builder = new AssetManager.Builder().setNoInit(); 641 642 final ArrayList<ApkKey> apkKeys = extractApkKeys(key); 643 for (int i = 0, n = apkKeys.size(); i < n; i++) { 644 final ApkKey apkKey = apkKeys.get(i); 645 try { 646 builder.addApkAssets( 647 (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey)); 648 } catch (IOException e) { 649 if (apkKey.overlay) { 650 Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e); 651 } else if (apkKey.sharedLib) { 652 Log.w(TAG, String.format( 653 "asset path '%s' does not exist or contains no resources", 654 apkKey.path), e); 655 } else { 656 Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e); 657 return null; 658 } 659 } 660 } 661 662 if (key.mLoaders != null) { 663 for (final ResourcesLoader loader : key.mLoaders) { 664 builder.addLoader(loader); 665 } 666 } 667 668 return builder.build(); 669 } 670 countLiveReferences(Collection<WeakReference<T>> collection)671 private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { 672 int count = 0; 673 for (WeakReference<T> ref : collection) { 674 final T value = ref != null ? ref.get() : null; 675 if (value != null) { 676 count++; 677 } 678 } 679 return count; 680 } 681 682 /** 683 * @hide 684 */ dump(String prefix, PrintWriter printWriter)685 public void dump(String prefix, PrintWriter printWriter) { 686 final int references; 687 final int resImpls; 688 synchronized (mLock) { 689 int refs = countLiveReferences(mResourceReferences); 690 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 691 refs += activityResources.countLiveReferences(); 692 } 693 references = refs; 694 resImpls = countLiveReferences(mResourceImpls.values()); 695 } 696 final int liveAssets; 697 synchronized (mCachedApkAssets) { 698 liveAssets = countLiveReferences(mCachedApkAssets.values()); 699 } 700 701 final var pw = new IndentingPrintWriter(printWriter, " "); 702 for (int i = 0; i < prefix.length() / 2; i++) { 703 pw.increaseIndent(); 704 } 705 pw.println("ResourcesManager:"); 706 pw.increaseIndent(); 707 pw.print("total apks: "); 708 pw.println(liveAssets); 709 pw.print("resources: "); 710 pw.println(references); 711 pw.print("resource impls: "); 712 pw.println(resImpls); 713 } 714 generateConfig(@onNull ResourcesKey key)715 private Configuration generateConfig(@NonNull ResourcesKey key) { 716 Configuration config; 717 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 718 if (hasOverrideConfig) { 719 config = new Configuration(getConfiguration()); 720 config.updateFrom(key.mOverrideConfiguration); 721 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 722 } else { 723 config = getConfiguration(); 724 } 725 return config; 726 } 727 generateDisplayId(@onNull ResourcesKey key)728 private int generateDisplayId(@NonNull ResourcesKey key) { 729 return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId; 730 } 731 createResourcesImpl(@onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)732 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key, 733 @Nullable ApkAssetsSupplier apkSupplier) { 734 final AssetManager assets = createAssetManager(key, apkSupplier); 735 if (assets == null) { 736 return null; 737 } 738 739 final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); 740 daj.setCompatibilityInfo(key.mCompatInfo); 741 742 final Configuration config = generateConfig(key); 743 final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); 744 final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); 745 746 if (DEBUG) { 747 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 748 } 749 return impl; 750 } 751 752 /** 753 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 754 * 755 * @param key The key to match. 756 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 757 */ findResourcesImplForKeyLocked(@onNull ResourcesKey key)758 private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) { 759 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 760 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 761 if (impl != null && impl.getAssets().isUpToDate()) { 762 return impl; 763 } 764 return null; 765 } 766 767 /** 768 * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or 769 * creates a new one and caches it for future use. 770 * @param key The key to match. 771 * @return a ResourcesImpl object matching the key. 772 */ findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)773 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( 774 @NonNull ResourcesKey key) { 775 return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null); 776 } 777 778 /** 779 * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to 780 * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl. 781 */ findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)782 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( 783 @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { 784 ResourcesImpl impl = findResourcesImplForKeyLocked(key); 785 // ResourcesImpl also need to be recreated if its shared library hash is not up-to-date. 786 if (impl == null || impl.getAppliedSharedLibsHash() != mSharedLibAssetsMap.size()) { 787 impl = createResourcesImpl(key, apkSupplier); 788 if (impl != null) { 789 mResourceImpls.put(key, new WeakReference<>(impl)); 790 } 791 } 792 return impl; 793 } 794 795 /** 796 * Find the ResourcesKey that this ResourcesImpl object is associated with. 797 * @return the ResourcesKey or null if none was found. 798 */ findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)799 private @Nullable ResourcesKey findKeyForResourceImplLocked( 800 @NonNull ResourcesImpl resourceImpl) { 801 int refCount = mResourceImpls.size(); 802 for (int i = 0; i < refCount; i++) { 803 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 804 if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) { 805 return mResourceImpls.keyAt(i); 806 } 807 } 808 return null; 809 } 810 811 /** 812 * Check if activity resources have same override config as the provided on. 813 * @param activityToken The Activity that resources should be associated with. 814 * @param overrideConfig The override configuration to be checked for equality with. 815 * @return true if activity resources override config matches the provided one or they are both 816 * null, false otherwise. 817 */ isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)818 public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken, 819 @Nullable Configuration overrideConfig) { 820 synchronized (mLock) { 821 final ActivityResources activityResources 822 = activityToken != null ? mActivityResourceReferences.get(activityToken) : null; 823 if (activityResources == null) { 824 return overrideConfig == null; 825 } else { 826 // The two configurations must either be equal or publicly equivalent to be 827 // considered the same. 828 return Objects.equals(activityResources.overrideConfig, overrideConfig) 829 || (overrideConfig != null && activityResources.overrideConfig != null 830 && 0 == overrideConfig.diffPublicOnly( 831 activityResources.overrideConfig)); 832 } 833 } 834 } 835 getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)836 private ActivityResources getOrCreateActivityResourcesStructLocked( 837 @NonNull IBinder activityToken) { 838 ActivityResources activityResources = mActivityResourceReferences.get(activityToken); 839 if (activityResources == null) { 840 activityResources = new ActivityResources(); 841 mActivityResourceReferences.put(activityToken, activityResources); 842 } 843 return activityResources; 844 } 845 846 @Nullable findResourcesForActivityLocked(@onNull IBinder targetActivityToken, @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader)847 private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken, 848 @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) { 849 ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 850 targetActivityToken); 851 852 final int size = activityResources.activityResources.size(); 853 for (int index = 0; index < size; index++) { 854 ActivityResource activityResource = activityResources.activityResources.get(index); 855 Resources resources = activityResource.resources.get(); 856 ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked( 857 resources.getImpl()); 858 859 if (key != null 860 && Objects.equals(resources.getClassLoader(), targetClassLoader) 861 && Objects.equals(key, targetKey)) { 862 return resources; 863 } 864 } 865 866 return null; 867 } 868 869 @NonNull createResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)870 private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken, 871 @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, 872 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, 873 @NonNull CompatibilityInfo compatInfo) { 874 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 875 activityToken); 876 cleanupReferences(activityResources.activityResources, 877 activityResources.activityResourcesQueue, 878 (r) -> r.resources); 879 880 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) 881 : new Resources(classLoader); 882 resources.setImpl(impl); 883 resources.setCallbacks(mUpdateCallbacks); 884 885 ActivityResource activityResource = new ActivityResource(); 886 activityResource.resources = new WeakReference<>(resources, 887 activityResources.activityResourcesQueue); 888 activityResource.overrideConfig.setTo(initialOverrideConfig); 889 activityResource.overrideDisplayId = overrideDisplayId; 890 activityResources.activityResources.add(activityResource); 891 if (DEBUG) { 892 Slog.d(TAG, "- creating new ref=" + resources); 893 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 894 } 895 return resources; 896 } 897 createResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)898 private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader, 899 @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) { 900 cleanupReferences(mResourceReferences, mResourcesReferencesQueue); 901 902 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) 903 : new Resources(classLoader); 904 resources.setImpl(impl); 905 resources.setCallbacks(mUpdateCallbacks); 906 mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue)); 907 if (DEBUG) { 908 Slog.d(TAG, "- creating new ref=" + resources); 909 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 910 } 911 return resources; 912 } 913 914 /** 915 * Creates base resources for a binder token. Calls to 916 * 917 * {@link #getResources(IBinder, String, String[], String[], String[], String[], Integer, 918 * Configuration, CompatibilityInfo, ClassLoader, List)} with the same binder token will have 919 * their override configurations merged with the one specified here. 920 * 921 * @param token Represents an {@link Activity} or {@link WindowContext}. 922 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 923 * @param splitResDirs An array of split resource paths. Can be null. 924 * @param legacyOverlayDirs An array of overlay APK paths. Can be null. 925 * @param overlayPaths An array of overlay APK and non-APK paths. Can be null. 926 * @param libDirs An array of resource library paths. Can be null. 927 * @param displayId The ID of the display for which to create the resources. 928 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 929 * {@code null}. This provides the base override for this token. 930 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 931 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 932 * @param classLoader The class loader to use when inflating Resources. If null, the 933 * {@link ClassLoader#getSystemClassLoader()} is used. 934 * @return a Resources object from which to access resources. 935 */ createBaseTokenResources(@onNull IBinder token, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)936 public @Nullable Resources createBaseTokenResources(@NonNull IBinder token, 937 @Nullable String resDir, 938 @Nullable String[] splitResDirs, 939 @Nullable String[] legacyOverlayDirs, 940 @Nullable String[] overlayPaths, 941 @Nullable String[] libDirs, 942 int displayId, 943 @Nullable Configuration overrideConfig, 944 @NonNull CompatibilityInfo compatInfo, 945 @Nullable ClassLoader classLoader, 946 @Nullable List<ResourcesLoader> loaders) { 947 try { 948 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 949 "ResourcesManager#createBaseActivityResources"); 950 final ResourcesKey key = new ResourcesKey( 951 resDir, 952 splitResDirs, 953 combinedOverlayPaths(legacyOverlayDirs, overlayPaths), 954 libDirs, 955 displayId, 956 overrideConfig, 957 compatInfo, 958 loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); 959 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 960 961 if (DEBUG) { 962 Slog.d(TAG, "createBaseActivityResources activity=" + token 963 + " with key=" + key); 964 } 965 966 synchronized (mLock) { 967 // Force the creation of an ActivityResourcesStruct. 968 getOrCreateActivityResourcesStructLocked(token); 969 } 970 971 // Update any existing Activity Resources references. 972 updateResourcesForActivity(token, overrideConfig, displayId); 973 974 synchronized (mLock) { 975 Resources resources = findResourcesForActivityLocked(token, key, 976 classLoader); 977 if (resources != null) { 978 return resources; 979 } 980 } 981 982 // Now request an actual Resources object. 983 return createResourcesForActivity(token, key, 984 /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null, 985 classLoader, /* apkSupplier */ null); 986 } finally { 987 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 988 } 989 } 990 991 /** 992 * Rebases a key's override config on top of the Activity's base override. 993 * 994 * @param activityToken the token the supplied {@code key} is derived from. 995 * @param key the key to rebase 996 * @param overridesActivityDisplay whether this key is overriding the display from the token 997 */ rebaseKeyForActivity(IBinder activityToken, ResourcesKey key, boolean overridesActivityDisplay)998 private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key, 999 boolean overridesActivityDisplay) { 1000 synchronized (mLock) { 1001 final ActivityResources activityResources = 1002 getOrCreateActivityResourcesStructLocked(activityToken); 1003 1004 if (key.mDisplayId == INVALID_DISPLAY) { 1005 key.mDisplayId = activityResources.overrideDisplayId; 1006 } 1007 1008 Configuration config; 1009 if (key.hasOverrideConfiguration()) { 1010 config = new Configuration(activityResources.overrideConfig); 1011 config.updateFrom(key.mOverrideConfiguration); 1012 } else { 1013 config = activityResources.overrideConfig; 1014 } 1015 1016 if (overridesActivityDisplay 1017 && key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) { 1018 if (!key.hasOverrideConfiguration()) { 1019 // Make a copy to handle the case where the override config is set to defaults. 1020 config = new Configuration(config); 1021 } 1022 1023 // If this key is overriding the display from the token and the key's 1024 // window config app bounds is null we need to explicitly override this to 1025 // ensure the display adjustments are as expected. 1026 config.windowConfiguration.setAppBounds(null); 1027 } 1028 1029 key.mOverrideConfiguration.setTo(config); 1030 } 1031 } 1032 1033 /** 1034 * Rebases a key's override config with display metrics of the {@code overrideDisplay} paired 1035 * with the {code displayAdjustments}. 1036 * 1037 * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) 1038 */ rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay)1039 private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) { 1040 final Configuration temp = new Configuration(); 1041 1042 DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); 1043 daj.setCompatibilityInfo(key.mCompatInfo); 1044 1045 final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj); 1046 applyDisplayMetricsToConfiguration(dm, temp); 1047 1048 if (key.hasOverrideConfiguration()) { 1049 temp.updateFrom(key.mOverrideConfiguration); 1050 } 1051 key.mOverrideConfiguration.setTo(temp); 1052 } 1053 1054 /** 1055 * Check WeakReferences and remove any dead references so they don't pile up. 1056 */ cleanupReferences(ArrayList<WeakReference<T>> references, ReferenceQueue<T> referenceQueue)1057 private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references, 1058 ReferenceQueue<T> referenceQueue) { 1059 cleanupReferences(references, referenceQueue, Function.identity()); 1060 } 1061 1062 /** 1063 * Check WeakReferences and remove any dead references so they don't pile up. 1064 */ cleanupReferences(ArrayList<C> referenceContainers, ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction)1065 private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers, 1066 ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) { 1067 Reference<? extends T> enqueuedRef = referenceQueue.poll(); 1068 if (enqueuedRef == null) { 1069 return; 1070 } 1071 1072 final HashSet<Reference<? extends T>> deadReferences = new HashSet<>(); 1073 for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) { 1074 deadReferences.add(enqueuedRef); 1075 } 1076 1077 ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> { 1078 WeakReference<T> ref = unwrappingFunction.apply(refContainer); 1079 return ref == null || deadReferences.contains(ref); 1080 }); 1081 } 1082 1083 /** 1084 * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key} 1085 * into the supplier. This should be done while the lock is not held to prevent performing I/O 1086 * while holding the lock. 1087 */ createApkAssetsSupplierNotLocked(@onNull ResourcesKey key)1088 private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) { 1089 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 1090 "ResourcesManager#createApkAssetsSupplierNotLocked"); 1091 try { 1092 if (DEBUG && Thread.holdsLock(mLock)) { 1093 Slog.w(TAG, "Calling thread " + Thread.currentThread().getName() 1094 + " is holding mLock", new Throwable()); 1095 } 1096 1097 final ApkAssetsSupplier supplier = new ApkAssetsSupplier(); 1098 final ArrayList<ApkKey> apkKeys = extractApkKeys(key); 1099 for (int i = 0, n = apkKeys.size(); i < n; i++) { 1100 final ApkKey apkKey = apkKeys.get(i); 1101 try { 1102 supplier.load(apkKey); 1103 } catch (IOException e) { 1104 Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e); 1105 } 1106 } 1107 return supplier; 1108 } finally { 1109 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1110 } 1111 } 1112 1113 /** 1114 * Creates a Resources object set with a ResourcesImpl object matching the given key. 1115 * 1116 * @param key The key describing the parameters of the ResourcesImpl object. 1117 * @param classLoader The classloader to use for the Resources object. 1118 * If null, {@link ClassLoader#getSystemClassLoader()} is used. 1119 * @return A Resources object that gets updated when 1120 * {@link #applyConfigurationToResources(Configuration, CompatibilityInfo)} 1121 * is called. 1122 */ 1123 @Nullable createResources(@onNull ResourcesKey key, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1124 private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader, 1125 @Nullable ApkAssetsSupplier apkSupplier) { 1126 synchronized (mLock) { 1127 if (DEBUG) { 1128 Throwable here = new Throwable(); 1129 here.fillInStackTrace(); 1130 Slog.w(TAG, "!! Create resources for key=" + key, here); 1131 } 1132 1133 ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier); 1134 if (resourcesImpl == null) { 1135 return null; 1136 } 1137 1138 return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); 1139 } 1140 } 1141 1142 @Nullable createResourcesForActivity(@onNull IBinder activityToken, @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1143 private Resources createResourcesForActivity(@NonNull IBinder activityToken, 1144 @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig, 1145 @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, 1146 @Nullable ApkAssetsSupplier apkSupplier) { 1147 synchronized (mLock) { 1148 if (DEBUG) { 1149 Throwable here = new Throwable(); 1150 here.fillInStackTrace(); 1151 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 1152 } 1153 1154 ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier); 1155 if (resourcesImpl == null) { 1156 return null; 1157 } 1158 1159 return createResourcesForActivityLocked(activityToken, initialOverrideConfig, 1160 overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo); 1161 } 1162 } 1163 1164 /** 1165 * Gets or creates a new Resources object associated with the IBinder token. References returned 1166 * by this method live as long as the Activity, meaning they can be cached and used by the 1167 * Activity even after a configuration change. If any other parameter is changed 1168 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 1169 * is updated and handed back to the caller. However, changing the class loader will result in a 1170 * new Resources object. 1171 * <p/> 1172 * If activityToken is null, a cached Resources object will be returned if it matches the 1173 * input parameters. Otherwise a new Resources object that satisfies these parameters is 1174 * returned. 1175 * 1176 * @param activityToken Represents an Activity. If null, global resources are assumed. 1177 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 1178 * @param splitResDirs An array of split resource paths. Can be null. 1179 * @param legacyOverlayDirs An array of overlay APK paths. Can be null. 1180 * @param overlayPaths An array of overlay APK and non-APK paths. Can be null. 1181 * @param libDirs An array of resource library paths. Can be null. 1182 * @param overrideDisplayId The ID of the display for which the returned Resources should be 1183 * based. This will cause display-based configuration properties to override those of the base 1184 * Resources for the {@code activityToken}, or the global configuration if {@code activityToken} 1185 * is null. 1186 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 1187 * null. Mostly used with Activities that are in multi-window which may override width and 1188 * height properties from the base config. 1189 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 1190 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 1191 * @param classLoader The class loader to use when inflating Resources. If null, the 1192 * {@link ClassLoader#getSystemClassLoader()} is used. 1193 * @return a Resources object from which to access resources. 1194 */ 1195 @Nullable getResources( @ullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, @Nullable Integer overrideDisplayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)1196 public Resources getResources( 1197 @Nullable IBinder activityToken, 1198 @Nullable String resDir, 1199 @Nullable String[] splitResDirs, 1200 @Nullable String[] legacyOverlayDirs, 1201 @Nullable String[] overlayPaths, 1202 @Nullable String[] libDirs, 1203 @Nullable Integer overrideDisplayId, 1204 @Nullable Configuration overrideConfig, 1205 @NonNull CompatibilityInfo compatInfo, 1206 @Nullable ClassLoader classLoader, 1207 @Nullable List<ResourcesLoader> loaders) { 1208 try { 1209 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 1210 final ResourcesKey key = new ResourcesKey( 1211 resDir, 1212 splitResDirs, 1213 combinedOverlayPaths(legacyOverlayDirs, overlayPaths), 1214 libDirs, 1215 overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY, 1216 overrideConfig, 1217 compatInfo, 1218 loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); 1219 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 1220 1221 // Preload the ApkAssets required by the key to prevent performing heavy I/O while the 1222 // ResourcesManager lock is held. 1223 final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key); 1224 1225 if (overrideDisplayId != null) { 1226 rebaseKeyForDisplay(key, overrideDisplayId); 1227 } 1228 1229 Resources resources; 1230 if (activityToken != null) { 1231 Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration); 1232 rebaseKeyForActivity(activityToken, key, overrideDisplayId != null); 1233 resources = createResourcesForActivity(activityToken, key, initialOverrideConfig, 1234 overrideDisplayId, classLoader, assetsSupplier); 1235 } else { 1236 resources = createResources(key, classLoader, assetsSupplier); 1237 } 1238 return resources; 1239 } finally { 1240 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1241 } 1242 } 1243 1244 /** 1245 * Updates an Activity's Resources object with overrideConfig. The Resources object 1246 * that was previously returned by {@link #getResources(IBinder, String, String[], String[], 1247 * String[], String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still 1248 * valid and will have the updated configuration. 1249 * 1250 * @param activityToken The Activity token. 1251 * @param overrideConfig The configuration override to update. 1252 * @param displayId Id of the display where activity currently resides. 1253 */ updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig, int displayId)1254 public void updateResourcesForActivity(@NonNull IBinder activityToken, 1255 @Nullable Configuration overrideConfig, int displayId) { 1256 try { 1257 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 1258 "ResourcesManager#updateResourcesForActivity"); 1259 if (displayId == INVALID_DISPLAY) { 1260 throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY"); 1261 } 1262 synchronized (mLock) { 1263 final ActivityResources activityResources = 1264 getOrCreateActivityResourcesStructLocked(activityToken); 1265 1266 boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId; 1267 if (Objects.equals(activityResources.overrideConfig, overrideConfig) 1268 && !movedToDifferentDisplay) { 1269 // They are the same and no change of display id, no work to do. 1270 return; 1271 } 1272 1273 // Grab a copy of the old configuration so we can create the delta's of each 1274 // Resources object associated with this Activity. 1275 final Configuration oldConfig = new Configuration(activityResources.overrideConfig); 1276 1277 // Update the Activity's base override. 1278 if (overrideConfig != null) { 1279 activityResources.overrideConfig.setTo(overrideConfig); 1280 } else { 1281 activityResources.overrideConfig.unset(); 1282 } 1283 1284 // Update the Activity's override display id. 1285 activityResources.overrideDisplayId = displayId; 1286 1287 // If a application info update was scheduled to occur in this process but has not 1288 // occurred yet, apply it now so the resources objects will have updated paths if 1289 // the assets sequence changed. 1290 applyAllPendingAppInfoUpdates(); 1291 1292 if (DEBUG) { 1293 Throwable here = new Throwable(); 1294 here.fillInStackTrace(); 1295 Slog.d(TAG, "updating resources override for activity=" + activityToken 1296 + " from oldConfig=" 1297 + Configuration.resourceQualifierString(oldConfig) 1298 + " to newConfig=" 1299 + Configuration.resourceQualifierString( 1300 activityResources.overrideConfig) + " displayId=" + displayId, 1301 here); 1302 } 1303 1304 1305 // Rebase each Resources associated with this Activity. 1306 final int refCount = activityResources.activityResources.size(); 1307 for (int i = 0; i < refCount; i++) { 1308 final ActivityResource activityResource = 1309 activityResources.activityResources.get(i); 1310 1311 final Resources resources = activityResource.resources.get(); 1312 if (resources == null) { 1313 continue; 1314 } 1315 1316 final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource, 1317 overrideConfig, displayId); 1318 if (newKey == null) { 1319 continue; 1320 } 1321 1322 // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl 1323 // constructions. 1324 final ResourcesImpl resourcesImpl = 1325 findOrCreateResourcesImplForKeyLocked(newKey); 1326 if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { 1327 // Set the ResourcesImpl, updating it for all users of this Resources 1328 // object. 1329 resources.setImpl(resourcesImpl); 1330 } 1331 } 1332 } 1333 } finally { 1334 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1335 } 1336 } 1337 1338 /** 1339 * Rebases an updated override config over any old override config and returns the new one 1340 * that an Activity's Resources should be set to. 1341 */ 1342 @Nullable rebaseActivityOverrideConfig(@onNull ActivityResource activityResource, @Nullable Configuration newOverrideConfig, int displayId)1343 private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource, 1344 @Nullable Configuration newOverrideConfig, int displayId) { 1345 final Resources resources = activityResource.resources.get(); 1346 if (resources == null) { 1347 return null; 1348 } 1349 1350 // Extract the ResourcesKey that was last used to create the Resources for this 1351 // activity. 1352 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 1353 if (oldKey == null) { 1354 Slog.e(TAG, "can't find ResourcesKey for resources impl=" 1355 + resources.getImpl()); 1356 return null; 1357 } 1358 1359 // Build the new override configuration for this ResourcesKey. 1360 final Configuration rebasedOverrideConfig = new Configuration(); 1361 if (newOverrideConfig != null) { 1362 rebasedOverrideConfig.setTo(newOverrideConfig); 1363 } 1364 1365 final Integer overrideDisplayId = activityResource.overrideDisplayId; 1366 if (overrideDisplayId != null) { 1367 DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig); 1368 displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig); 1369 displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo); 1370 1371 DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments); 1372 applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig); 1373 } 1374 1375 final boolean hasOverrideConfig = 1376 !activityResource.overrideConfig.equals(Configuration.EMPTY); 1377 if (hasOverrideConfig) { 1378 rebasedOverrideConfig.updateFrom(activityResource.overrideConfig); 1379 } 1380 1381 if (activityResource.overrideDisplayId != null 1382 && activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) { 1383 // If this activity resource is overriding the display from the token and the key's 1384 // window config app bounds is null we need to explicitly override this to 1385 // ensure the display adjustments are as expected. 1386 rebasedOverrideConfig.windowConfiguration.setAppBounds(null); 1387 } 1388 1389 // Ensure the new key keeps the expected override display instead of the new token display. 1390 displayId = overrideDisplayId != null ? overrideDisplayId : displayId; 1391 1392 // Create the new ResourcesKey with the rebased override config. 1393 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, 1394 oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs, 1395 displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders); 1396 1397 if (DEBUG) { 1398 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey 1399 + " to newKey=" + newKey + ", displayId=" + displayId); 1400 } 1401 1402 return newKey; 1403 } 1404 appendPendingAppInfoUpdate(@onNull String[] oldSourceDirs, @NonNull ApplicationInfo appInfo)1405 public void appendPendingAppInfoUpdate(@NonNull String[] oldSourceDirs, 1406 @NonNull ApplicationInfo appInfo) { 1407 synchronized (mLock) { 1408 if (mPendingAppInfoUpdates == null) { 1409 mPendingAppInfoUpdates = new ArrayList<>(); 1410 } 1411 // Clear previous app info changes for a package to prevent multiple ResourcesImpl 1412 // recreations when the recreation caused by this update completely overrides the 1413 // previous pending changes. 1414 for (int i = mPendingAppInfoUpdates.size() - 1; i >= 0; i--) { 1415 if (ArrayUtils.containsAll(oldSourceDirs, mPendingAppInfoUpdates.get(i).first)) { 1416 mPendingAppInfoUpdates.remove(i); 1417 } 1418 } 1419 mPendingAppInfoUpdates.add(new Pair<>(oldSourceDirs, appInfo)); 1420 } 1421 } 1422 applyAllPendingAppInfoUpdates()1423 public final void applyAllPendingAppInfoUpdates() { 1424 synchronized (mLock) { 1425 if (mPendingAppInfoUpdates != null) { 1426 for (int i = 0, n = mPendingAppInfoUpdates.size(); i < n; i++) { 1427 final Pair<String[], ApplicationInfo> appInfo = mPendingAppInfoUpdates.get(i); 1428 applyNewResourceDirsLocked(appInfo.first, appInfo.second); 1429 } 1430 mPendingAppInfoUpdates = null; 1431 } 1432 } 1433 } 1434 applyConfigurationToResources(@onNull Configuration config, @Nullable CompatibilityInfo compat)1435 public final boolean applyConfigurationToResources(@NonNull Configuration config, 1436 @Nullable CompatibilityInfo compat) { 1437 synchronized (mLock) { 1438 try { 1439 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 1440 "ResourcesManager#applyConfigurationToResources"); 1441 1442 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 1443 if (DEBUG || DEBUG_CONFIGURATION) { 1444 Slog.v(TAG, "Skipping new config: curSeq=" 1445 + mResConfiguration.seq + ", newSeq=" + config.seq); 1446 } 1447 return false; 1448 } 1449 1450 int changes = mResConfiguration.updateFrom(config); 1451 if (compat != null && (mResCompatibilityInfo == null 1452 || !mResCompatibilityInfo.equals(compat))) { 1453 mResCompatibilityInfo = compat; 1454 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 1455 | ActivityInfo.CONFIG_SCREEN_SIZE 1456 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 1457 } 1458 1459 // If a application info update was scheduled to occur in this process but has not 1460 // occurred yet, apply it now so the resources objects will have updated paths when 1461 // the assets sequence changes. 1462 if ((changes & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) { 1463 applyAllPendingAppInfoUpdates(); 1464 } 1465 1466 final DisplayMetrics displayMetrics = getDisplayMetrics(config); 1467 Resources.updateSystemConfiguration(config, displayMetrics, compat); 1468 1469 ApplicationPackageManager.configurationChanged(); 1470 1471 Configuration tmpConfig = new Configuration(); 1472 1473 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 1474 ResourcesKey key = mResourceImpls.keyAt(i); 1475 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1476 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null; 1477 if (r != null) { 1478 applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r); 1479 } else { 1480 mResourceImpls.removeAt(i); 1481 } 1482 } 1483 1484 return changes != 0; 1485 } finally { 1486 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1487 } 1488 } 1489 } 1490 applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat, Configuration tmpConfig, ResourcesKey key, ResourcesImpl resourcesImpl)1491 private void applyConfigurationToResourcesLocked(@NonNull Configuration config, 1492 @Nullable CompatibilityInfo compat, Configuration tmpConfig, 1493 ResourcesKey key, ResourcesImpl resourcesImpl) { 1494 if (DEBUG || DEBUG_CONFIGURATION) { 1495 Slog.v(TAG, "Changing resources " 1496 + resourcesImpl + " config to: " + config); 1497 } 1498 1499 tmpConfig.setTo(config); 1500 if (key.hasOverrideConfiguration()) { 1501 tmpConfig.updateFrom(key.mOverrideConfiguration); 1502 } 1503 1504 // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update 1505 // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the 1506 // update internally. 1507 DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments(); 1508 if (compat != null) { 1509 daj = new DisplayAdjustments(daj); 1510 daj.setCompatibilityInfo(compat); 1511 } 1512 daj.setConfiguration(tmpConfig); 1513 DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj); 1514 1515 resourcesImpl.updateConfiguration(tmpConfig, dm, compat); 1516 } 1517 1518 /** 1519 * Appends the library asset path to any ResourcesImpl object that contains the main 1520 * assetPath. 1521 * @param assetPath The main asset path for which to add the library asset path. 1522 * @param libAsset The library asset path to add. 1523 */ 1524 @UnsupportedAppUsage appendLibAssetForMainAssetPath(String assetPath, String libAsset)1525 public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { 1526 appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset }); 1527 } 1528 1529 /** 1530 * Appends the library asset paths to any ResourcesImpl object that contains the main 1531 * assetPath. 1532 * @param assetPath The main asset path for which to add the library asset path. 1533 * @param libAssets The library asset paths to add. 1534 */ appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets)1535 public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) { 1536 synchronized (mLock) { 1537 // Record which ResourcesImpl need updating 1538 // (and what ResourcesKey they should update to). 1539 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 1540 1541 final int implCount = mResourceImpls.size(); 1542 for (int i = 0; i < implCount; i++) { 1543 final ResourcesKey key = mResourceImpls.keyAt(i); 1544 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1545 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 1546 if (impl != null && Objects.equals(key.mResDir, assetPath)) { 1547 String[] newLibAssets = key.mLibDirs; 1548 for (String libAsset : libAssets) { 1549 newLibAssets = 1550 ArrayUtils.appendElement(String.class, newLibAssets, libAsset); 1551 } 1552 1553 if (!Arrays.equals(newLibAssets, key.mLibDirs)) { 1554 updatedResourceKeys.put(impl, new ResourcesKey( 1555 key.mResDir, 1556 key.mSplitResDirs, 1557 key.mOverlayPaths, 1558 newLibAssets, 1559 key.mDisplayId, 1560 key.mOverrideConfiguration, 1561 key.mCompatInfo, 1562 key.mLoaders)); 1563 } 1564 } 1565 } 1566 1567 redirectResourcesToNewImplLocked(updatedResourceKeys); 1568 } 1569 } 1570 1571 /** 1572 * A utility class to collect resources paths into a ResourcesKey object: 1573 * - Separates the libraries and the overlays into different sets as those are loaded in 1574 * different ways. 1575 * - Allows to start with an existing original key object, and copies all non-path related 1576 * properties into the final one. 1577 * - Preserves the path order while dropping all duplicates in an efficient manner. 1578 */ 1579 private static class PathCollector { 1580 public final ResourcesKey originalKey; 1581 public final ArrayList<String> orderedLibs = new ArrayList<>(); 1582 public final ArraySet<String> libsSet = new ArraySet<>(); 1583 public final ArrayList<String> orderedOverlays = new ArrayList<>(); 1584 public final ArraySet<String> overlaysSet = new ArraySet<>(); 1585 appendNewPath(@onNull String path, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths)1586 static void appendNewPath(@NonNull String path, 1587 @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { 1588 if (uniquePaths.add(path)) { 1589 orderedPaths.add(path); 1590 } 1591 } 1592 appendAllNewPaths(@ullable String[] paths, @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths)1593 static void appendAllNewPaths(@Nullable String[] paths, 1594 @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { 1595 if (paths == null) return; 1596 for (int i = 0, size = paths.length; i < size; i++) { 1597 appendNewPath(paths[i], uniquePaths, orderedPaths); 1598 } 1599 } 1600 PathCollector(@ullable ResourcesKey original)1601 PathCollector(@Nullable ResourcesKey original) { 1602 originalKey = original; 1603 if (originalKey != null) { 1604 appendKey(originalKey); 1605 } 1606 } 1607 appendKey(@onNull ResourcesKey key)1608 public void appendKey(@NonNull ResourcesKey key) { 1609 appendAllNewPaths(key.mLibDirs, libsSet, orderedLibs); 1610 appendAllNewPaths(key.mOverlayPaths, overlaysSet, orderedOverlays); 1611 } 1612 isSameAsOriginal()1613 boolean isSameAsOriginal() { 1614 if (originalKey == null) { 1615 return orderedLibs.isEmpty() && orderedOverlays.isEmpty(); 1616 } 1617 return ((originalKey.mLibDirs == null && orderedLibs.isEmpty()) 1618 || (originalKey.mLibDirs != null 1619 && originalKey.mLibDirs.length == orderedLibs.size())) 1620 && ((originalKey.mOverlayPaths == null && orderedOverlays.isEmpty()) 1621 || (originalKey.mOverlayPaths != null 1622 && originalKey.mOverlayPaths.length == orderedOverlays.size())); 1623 } 1624 collectedKey()1625 @NonNull ResourcesKey collectedKey() { 1626 return new ResourcesKey( 1627 originalKey == null ? null : originalKey.mResDir, 1628 originalKey == null ? null : originalKey.mSplitResDirs, 1629 orderedOverlays.toArray(new String[0]), orderedLibs.toArray(new String[0]), 1630 originalKey == null ? 0 : originalKey.mDisplayId, 1631 originalKey == null ? null : originalKey.mOverrideConfiguration, 1632 originalKey == null ? null : originalKey.mCompatInfo, 1633 originalKey == null ? null : originalKey.mLoaders); 1634 } 1635 } 1636 1637 /** 1638 * Takes the original resources key and the one containing a set of library paths and overlays 1639 * to append, and combines them together. In case when the original key already contains all 1640 * those paths this function returns null, otherwise it makes a new ResourcesKey object. 1641 */ createNewResourceKeyIfNeeded( @onNull ResourcesKey original, @NonNull ResourcesKey library)1642 private @Nullable ResourcesKey createNewResourceKeyIfNeeded( 1643 @NonNull ResourcesKey original, @NonNull ResourcesKey library) { 1644 final var collector = new PathCollector(original); 1645 collector.appendKey(library); 1646 return collector.isSameAsOriginal() ? null : collector.collectedKey(); 1647 } 1648 1649 /** 1650 * Append the newly registered shared library asset paths to all existing resources objects. 1651 */ appendLibAssetsLocked(@onNull SharedLibraryAssets libAssets)1652 private void appendLibAssetsLocked(@NonNull SharedLibraryAssets libAssets) { 1653 // Record the ResourcesImpl's that need updating, and what ResourcesKey they should 1654 // update to. 1655 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 1656 final int implCount = mResourceImpls.size(); 1657 for (int i = 0; i < implCount; i++) { 1658 final ResourcesKey key = mResourceImpls.keyAt(i); 1659 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1660 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 1661 if (impl == null) { 1662 Slog.w(TAG, "Found a null ResourcesImpl, skipped."); 1663 continue; 1664 } 1665 1666 final var newKey = createNewResourceKeyIfNeeded(key, libAssets.getResourcesKey()); 1667 if (newKey != null) { 1668 updatedResourceKeys.put(impl, newKey); 1669 } 1670 } 1671 redirectAllResourcesToNewImplLocked(updatedResourceKeys); 1672 } 1673 applyNewResourceDirsLocked(@ullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo)1674 private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, 1675 @NonNull final ApplicationInfo appInfo) { 1676 try { 1677 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 1678 "ResourcesManager#applyNewResourceDirsLocked"); 1679 1680 String baseCodePath = appInfo.getBaseCodePath(); 1681 1682 final int myUid = Process.myUid(); 1683 String[] newSplitDirs = appInfo.uid == myUid 1684 ? appInfo.splitSourceDirs 1685 : appInfo.splitPublicSourceDirs; 1686 1687 // ApplicationInfo is mutable, so clone the arrays to prevent outside modification 1688 String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs); 1689 String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs, 1690 appInfo.overlayPaths); 1691 1692 if (appInfo.uid == myUid) { 1693 addApplicationPathsLocked(baseCodePath, copiedSplitDirs); 1694 } 1695 1696 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 1697 final int implCount = mResourceImpls.size(); 1698 for (int i = 0; i < implCount; i++) { 1699 final ResourcesKey key = mResourceImpls.keyAt(i); 1700 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 1701 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 1702 1703 if (impl == null) { 1704 continue; 1705 } 1706 1707 if (key.mResDir == null 1708 || key.mResDir.equals(baseCodePath) 1709 || ArrayUtils.contains(oldSourceDirs, key.mResDir)) { 1710 updatedResourceKeys.put(impl, new ResourcesKey( 1711 baseCodePath, 1712 copiedSplitDirs, 1713 copiedResourceDirs, 1714 key.mLibDirs, 1715 key.mDisplayId, 1716 key.mOverrideConfiguration, 1717 key.mCompatInfo, 1718 key.mLoaders 1719 )); 1720 } 1721 } 1722 1723 redirectResourcesToNewImplLocked(updatedResourceKeys); 1724 } finally { 1725 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1726 } 1727 } 1728 1729 /** 1730 * Creates an array with the contents of {@param overlayPaths} and the unique elements of 1731 * {@param resourceDirs}. 1732 * 1733 * {@link ApplicationInfo#resourceDirs} only contains paths of overlays APKs. 1734 * {@link ApplicationInfo#overlayPaths} was created to contain paths of overlay of varying file 1735 * formats. It also contains the contents of {@code resourceDirs} because the order of loaded 1736 * overlays matter. In case {@code resourceDirs} contains overlay APK paths that are not present 1737 * in overlayPaths (perhaps an app inserted an additional overlay path into a 1738 * {@code resourceDirs}), this method is used to combine the contents of {@code resourceDirs} 1739 * that do not exist in {@code overlayPaths}} and {@code overlayPaths}}. 1740 */ 1741 @Nullable combinedOverlayPaths(@ullable String[] resourceDirs, @Nullable String[] overlayPaths)1742 private static String[] combinedOverlayPaths(@Nullable String[] resourceDirs, 1743 @Nullable String[] overlayPaths) { 1744 if (resourceDirs == null) { 1745 return ArrayUtils.cloneOrNull(overlayPaths); 1746 } else if(overlayPaths == null) { 1747 return ArrayUtils.cloneOrNull(resourceDirs); 1748 } else { 1749 final var paths = new ArrayList<String>(overlayPaths.length + resourceDirs.length); 1750 for (final String path : overlayPaths) { 1751 paths.add(path); 1752 } 1753 for (final String path : resourceDirs) { 1754 if (!paths.contains(path)) { 1755 paths.add(path); 1756 } 1757 } 1758 return paths.toArray(new String[0]); 1759 } 1760 } 1761 redirectResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1762 private void redirectResourcesToNewImplLocked( 1763 @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { 1764 // Bail early if there is no work to do. 1765 if (updatedResourceKeys.isEmpty()) { 1766 return; 1767 } 1768 1769 // Update any references to ResourcesImpl that require reloading. 1770 final int resourcesCount = mResourceReferences.size(); 1771 for (int i = 0; i < resourcesCount; i++) { 1772 final WeakReference<Resources> ref = mResourceReferences.get(i); 1773 final Resources r = ref != null ? ref.get() : null; 1774 if (r != null) { 1775 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 1776 if (key != null) { 1777 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 1778 if (impl == null) { 1779 throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); 1780 } 1781 r.setImpl(impl); 1782 } 1783 } 1784 } 1785 1786 // Update any references to ResourcesImpl that require reloading for each Activity. 1787 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 1788 final int resCount = activityResources.activityResources.size(); 1789 for (int i = 0; i < resCount; i++) { 1790 final ActivityResource activityResource = 1791 activityResources.activityResources.get(i); 1792 final Resources r = activityResource != null 1793 ? activityResource.resources.get() : null; 1794 if (r != null) { 1795 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 1796 if (key != null) { 1797 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 1798 if (impl == null) { 1799 throw new Resources.NotFoundException( 1800 "failed to redirect ResourcesImpl"); 1801 } 1802 r.setImpl(impl); 1803 } 1804 } 1805 } 1806 } 1807 } 1808 1809 // Another redirect function which will loop through all Resources in the process, even the ones 1810 // the app created outside of the regular Android Runtime, and reload their ResourcesImpl if it 1811 // needs a shared library asset paths update. redirectAllResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1812 private void redirectAllResourcesToNewImplLocked( 1813 @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { 1814 cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue); 1815 1816 // Update any references to ResourcesImpl that require reloading. 1817 final int resourcesCount = mAllResourceReferences.size(); 1818 for (int i = 0; i < resourcesCount; i++) { 1819 final WeakReference<Resources> ref = mAllResourceReferences.get(i); 1820 final Resources r = ref != null ? ref.get() : null; 1821 if (r != null) { 1822 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 1823 if (key != null) { 1824 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 1825 if (impl == null) { 1826 throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); 1827 } 1828 r.setImpl(impl); 1829 } else { 1830 // ResourcesKey is null which means the ResourcesImpl could belong to a 1831 // Resources created by application through Resources constructor and was not 1832 // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to 1833 // have shared library asset paths appended if there are any. 1834 if (r.getImpl() != null) { 1835 final ResourcesImpl oldImpl = r.getImpl(); 1836 final AssetManager oldAssets = oldImpl.getAssets(); 1837 // ResourcesImpl constructor will help to append shared library asset paths. 1838 if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) { 1839 final ResourcesImpl newImpl = new ResourcesImpl(oldAssets, 1840 oldImpl.getMetrics(), oldImpl.getConfiguration(), 1841 oldImpl.getDisplayAdjustments()); 1842 r.setImpl(newImpl); 1843 } else { 1844 Slog.w(TAG, "Skip appending shared library asset paths for the " 1845 + "Resource as its assets are not up to date."); 1846 } 1847 } 1848 } 1849 } 1850 } 1851 } 1852 1853 /** 1854 * Returns the LocaleConfig current set 1855 */ getLocaleConfig()1856 public LocaleConfig getLocaleConfig() { 1857 return mLocaleConfig; 1858 } 1859 1860 /** 1861 * Sets the LocaleConfig of the app 1862 */ setLocaleConfig(LocaleConfig localeConfig)1863 public void setLocaleConfig(LocaleConfig localeConfig) { 1864 if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null) 1865 && !localeConfig.getSupportedLocales().isEmpty()) { 1866 mLocaleConfig = localeConfig; 1867 } 1868 } 1869 1870 private class UpdateHandler implements Resources.UpdateCallbacks { 1871 1872 /** 1873 * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources} 1874 * instance uses. 1875 */ 1876 @Override onLoadersChanged(@onNull Resources resources, @NonNull List<ResourcesLoader> newLoader)1877 public void onLoadersChanged(@NonNull Resources resources, 1878 @NonNull List<ResourcesLoader> newLoader) { 1879 synchronized (mLock) { 1880 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 1881 if (oldKey == null) { 1882 throw new IllegalArgumentException("Cannot modify resource loaders of" 1883 + " ResourcesImpl not registered with ResourcesManager"); 1884 } 1885 1886 final ResourcesKey newKey = new ResourcesKey( 1887 oldKey.mResDir, 1888 oldKey.mSplitResDirs, 1889 oldKey.mOverlayPaths, 1890 oldKey.mLibDirs, 1891 oldKey.mDisplayId, 1892 oldKey.mOverrideConfiguration, 1893 oldKey.mCompatInfo, 1894 newLoader.toArray(new ResourcesLoader[0])); 1895 1896 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey); 1897 resources.setImpl(impl); 1898 } 1899 } 1900 1901 /** 1902 * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the 1903 * {@code loader} to apply any changes of the set of {@link ApkAssets}. 1904 **/ 1905 @Override onLoaderUpdated(@onNull ResourcesLoader loader)1906 public void onLoaderUpdated(@NonNull ResourcesLoader loader) { 1907 synchronized (mLock) { 1908 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys = 1909 new ArrayMap<>(); 1910 1911 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 1912 final ResourcesKey key = mResourceImpls.keyAt(i); 1913 final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i); 1914 if (impl == null || impl.refersTo(null) 1915 || !ArrayUtils.contains(key.mLoaders, loader)) { 1916 continue; 1917 } 1918 1919 mResourceImpls.remove(key); 1920 updatedResourceImplKeys.put(impl.get(), key); 1921 } 1922 1923 redirectResourcesToNewImplLocked(updatedResourceImplKeys); 1924 } 1925 } 1926 } 1927 1928 @VisibleForTesting 1929 public static class SharedLibraryAssets { 1930 private final ResourcesKey mResourcesKey; 1931 SharedLibraryAssets(ApplicationInfo appInfo)1932 private SharedLibraryAssets(ApplicationInfo appInfo) { 1933 // We're loading all library's files as shared libs, regardless where they are in 1934 // its own ApplicationInfo. 1935 final var collector = new PathCollector(null); 1936 PathCollector.appendNewPath(appInfo.sourceDir, collector.libsSet, 1937 collector.orderedLibs); 1938 PathCollector.appendAllNewPaths(appInfo.splitSourceDirs, collector.libsSet, 1939 collector.orderedLibs); 1940 PathCollector.appendAllNewPaths(appInfo.sharedLibraryFiles, collector.libsSet, 1941 collector.orderedLibs); 1942 PathCollector.appendAllNewPaths(appInfo.resourceDirs, collector.overlaysSet, 1943 collector.orderedOverlays); 1944 PathCollector.appendAllNewPaths(appInfo.overlayPaths, collector.overlaysSet, 1945 collector.orderedOverlays); 1946 mResourcesKey = collector.collectedKey(); 1947 } 1948 1949 /** 1950 * @return the resources key for this library assets. 1951 */ getResourcesKey()1952 public @NonNull ResourcesKey getResourcesKey() { 1953 return mResourcesKey; 1954 } 1955 } 1956 1957 /** 1958 * Add all resources references to the list which is designed to help to append shared library 1959 * asset paths. This is invoked in Resources constructor to include all Resources instances. 1960 */ registerAllResourcesReference(@onNull Resources resources)1961 public void registerAllResourcesReference(@NonNull Resources resources) { 1962 if (android.content.res.Flags.registerResourcePaths()) { 1963 synchronized (mLock) { 1964 mAllResourceReferences.add( 1965 new WeakReference<>(resources, mAllResourceReferencesQueue)); 1966 } 1967 } 1968 } 1969 } 1970