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