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