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