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