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