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 @Nullable 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 Log.e(TAG, "failed to add asset path " + key.mResDir); 254 return null; 255 } 256 } 257 258 if (key.mSplitResDirs != null) { 259 for (final String splitResDir : key.mSplitResDirs) { 260 if (assets.addAssetPath(splitResDir) == 0) { 261 Log.e(TAG, "failed to add split asset path " + splitResDir); 262 return null; 263 } 264 } 265 } 266 267 if (key.mOverlayDirs != null) { 268 for (final String idmapPath : key.mOverlayDirs) { 269 assets.addOverlayPath(idmapPath); 270 } 271 } 272 273 if (key.mLibDirs != null) { 274 for (final String libDir : key.mLibDirs) { 275 if (libDir.endsWith(".apk")) { 276 // Avoid opening files we know do not have resources, 277 // like code-only .jar files. 278 if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { 279 Log.w(TAG, "Asset path '" + libDir + 280 "' does not exist or contains no resources."); 281 } 282 } 283 } 284 } 285 return assets; 286 } 287 generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)288 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { 289 Configuration config; 290 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); 291 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 292 if (!isDefaultDisplay || hasOverrideConfig) { 293 config = new Configuration(getConfiguration()); 294 if (!isDefaultDisplay) { 295 applyNonDefaultDisplayMetricsToConfiguration(dm, config); 296 } 297 if (hasOverrideConfig) { 298 config.updateFrom(key.mOverrideConfiguration); 299 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 300 } 301 } else { 302 config = getConfiguration(); 303 } 304 return config; 305 } 306 createResourcesImpl(@onNull ResourcesKey key)307 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { 308 final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); 309 daj.setCompatibilityInfo(key.mCompatInfo); 310 311 final AssetManager assets = createAssetManager(key); 312 if (assets == null) { 313 return null; 314 } 315 316 final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); 317 final Configuration config = generateConfig(key, dm); 318 final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); 319 if (DEBUG) { 320 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 321 } 322 return impl; 323 } 324 325 /** 326 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 327 * 328 * @param key The key to match. 329 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 330 */ findResourcesImplForKeyLocked(@onNull ResourcesKey key)331 private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) { 332 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 333 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 334 if (impl != null && impl.getAssets().isUpToDate()) { 335 return impl; 336 } 337 return null; 338 } 339 340 /** 341 * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or 342 * creates a new one and caches it for future use. 343 * @param key The key to match. 344 * @return a ResourcesImpl object matching the key. 345 */ findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)346 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( 347 @NonNull ResourcesKey key) { 348 ResourcesImpl impl = findResourcesImplForKeyLocked(key); 349 if (impl == null) { 350 impl = createResourcesImpl(key); 351 if (impl != null) { 352 mResourceImpls.put(key, new WeakReference<>(impl)); 353 } 354 } 355 return impl; 356 } 357 358 /** 359 * Find the ResourcesKey that this ResourcesImpl object is associated with. 360 * @return the ResourcesKey or null if none was found. 361 */ findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)362 private @Nullable ResourcesKey findKeyForResourceImplLocked( 363 @NonNull ResourcesImpl resourceImpl) { 364 final int refCount = mResourceImpls.size(); 365 for (int i = 0; i < refCount; i++) { 366 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 367 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 368 if (impl != null && resourceImpl == impl) { 369 return mResourceImpls.keyAt(i); 370 } 371 } 372 return null; 373 } 374 375 /** 376 * Check if activity resources have same override config as the provided on. 377 * @param activityToken The Activity that resources should be associated with. 378 * @param overrideConfig The override configuration to be checked for equality with. 379 * @return true if activity resources override config matches the provided one or they are both 380 * null, false otherwise. 381 */ isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)382 boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken, 383 @Nullable Configuration overrideConfig) { 384 synchronized (this) { 385 final ActivityResources activityResources 386 = activityToken != null ? mActivityResourceReferences.get(activityToken) : null; 387 if (activityResources == null) { 388 return overrideConfig == null; 389 } else { 390 return Objects.equals(activityResources.overrideConfig, overrideConfig); 391 } 392 } 393 } 394 getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)395 private ActivityResources getOrCreateActivityResourcesStructLocked( 396 @NonNull IBinder activityToken) { 397 ActivityResources activityResources = mActivityResourceReferences.get(activityToken); 398 if (activityResources == null) { 399 activityResources = new ActivityResources(); 400 mActivityResourceReferences.put(activityToken, activityResources); 401 } 402 return activityResources; 403 } 404 405 /** 406 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist 407 * or the class loader is different. 408 */ getOrCreateResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl)409 private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, 410 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) { 411 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 412 activityToken); 413 414 final int refCount = activityResources.activityResources.size(); 415 for (int i = 0; i < refCount; i++) { 416 WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i); 417 Resources resources = weakResourceRef.get(); 418 419 if (resources != null 420 && Objects.equals(resources.getClassLoader(), classLoader) 421 && resources.getImpl() == impl) { 422 if (DEBUG) { 423 Slog.d(TAG, "- using existing ref=" + resources); 424 } 425 return resources; 426 } 427 } 428 429 Resources resources = new Resources(classLoader); 430 resources.setImpl(impl); 431 activityResources.activityResources.add(new WeakReference<>(resources)); 432 if (DEBUG) { 433 Slog.d(TAG, "- creating new ref=" + resources); 434 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 435 } 436 return resources; 437 } 438 439 /** 440 * Gets an existing Resources object if the class loader and ResourcesImpl are the same, 441 * otherwise creates a new Resources object. 442 */ getOrCreateResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl)443 private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, 444 @NonNull ResourcesImpl impl) { 445 // Find an existing Resources that has this ResourcesImpl set. 446 final int refCount = mResourceReferences.size(); 447 for (int i = 0; i < refCount; i++) { 448 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); 449 Resources resources = weakResourceRef.get(); 450 if (resources != null && 451 Objects.equals(resources.getClassLoader(), classLoader) && 452 resources.getImpl() == impl) { 453 if (DEBUG) { 454 Slog.d(TAG, "- using existing ref=" + resources); 455 } 456 return resources; 457 } 458 } 459 460 // Create a new Resources reference and use the existing ResourcesImpl object. 461 Resources resources = new Resources(classLoader); 462 resources.setImpl(impl); 463 mResourceReferences.add(new WeakReference<>(resources)); 464 if (DEBUG) { 465 Slog.d(TAG, "- creating new ref=" + resources); 466 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 467 } 468 return resources; 469 } 470 471 /** 472 * Creates base resources for an Activity. Calls to 473 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 474 * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override 475 * configurations merged with the one specified here. 476 * 477 * @param activityToken Represents an Activity. 478 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 479 * @param splitResDirs An array of split resource paths. Can be null. 480 * @param overlayDirs An array of overlay paths. Can be null. 481 * @param libDirs An array of resource library paths. Can be null. 482 * @param displayId The ID of the display for which to create the resources. 483 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 484 * null. This provides the base override for this Activity. 485 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 486 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 487 * @param classLoader The class loader to use when inflating Resources. If null, the 488 * {@link ClassLoader#getSystemClassLoader()} is used. 489 * @return a Resources object from which to access resources. 490 */ 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)491 public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken, 492 @Nullable String resDir, 493 @Nullable String[] splitResDirs, 494 @Nullable String[] overlayDirs, 495 @Nullable String[] libDirs, 496 int displayId, 497 @Nullable Configuration overrideConfig, 498 @NonNull CompatibilityInfo compatInfo, 499 @Nullable ClassLoader classLoader) { 500 try { 501 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 502 "ResourcesManager#createBaseActivityResources"); 503 final ResourcesKey key = new ResourcesKey( 504 resDir, 505 splitResDirs, 506 overlayDirs, 507 libDirs, 508 displayId, 509 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 510 compatInfo); 511 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 512 513 if (DEBUG) { 514 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken 515 + " with key=" + key); 516 } 517 518 synchronized (this) { 519 // Force the creation of an ActivityResourcesStruct. 520 getOrCreateActivityResourcesStructLocked(activityToken); 521 } 522 523 // Update any existing Activity Resources references. 524 updateResourcesForActivity(activityToken, overrideConfig); 525 526 // Now request an actual Resources object. 527 return getOrCreateResources(activityToken, key, classLoader); 528 } finally { 529 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 530 } 531 } 532 533 /** 534 * Gets an existing Resources object set with a ResourcesImpl object matching the given key, 535 * or creates one if it doesn't exist. 536 * 537 * @param activityToken The Activity this Resources object should be associated with. 538 * @param key The key describing the parameters of the ResourcesImpl object. 539 * @param classLoader The classloader to use for the Resources object. 540 * If null, {@link ClassLoader#getSystemClassLoader()} is used. 541 * @return A Resources object that gets updated when 542 * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} 543 * is called. 544 */ getOrCreateResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader)545 private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken, 546 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { 547 synchronized (this) { 548 if (DEBUG) { 549 Throwable here = new Throwable(); 550 here.fillInStackTrace(); 551 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 552 } 553 554 if (activityToken != null) { 555 final ActivityResources activityResources = 556 getOrCreateActivityResourcesStructLocked(activityToken); 557 558 // Clean up any dead references so they don't pile up. 559 ArrayUtils.unstableRemoveIf(activityResources.activityResources, 560 sEmptyReferencePredicate); 561 562 // Rebase the key's override config on top of the Activity's base override. 563 if (key.hasOverrideConfiguration() 564 && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { 565 final Configuration temp = new Configuration(activityResources.overrideConfig); 566 temp.updateFrom(key.mOverrideConfiguration); 567 key.mOverrideConfiguration.setTo(temp); 568 } 569 570 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 571 if (resourcesImpl != null) { 572 if (DEBUG) { 573 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 574 } 575 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, 576 resourcesImpl); 577 } 578 579 // We will create the ResourcesImpl object outside of holding this lock. 580 581 } else { 582 // Clean up any dead references so they don't pile up. 583 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 584 585 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 586 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 587 if (resourcesImpl != null) { 588 if (DEBUG) { 589 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 590 } 591 return getOrCreateResourcesLocked(classLoader, resourcesImpl); 592 } 593 594 // We will create the ResourcesImpl object outside of holding this lock. 595 } 596 } 597 598 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 599 ResourcesImpl resourcesImpl = createResourcesImpl(key); 600 if (resourcesImpl == null) { 601 return null; 602 } 603 604 synchronized (this) { 605 ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); 606 if (existingResourcesImpl != null) { 607 if (DEBUG) { 608 Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl 609 + " new impl=" + resourcesImpl); 610 } 611 resourcesImpl.getAssets().close(); 612 resourcesImpl = existingResourcesImpl; 613 } else { 614 // Add this ResourcesImpl to the cache. 615 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); 616 } 617 618 final Resources resources; 619 if (activityToken != null) { 620 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, 621 resourcesImpl); 622 } else { 623 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); 624 } 625 return resources; 626 } 627 } 628 629 /** 630 * Gets or creates a new Resources object associated with the IBinder token. References returned 631 * by this method live as long as the Activity, meaning they can be cached and used by the 632 * Activity even after a configuration change. If any other parameter is changed 633 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 634 * is updated and handed back to the caller. However, changing the class loader will result in a 635 * new Resources object. 636 * <p/> 637 * If activityToken is null, a cached Resources object will be returned if it matches the 638 * input parameters. Otherwise a new Resources object that satisfies these parameters is 639 * returned. 640 * 641 * @param activityToken Represents an Activity. If null, global resources are assumed. 642 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 643 * @param splitResDirs An array of split resource paths. Can be null. 644 * @param overlayDirs An array of overlay paths. Can be null. 645 * @param libDirs An array of resource library paths. Can be null. 646 * @param displayId The ID of the display for which to create the resources. 647 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 648 * null. Mostly used with Activities that are in multi-window which may override width and 649 * height properties from the base config. 650 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 651 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 652 * @param classLoader The class loader to use when inflating Resources. If null, the 653 * {@link ClassLoader#getSystemClassLoader()} is used. 654 * @return a Resources object from which to access resources. 655 */ 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)656 public @Nullable Resources getResources(@Nullable IBinder activityToken, 657 @Nullable String resDir, 658 @Nullable String[] splitResDirs, 659 @Nullable String[] overlayDirs, 660 @Nullable String[] libDirs, 661 int displayId, 662 @Nullable Configuration overrideConfig, 663 @NonNull CompatibilityInfo compatInfo, 664 @Nullable ClassLoader classLoader) { 665 try { 666 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 667 final ResourcesKey key = new ResourcesKey( 668 resDir, 669 splitResDirs, 670 overlayDirs, 671 libDirs, 672 displayId, 673 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 674 compatInfo); 675 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 676 return getOrCreateResources(activityToken, key, classLoader); 677 } finally { 678 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 679 } 680 } 681 682 /** 683 * Updates an Activity's Resources object with overrideConfig. The Resources object 684 * that was previously returned by 685 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 686 * CompatibilityInfo, ClassLoader)} is 687 * still valid and will have the updated configuration. 688 * @param activityToken The Activity token. 689 * @param overrideConfig The configuration override to update. 690 */ updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig)691 public void updateResourcesForActivity(@NonNull IBinder activityToken, 692 @Nullable Configuration overrideConfig) { 693 try { 694 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 695 "ResourcesManager#updateResourcesForActivity"); 696 synchronized (this) { 697 final ActivityResources activityResources = 698 getOrCreateActivityResourcesStructLocked(activityToken); 699 700 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) { 701 // They are the same, no work to do. 702 return; 703 } 704 705 // Grab a copy of the old configuration so we can create the delta's of each 706 // Resources object associated with this Activity. 707 final Configuration oldConfig = new Configuration(activityResources.overrideConfig); 708 709 // Update the Activity's base override. 710 if (overrideConfig != null) { 711 activityResources.overrideConfig.setTo(overrideConfig); 712 } else { 713 activityResources.overrideConfig.setToDefaults(); 714 } 715 716 if (DEBUG) { 717 Throwable here = new Throwable(); 718 here.fillInStackTrace(); 719 Slog.d(TAG, "updating resources override for activity=" + activityToken 720 + " from oldConfig=" 721 + Configuration.resourceQualifierString(oldConfig) 722 + " to newConfig=" 723 + Configuration.resourceQualifierString( 724 activityResources.overrideConfig), 725 here); 726 } 727 728 final boolean activityHasOverrideConfig = 729 !activityResources.overrideConfig.equals(Configuration.EMPTY); 730 731 // Rebase each Resources associated with this Activity. 732 final int refCount = activityResources.activityResources.size(); 733 for (int i = 0; i < refCount; i++) { 734 WeakReference<Resources> weakResRef = activityResources.activityResources.get( 735 i); 736 Resources resources = weakResRef.get(); 737 if (resources == null) { 738 continue; 739 } 740 741 // Extract the ResourcesKey that was last used to create the Resources for this 742 // activity. 743 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 744 if (oldKey == null) { 745 Slog.e(TAG, "can't find ResourcesKey for resources impl=" 746 + resources.getImpl()); 747 continue; 748 } 749 750 // Build the new override configuration for this ResourcesKey. 751 final Configuration rebasedOverrideConfig = new Configuration(); 752 if (overrideConfig != null) { 753 rebasedOverrideConfig.setTo(overrideConfig); 754 } 755 756 if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) { 757 // Generate a delta between the old base Activity override configuration and 758 // the actual final override configuration that was used to figure out the 759 // real delta this Resources object wanted. 760 Configuration overrideOverrideConfig = Configuration.generateDelta( 761 oldConfig, oldKey.mOverrideConfiguration); 762 rebasedOverrideConfig.updateFrom(overrideOverrideConfig); 763 } 764 765 // Create the new ResourcesKey with the rebased override config. 766 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, 767 oldKey.mSplitResDirs, 768 oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId, 769 rebasedOverrideConfig, oldKey.mCompatInfo); 770 771 if (DEBUG) { 772 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey 773 + " to newKey=" + newKey); 774 } 775 776 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); 777 if (resourcesImpl == null) { 778 resourcesImpl = createResourcesImpl(newKey); 779 if (resourcesImpl != null) { 780 mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); 781 } 782 } 783 784 if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { 785 // Set the ResourcesImpl, updating it for all users of this Resources 786 // object. 787 resources.setImpl(resourcesImpl); 788 } 789 } 790 } 791 } finally { 792 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 793 } 794 } 795 applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)796 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, 797 @Nullable CompatibilityInfo compat) { 798 try { 799 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 800 "ResourcesManager#applyConfigurationToResourcesLocked"); 801 802 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 803 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 804 + mResConfiguration.seq + ", newSeq=" + config.seq); 805 return false; 806 } 807 int changes = mResConfiguration.updateFrom(config); 808 // Things might have changed in display manager, so clear the cached displays. 809 mDisplays.clear(); 810 DisplayMetrics defaultDisplayMetrics = getDisplayMetrics(); 811 812 if (compat != null && (mResCompatibilityInfo == null || 813 !mResCompatibilityInfo.equals(compat))) { 814 mResCompatibilityInfo = compat; 815 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 816 | ActivityInfo.CONFIG_SCREEN_SIZE 817 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 818 } 819 820 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); 821 822 ApplicationPackageManager.configurationChanged(); 823 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 824 825 Configuration tmpConfig = null; 826 827 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 828 ResourcesKey key = mResourceImpls.keyAt(i); 829 ResourcesImpl r = mResourceImpls.valueAt(i).get(); 830 if (r != null) { 831 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 832 + r + " config to: " + config); 833 int displayId = key.mDisplayId; 834 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 835 DisplayMetrics dm = defaultDisplayMetrics; 836 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 837 if (!isDefaultDisplay || hasOverrideConfiguration) { 838 if (tmpConfig == null) { 839 tmpConfig = new Configuration(); 840 } 841 tmpConfig.setTo(config); 842 if (!isDefaultDisplay) { 843 // Get new DisplayMetrics based on the DisplayAdjustments given 844 // to the ResourcesImpl. Udate a copy if the CompatibilityInfo 845 // changed, because the ResourcesImpl object will handle the 846 // update internally. 847 DisplayAdjustments daj = r.getDisplayAdjustments(); 848 if (compat != null) { 849 daj = new DisplayAdjustments(daj); 850 daj.setCompatibilityInfo(compat); 851 } 852 dm = getDisplayMetrics(displayId, daj); 853 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); 854 } 855 if (hasOverrideConfiguration) { 856 tmpConfig.updateFrom(key.mOverrideConfiguration); 857 } 858 r.updateConfiguration(tmpConfig, dm, compat); 859 } else { 860 r.updateConfiguration(config, dm, compat); 861 } 862 //Slog.i(TAG, "Updated app resources " + v.getKey() 863 // + " " + r + ": " + r.getConfiguration()); 864 } else { 865 //Slog.i(TAG, "Removing old resources " + v.getKey()); 866 mResourceImpls.removeAt(i); 867 } 868 } 869 870 return changes != 0; 871 } finally { 872 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 873 } 874 } 875 876 /** 877 * Appends the library asset path to any ResourcesImpl object that contains the main 878 * assetPath. 879 * @param assetPath The main asset path for which to add the library asset path. 880 * @param libAsset The library asset path to add. 881 */ appendLibAssetForMainAssetPath(String assetPath, String libAsset)882 public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { 883 synchronized (this) { 884 // Record which ResourcesImpl need updating 885 // (and what ResourcesKey they should update to). 886 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); 887 888 final int implCount = mResourceImpls.size(); 889 for (int i = 0; i < implCount; i++) { 890 final ResourcesImpl impl = mResourceImpls.valueAt(i).get(); 891 final ResourcesKey key = mResourceImpls.keyAt(i); 892 if (impl != null && key.mResDir.equals(assetPath)) { 893 if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { 894 final int newLibAssetCount = 1 + 895 (key.mLibDirs != null ? key.mLibDirs.length : 0); 896 final String[] newLibAssets = new String[newLibAssetCount]; 897 if (key.mLibDirs != null) { 898 System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length); 899 } 900 newLibAssets[newLibAssetCount - 1] = libAsset; 901 902 updatedResourceKeys.put(impl, new ResourcesKey( 903 key.mResDir, 904 key.mSplitResDirs, 905 key.mOverlayDirs, 906 newLibAssets, 907 key.mDisplayId, 908 key.mOverrideConfiguration, 909 key.mCompatInfo)); 910 } 911 } 912 } 913 914 // Bail early if there is no work to do. 915 if (updatedResourceKeys.isEmpty()) { 916 return; 917 } 918 919 // Update any references to ResourcesImpl that require reloading. 920 final int resourcesCount = mResourceReferences.size(); 921 for (int i = 0; i < resourcesCount; i++) { 922 final Resources r = mResourceReferences.get(i).get(); 923 if (r != null) { 924 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 925 if (key != null) { 926 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 927 if (impl == null) { 928 throw new Resources.NotFoundException("failed to load " + libAsset); 929 } 930 r.setImpl(impl); 931 } 932 } 933 } 934 935 // Update any references to ResourcesImpl that require reloading for each Activity. 936 for (ActivityResources activityResources : mActivityResourceReferences.values()) { 937 final int resCount = activityResources.activityResources.size(); 938 for (int i = 0; i < resCount; i++) { 939 final Resources r = activityResources.activityResources.get(i).get(); 940 if (r != null) { 941 final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); 942 if (key != null) { 943 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); 944 if (impl == null) { 945 throw new Resources.NotFoundException("failed to load " + libAsset); 946 } 947 r.setImpl(impl); 948 } 949 } 950 } 951 } 952 } 953 } 954 } 955