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