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.content.pm.ActivityInfo; 24 import android.content.res.AssetManager; 25 import android.content.res.CompatibilityInfo; 26 import android.content.res.Configuration; 27 import android.content.res.Resources; 28 import android.content.res.ResourcesImpl; 29 import android.content.res.ResourcesKey; 30 import android.hardware.display.DisplayManagerGlobal; 31 import android.os.IBinder; 32 import android.os.Trace; 33 import android.util.ArrayMap; 34 import android.util.DisplayMetrics; 35 import android.util.Log; 36 import android.util.Pair; 37 import android.util.Slog; 38 import android.view.Display; 39 import android.view.DisplayAdjustments; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.ArrayUtils; 43 44 import java.lang.ref.WeakReference; 45 import java.util.ArrayList; 46 import java.util.Objects; 47 import java.util.WeakHashMap; 48 import java.util.function.Predicate; 49 50 /** @hide */ 51 public class ResourcesManager { 52 static final String TAG = "ResourcesManager"; 53 private static final boolean DEBUG = false; 54 55 private static ResourcesManager sResourcesManager; 56 57 /** 58 * Predicate that returns true if a WeakReference is gc'ed. 59 */ 60 private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = 61 new Predicate<WeakReference<Resources>>() { 62 @Override 63 public boolean test(WeakReference<Resources> weakRef) { 64 return weakRef == null || weakRef.get() == null; 65 } 66 }; 67 68 /** 69 * The global compatibility settings. 70 */ 71 private CompatibilityInfo mResCompatibilityInfo; 72 73 /** 74 * The global configuration upon which all Resources are based. Multi-window Resources 75 * apply their overrides to this configuration. 76 */ 77 private final Configuration mResConfiguration = new Configuration(); 78 79 /** 80 * A mapping of ResourceImpls and their configurations. These are heavy weight objects 81 * which should be reused as much as possible. 82 */ 83 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = 84 new ArrayMap<>(); 85 86 /** 87 * A list of Resource references that can be reused. 88 */ 89 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); 90 91 /** 92 * Resources and base configuration override associated with an Activity. 93 */ 94 private static class ActivityResources { 95 public final Configuration overrideConfig = new Configuration(); 96 public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>(); 97 } 98 99 /** 100 * Each Activity may has a base override configuration that is applied to each Resources object, 101 * which in turn may have their own override configuration specified. 102 */ 103 private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = 104 new WeakHashMap<>(); 105 106 /** 107 * A cache of DisplayId to DisplayAdjustments. 108 */ 109 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays = 110 new ArrayMap<>(); 111 getInstance()112 public static ResourcesManager getInstance() { 113 synchronized (ResourcesManager.class) { 114 if (sResourcesManager == null) { 115 sResourcesManager = new ResourcesManager(); 116 } 117 return sResourcesManager; 118 } 119 } 120 121 /** 122 * Invalidate and destroy any resources that reference content under the 123 * given filesystem path. Typically used when unmounting a storage device to 124 * try as hard as possible to release any open FDs. 125 */ invalidatePath(String path)126 public void invalidatePath(String path) { 127 synchronized (this) { 128 int count = 0; 129 for (int i = 0; i < mResourceImpls.size();) { 130 final ResourcesKey key = mResourceImpls.keyAt(i); 131 if (key.isPathReferenced(path)) { 132 final ResourcesImpl res = mResourceImpls.removeAt(i).get(); 133 if (res != null) { 134 res.flushLayoutCache(); 135 } 136 count++; 137 } else { 138 i++; 139 } 140 } 141 Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); 142 } 143 } 144 getConfiguration()145 public Configuration getConfiguration() { 146 synchronized (this) { 147 return mResConfiguration; 148 } 149 } 150 getDisplayMetrics()151 DisplayMetrics getDisplayMetrics() { 152 return getDisplayMetrics(Display.DEFAULT_DISPLAY, 153 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 154 } 155 156 /** 157 * Protected so that tests can override and returns something a fixed value. 158 */ 159 @VisibleForTesting getDisplayMetrics(int displayId, DisplayAdjustments da)160 protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) { 161 DisplayMetrics dm = new DisplayMetrics(); 162 final Display display = getAdjustedDisplay(displayId, da); 163 if (display != null) { 164 display.getMetrics(dm); 165 } else { 166 dm.setToDefaults(); 167 } 168 return dm; 169 } 170 applyNonDefaultDisplayMetricsToConfiguration( @onNull DisplayMetrics dm, @NonNull Configuration config)171 private static void applyNonDefaultDisplayMetricsToConfiguration( 172 @NonNull DisplayMetrics dm, @NonNull Configuration config) { 173 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; 174 config.densityDpi = dm.densityDpi; 175 config.screenWidthDp = (int) (dm.widthPixels / dm.density); 176 config.screenHeightDp = (int) (dm.heightPixels / dm.density); 177 int sl = Configuration.resetScreenLayout(config.screenLayout); 178 if (dm.widthPixels > dm.heightPixels) { 179 config.orientation = Configuration.ORIENTATION_LANDSCAPE; 180 config.screenLayout = Configuration.reduceScreenLayout(sl, 181 config.screenWidthDp, config.screenHeightDp); 182 } else { 183 config.orientation = Configuration.ORIENTATION_PORTRAIT; 184 config.screenLayout = Configuration.reduceScreenLayout(sl, 185 config.screenHeightDp, config.screenWidthDp); 186 } 187 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate 188 config.compatScreenWidthDp = config.screenWidthDp; 189 config.compatScreenHeightDp = config.screenHeightDp; 190 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; 191 } 192 applyCompatConfigurationLocked(int displayDensity, @NonNull Configuration compatConfiguration)193 public boolean applyCompatConfigurationLocked(int displayDensity, 194 @NonNull Configuration compatConfiguration) { 195 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { 196 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); 197 return true; 198 } 199 return false; 200 } 201 202 /** 203 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't 204 * available. 205 * 206 * @param displayId display Id. 207 * @param displayAdjustments display adjustments. 208 */ getAdjustedDisplay(final int displayId, @Nullable DisplayAdjustments displayAdjustments)209 public Display getAdjustedDisplay(final int displayId, 210 @Nullable DisplayAdjustments displayAdjustments) { 211 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) 212 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); 213 final Pair<Integer, DisplayAdjustments> key = 214 Pair.create(displayId, displayAdjustmentsCopy); 215 synchronized (this) { 216 WeakReference<Display> wd = mDisplays.get(key); 217 if (wd != null) { 218 final Display display = wd.get(); 219 if (display != null) { 220 return display; 221 } 222 } 223 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); 224 if (dm == null) { 225 // may be null early in system startup 226 return null; 227 } 228 final Display display = dm.getCompatibleDisplay(displayId, key.second); 229 if (display != null) { 230 mDisplays.put(key, new WeakReference<>(display)); 231 } 232 return display; 233 } 234 } 235 236 /** 237 * Creates an AssetManager from the paths within the ResourcesKey. 238 * 239 * This can be overridden in tests so as to avoid creating a real AssetManager with 240 * real APK paths. 241 * @param key The key containing the resource paths to add to the AssetManager. 242 * @return a new AssetManager. 243 */ 244 @VisibleForTesting createAssetManager(@onNull final ResourcesKey key)245 protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) { 246 AssetManager assets = new AssetManager(); 247 248 // resDir can be null if the 'android' package is creating a new Resources object. 249 // This is fine, since each AssetManager automatically loads the 'android' package 250 // already. 251 if (key.mResDir != null) { 252 if (assets.addAssetPath(key.mResDir) == 0) { 253 throw new Resources.NotFoundException("failed to add asset path " + key.mResDir); 254 } 255 } 256 257 if (key.mSplitResDirs != null) { 258 for (final String splitResDir : key.mSplitResDirs) { 259 if (assets.addAssetPath(splitResDir) == 0) { 260 throw new Resources.NotFoundException( 261 "failed to add split asset path " + splitResDir); 262 } 263 } 264 } 265 266 if (key.mOverlayDirs != null) { 267 for (final String idmapPath : key.mOverlayDirs) { 268 assets.addOverlayPath(idmapPath); 269 } 270 } 271 272 if (key.mLibDirs != null) { 273 for (final String libDir : key.mLibDirs) { 274 if (libDir.endsWith(".apk")) { 275 // Avoid opening files we know do not have resources, 276 // like code-only .jar files. 277 if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { 278 Log.w(TAG, "Asset path '" + libDir + 279 "' does not exist or contains no resources."); 280 } 281 } 282 } 283 } 284 return assets; 285 } 286 generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)287 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { 288 Configuration config; 289 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); 290 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 291 if (!isDefaultDisplay || hasOverrideConfig) { 292 config = new Configuration(getConfiguration()); 293 if (!isDefaultDisplay) { 294 applyNonDefaultDisplayMetricsToConfiguration(dm, config); 295 } 296 if (hasOverrideConfig) { 297 config.updateFrom(key.mOverrideConfiguration); 298 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 299 } 300 } else { 301 config = getConfiguration(); 302 } 303 return config; 304 } 305 createResourcesImpl(@onNull ResourcesKey key)306 private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { 307 final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); 308 daj.setCompatibilityInfo(key.mCompatInfo); 309 310 final AssetManager assets = createAssetManager(key); 311 final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); 312 final Configuration config = generateConfig(key, dm); 313 final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); 314 if (DEBUG) { 315 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 316 } 317 return impl; 318 } 319 320 /** 321 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 322 * 323 * @param key The key to match. 324 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 325 */ findResourcesImplForKeyLocked(@onNull ResourcesKey key)326 private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) { 327 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 328 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 329 if (impl != null && impl.getAssets().isUpToDate()) { 330 return impl; 331 } 332 return null; 333 } 334 335 /** 336 * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or 337 * creates a new one and caches it for future use. 338 * @param key The key to match. 339 * @return a ResourcesImpl object matching the key. 340 */ findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)341 private @NonNull ResourcesImpl findOrCreateResourcesImplForKeyLocked( 342 @NonNull ResourcesKey key) { 343 ResourcesImpl impl = findResourcesImplForKeyLocked(key); 344 if (impl == null) { 345 impl = createResourcesImpl(key); 346 mResourceImpls.put(key, new WeakReference<>(impl)); 347 } 348 return impl; 349 } 350 351 /** 352 * Find the ResourcesKey that this ResourcesImpl object is associated with. 353 * @return the ResourcesKey or null if none was found. 354 */ findKeyForResourceImplLocked(@onNull ResourcesImpl resourceImpl)355 private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) { 356 final int refCount = mResourceImpls.size(); 357 for (int i = 0; i < refCount; i++) { 358 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 359 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 360 if (impl != null && resourceImpl == impl) { 361 return mResourceImpls.keyAt(i); 362 } 363 } 364 return null; 365 } 366 getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)367 private ActivityResources getOrCreateActivityResourcesStructLocked( 368 @NonNull IBinder activityToken) { 369 ActivityResources activityResources = mActivityResourceReferences.get(activityToken); 370 if (activityResources == null) { 371 activityResources = new ActivityResources(); 372 mActivityResourceReferences.put(activityToken, activityResources); 373 } 374 return activityResources; 375 } 376 377 /** 378 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist 379 * or the class loader is different. 380 */ getOrCreateResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl)381 private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, 382 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) { 383 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 384 activityToken); 385 386 final int refCount = activityResources.activityResources.size(); 387 for (int i = 0; i < refCount; i++) { 388 WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i); 389 Resources resources = weakResourceRef.get(); 390 391 if (resources != null 392 && Objects.equals(resources.getClassLoader(), classLoader) 393 && resources.getImpl() == impl) { 394 if (DEBUG) { 395 Slog.d(TAG, "- using existing ref=" + resources); 396 } 397 return resources; 398 } 399 } 400 401 Resources resources = new Resources(classLoader); 402 resources.setImpl(impl); 403 activityResources.activityResources.add(new WeakReference<>(resources)); 404 if (DEBUG) { 405 Slog.d(TAG, "- creating new ref=" + resources); 406 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 407 } 408 return resources; 409 } 410 411 /** 412 * Gets an existing Resources object if the class loader and ResourcesImpl are the same, 413 * otherwise creates a new Resources object. 414 */ getOrCreateResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl)415 private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, 416 @NonNull ResourcesImpl impl) { 417 // Find an existing Resources that has this ResourcesImpl set. 418 final int refCount = mResourceReferences.size(); 419 for (int i = 0; i < refCount; i++) { 420 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); 421 Resources resources = weakResourceRef.get(); 422 if (resources != null && 423 Objects.equals(resources.getClassLoader(), classLoader) && 424 resources.getImpl() == impl) { 425 if (DEBUG) { 426 Slog.d(TAG, "- using existing ref=" + resources); 427 } 428 return resources; 429 } 430 } 431 432 // Create a new Resources reference and use the existing ResourcesImpl object. 433 Resources resources = new Resources(classLoader); 434 resources.setImpl(impl); 435 mResourceReferences.add(new WeakReference<>(resources)); 436 if (DEBUG) { 437 Slog.d(TAG, "- creating new ref=" + resources); 438 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 439 } 440 return resources; 441 } 442 443 /** 444 * Creates base resources for an Activity. Calls to 445 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 446 * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override 447 * configurations merged with the one specified here. 448 * 449 * @param activityToken Represents an Activity. 450 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 451 * @param splitResDirs An array of split resource paths. Can be null. 452 * @param overlayDirs An array of overlay paths. Can be null. 453 * @param libDirs An array of resource library paths. Can be null. 454 * @param displayId The ID of the display for which to create the resources. 455 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 456 * null. This provides the base override for this Activity. 457 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 458 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 459 * @param classLoader The class loader to use when inflating Resources. If null, the 460 * {@link ClassLoader#getSystemClassLoader()} is used. 461 * @return a Resources object from which to access resources. 462 */ createBaseActivityResources(@onNull 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)463 public @NonNull Resources createBaseActivityResources(@NonNull IBinder activityToken, 464 @Nullable String resDir, 465 @Nullable String[] splitResDirs, 466 @Nullable String[] overlayDirs, 467 @Nullable String[] libDirs, 468 int displayId, 469 @Nullable Configuration overrideConfig, 470 @NonNull CompatibilityInfo compatInfo, 471 @Nullable ClassLoader classLoader) { 472 try { 473 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 474 "ResourcesManager#createBaseActivityResources"); 475 final ResourcesKey key = new ResourcesKey( 476 resDir, 477 splitResDirs, 478 overlayDirs, 479 libDirs, 480 displayId, 481 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 482 compatInfo); 483 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 484 485 if (DEBUG) { 486 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken 487 + " with key=" + key); 488 } 489 490 synchronized (this) { 491 // Force the creation of an ActivityResourcesStruct. 492 getOrCreateActivityResourcesStructLocked(activityToken); 493 } 494 495 // Update any existing Activity Resources references. 496 updateResourcesForActivity(activityToken, overrideConfig); 497 498 // Now request an actual Resources object. 499 return getOrCreateResources(activityToken, key, classLoader); 500 } finally { 501 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 502 } 503 } 504 505 /** 506 * Gets an existing Resources object set with a ResourcesImpl object matching the given key, 507 * or creates one if it doesn't exist. 508 * 509 * @param activityToken The Activity this Resources object should be associated with. 510 * @param key The key describing the parameters of the ResourcesImpl object. 511 * @param classLoader The classloader to use for the Resources object. 512 * If null, {@link ClassLoader#getSystemClassLoader()} is used. 513 * @return A Resources object that gets updated when 514 * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} 515 * is called. 516 */ getOrCreateResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader)517 private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken, 518 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { 519 synchronized (this) { 520 if (DEBUG) { 521 Throwable here = new Throwable(); 522 here.fillInStackTrace(); 523 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 524 } 525 526 if (activityToken != null) { 527 final ActivityResources activityResources = 528 getOrCreateActivityResourcesStructLocked(activityToken); 529 530 // Clean up any dead references so they don't pile up. 531 ArrayUtils.unstableRemoveIf(activityResources.activityResources, 532 sEmptyReferencePredicate); 533 534 // Rebase the key's override config on top of the Activity's base override. 535 if (key.hasOverrideConfiguration() 536 && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { 537 final Configuration temp = new Configuration(activityResources.overrideConfig); 538 temp.updateFrom(key.mOverrideConfiguration); 539 key.mOverrideConfiguration.setTo(temp); 540 } 541 542 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 543 if (resourcesImpl != null) { 544 if (DEBUG) { 545 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 546 } 547 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, 548 resourcesImpl); 549 } 550 551 // We will create the ResourcesImpl object outside of holding this lock. 552 553 } else { 554 // Clean up any dead references so they don't pile up. 555 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 556 557 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 558 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 559 if (resourcesImpl != null) { 560 if (DEBUG) { 561 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 562 } 563 return getOrCreateResourcesLocked(classLoader, resourcesImpl); 564 } 565 566 // We will create the ResourcesImpl object outside of holding this lock. 567 } 568 } 569 570 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 571 ResourcesImpl resourcesImpl = createResourcesImpl(key); 572 573 synchronized (this) { 574 ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); 575 if (existingResourcesImpl != null) { 576 if (DEBUG) { 577 Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl 578 + " new impl=" + resourcesImpl); 579 } 580 resourcesImpl.getAssets().close(); 581 resourcesImpl = existingResourcesImpl; 582 } else { 583 // Add this ResourcesImpl to the cache. 584 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); 585 } 586 587 final Resources resources; 588 if (activityToken != null) { 589 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, 590 resourcesImpl); 591 } else { 592 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); 593 } 594 return resources; 595 } 596 } 597 598 /** 599 * Gets or creates a new Resources object associated with the IBinder token. References returned 600 * by this method live as long as the Activity, meaning they can be cached and used by the 601 * Activity even after a configuration change. If any other parameter is changed 602 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 603 * is updated and handed back to the caller. However, changing the class loader will result in a 604 * new Resources object. 605 * <p/> 606 * If activityToken is null, a cached Resources object will be returned if it matches the 607 * input parameters. Otherwise a new Resources object that satisfies these parameters is 608 * returned. 609 * 610 * @param activityToken Represents an Activity. If null, global resources are assumed. 611 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 612 * @param splitResDirs An array of split resource paths. Can be null. 613 * @param overlayDirs An array of overlay paths. Can be null. 614 * @param libDirs An array of resource library paths. Can be null. 615 * @param displayId The ID of the display for which to create the resources. 616 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 617 * null. Mostly used with Activities that are in multi-window which may override width and 618 * height properties from the base config. 619 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 620 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 621 * @param classLoader The class loader to use when inflating Resources. If null, the 622 * {@link ClassLoader#getSystemClassLoader()} is used. 623 * @return a Resources object from which to access resources. 624 */ 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)625 public @NonNull Resources getResources(@Nullable IBinder activityToken, 626 @Nullable String resDir, 627 @Nullable String[] splitResDirs, 628 @Nullable String[] overlayDirs, 629 @Nullable String[] libDirs, 630 int displayId, 631 @Nullable Configuration overrideConfig, 632 @NonNull CompatibilityInfo compatInfo, 633 @Nullable ClassLoader classLoader) { 634 try { 635 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 636 final ResourcesKey key = new ResourcesKey( 637 resDir, 638 splitResDirs, 639 overlayDirs, 640 libDirs, 641 displayId, 642 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 643 compatInfo); 644 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 645 return getOrCreateResources(activityToken, key, classLoader); 646 } finally { 647 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 648 } 649 } 650 651 /** 652 * Updates an Activity's Resources object with overrideConfig. The Resources object 653 * that was previously returned by 654 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 655 * CompatibilityInfo, ClassLoader)} is 656 * still valid and will have the updated configuration. 657 * @param activityToken The Activity token. 658 * @param overrideConfig The configuration override to update. 659 */ updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig)660 public void updateResourcesForActivity(@NonNull IBinder activityToken, 661 @Nullable Configuration overrideConfig) { 662 try { 663 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 664 "ResourcesManager#updateResourcesForActivity"); 665 synchronized (this) { 666 final ActivityResources activityResources = 667 getOrCreateActivityResourcesStructLocked(activityToken); 668 669 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) { 670 // They are the same, no work to do. 671 return; 672 } 673 674 // Grab a copy of the old configuration so we can create the delta's of each 675 // Resources object associated with this Activity. 676 final Configuration oldConfig = new Configuration(activityResources.overrideConfig); 677 678 // Update the Activity's base override. 679 if (overrideConfig != null) { 680 activityResources.overrideConfig.setTo(overrideConfig); 681 } else { 682 activityResources.overrideConfig.setToDefaults(); 683 } 684 685 if (DEBUG) { 686 Throwable here = new Throwable(); 687 here.fillInStackTrace(); 688 Slog.d(TAG, "updating resources override for activity=" + activityToken 689 + " from oldConfig=" 690 + Configuration.resourceQualifierString(oldConfig) 691 + " to newConfig=" 692 + Configuration.resourceQualifierString( 693 activityResources.overrideConfig), 694 here); 695 } 696 697 final boolean activityHasOverrideConfig = 698 !activityResources.overrideConfig.equals(Configuration.EMPTY); 699 700 // Rebase each Resources associated with this Activity. 701 final int refCount = activityResources.activityResources.size(); 702 for (int i = 0; i < refCount; i++) { 703 WeakReference<Resources> weakResRef = activityResources.activityResources.get( 704 i); 705 Resources resources = weakResRef.get(); 706 if (resources == null) { 707 continue; 708 } 709 710 // Extract the ResourcesKey that was last used to create the Resources for this 711 // activity. 712 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 713 if (oldKey == null) { 714 Slog.e(TAG, "can't find ResourcesKey for resources impl=" 715 + resources.getImpl()); 716 continue; 717 } 718 719 // Build the new override configuration for this ResourcesKey. 720 final Configuration rebasedOverrideConfig = new Configuration(); 721 if (overrideConfig != null) { 722 rebasedOverrideConfig.setTo(overrideConfig); 723 } 724 725 if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) { 726 // Generate a delta between the old base Activity override configuration and 727 // the actual final override configuration that was used to figure out the 728 // real delta this Resources object wanted. 729 Configuration overrideOverrideConfig = Configuration.generateDelta( 730 oldConfig, oldKey.mOverrideConfiguration); 731 rebasedOverrideConfig.updateFrom(overrideOverrideConfig); 732 } 733 734 // Create the new ResourcesKey with the rebased override config. 735 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, 736 oldKey.mSplitResDirs, 737 oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId, 738 rebasedOverrideConfig, oldKey.mCompatInfo); 739 740 if (DEBUG) { 741 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey 742 + " to newKey=" + newKey); 743 } 744 745 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); 746 if (resourcesImpl == null) { 747 resourcesImpl = createResourcesImpl(newKey); 748 mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); 749 } 750 751 if (resourcesImpl != resources.getImpl()) { 752 // Set the ResourcesImpl, updating it for all users of this Resources 753 // object. 754 resources.setImpl(resourcesImpl); 755 } 756 } 757 } 758 } finally { 759 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 760 } 761 } 762 applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)763 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, 764 @Nullable CompatibilityInfo compat) { 765 try { 766 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 767 "ResourcesManager#applyConfigurationToResourcesLocked"); 768 769 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 770 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 771 + mResConfiguration.seq + ", newSeq=" + config.seq); 772 return false; 773 } 774 int changes = mResConfiguration.updateFrom(config); 775 // Things might have changed in display manager, so clear the cached displays. 776 mDisplays.clear(); 777 DisplayMetrics defaultDisplayMetrics = getDisplayMetrics(); 778 779 if (compat != null && (mResCompatibilityInfo == null || 780 !mResCompatibilityInfo.equals(compat))) { 781 mResCompatibilityInfo = compat; 782 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 783 | ActivityInfo.CONFIG_SCREEN_SIZE 784 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 785 } 786 787 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); 788 789 ApplicationPackageManager.configurationChanged(); 790 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 791 792 Configuration tmpConfig = null; 793 794 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 795 ResourcesKey key = mResourceImpls.keyAt(i); 796 ResourcesImpl r = mResourceImpls.valueAt(i).get(); 797 if (r != null) { 798 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 799 + r + " config to: " + config); 800 int displayId = key.mDisplayId; 801 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 802 DisplayMetrics dm = defaultDisplayMetrics; 803 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 804 if (!isDefaultDisplay || hasOverrideConfiguration) { 805 if (tmpConfig == null) { 806 tmpConfig = new Configuration(); 807 } 808 tmpConfig.setTo(config); 809 if (!isDefaultDisplay) { 810 // Get new DisplayMetrics based on the DisplayAdjustments given 811 // to the ResourcesImpl. Udate a copy if the CompatibilityInfo 812 // changed, because the ResourcesImpl object will handle the 813 // update internally. 814 DisplayAdjustments daj = r.getDisplayAdjustments(); 815 if (compat != null) { 816 daj = new DisplayAdjustments(daj); 817 daj.setCompatibilityInfo(compat); 818 } 819 dm = getDisplayMetrics(displayId, daj); 820 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); 821 } 822 if (hasOverrideConfiguration) { 823 tmpConfig.updateFrom(key.mOverrideConfiguration); 824 } 825 r.updateConfiguration(tmpConfig, dm, compat); 826 } else { 827 r.updateConfiguration(config, dm, compat); 828 } 829 //Slog.i(TAG, "Updated app resources " + v.getKey() 830 // + " " + r + ": " + r.getConfiguration()); 831 } else { 832 //Slog.i(TAG, "Removing old resources " + v.getKey()); 833 mResourceImpls.removeAt(i); 834 } 835 } 836 837 return changes != 0; 838 } finally { 839 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 840 } 841 } 842 843 /** 844 * Appends the library asset path to any ResourcesImpl object that contains the main 845 * assetPath. 846 * @param assetPath The main asset path for which to add the library asset path. 847 * @param libAsset The library asset path to add. 848 */ appendLibAssetForMainAssetPath(String assetPath, String libAsset)849 public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { 850 synchronized (this) { 851 // Record which ResourcesImpl need updating 852 // (and what ResourcesKey they should update to). 853 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 854 855 final int implCount = mResourceImpls.size(); 856 for (int i = 0; i < implCount; i++) { 857 final ResourcesImpl impl = mResourceImpls.valueAt(i).get(); 858 final ResourcesKey key = mResourceImpls.keyAt(i); 859 if (impl != null && key.mResDir.equals(assetPath)) { 860 if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { 861 final int newLibAssetCount = 1 + 862 (key.mLibDirs != null ? key.mLibDirs.length : 0); 863 final String[] newLibAssets = new String[newLibAssetCount]; 864 if (key.mLibDirs != null) { 865 System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length); 866 } 867 newLibAssets[newLibAssetCount - 1] = libAsset; 868 869 updatedResourceKeys.put(impl, new ResourcesKey( 870 key.mResDir, 871 key.mSplitResDirs, 872 key.mOverlayDirs, 873 newLibAssets, 874 key.mDisplayId, 875 key.mOverrideConfiguration, 876 key.mCompatInfo)); 877 } 878 } 879 } 880 881 // Bail early if there is no work to do. 882 if (updatedResourceKeys.isEmpty()) { 883 return; 884 } 885 886 // Update any references to ResourcesImpl that require reloading. 887 final int resourcesCount = mResourceReferences.size(); 888 for (int i = 0; i < resourcesCount; i++) { 889 final Resources r = mResourceReferences.get(i).get(); 890 if (r != null) { 891 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 892 if (key != null) { 893 r.setImpl(findOrCreateResourcesImplForKeyLocked(key)); 894 } 895 } 896 } 897 898 // Update any references to ResourcesImpl that require reloading for each Activity. 899 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 900 final int resCount = activityResources.activityResources.size(); 901 for (int i = 0; i < resCount; i++) { 902 final Resources r = activityResources.activityResources.get(i).get(); 903 if (r != null) { 904 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 905 if (key != null) { 906 r.setImpl(findOrCreateResourcesImplForKeyLocked(key)); 907 } 908 } 909 } 910 } 911 } 912 } 913 } 914