ResourcesManager.java revision 98bf12f99989ba2550fac83ee48ecbb6f1582f07
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 17package android.app; 18 19import static android.app.ActivityThread.DEBUG_CONFIGURATION; 20 21import android.annotation.NonNull; 22import android.annotation.Nullable; 23import android.content.pm.ActivityInfo; 24import android.content.res.AssetManager; 25import android.content.res.CompatibilityInfo; 26import android.content.res.Configuration; 27import android.content.res.Resources; 28import android.content.res.ResourcesImpl; 29import android.content.res.ResourcesKey; 30import android.hardware.display.DisplayManagerGlobal; 31import android.os.IBinder; 32import android.os.Trace; 33import android.util.ArrayMap; 34import android.util.DisplayMetrics; 35import android.util.Log; 36import android.util.Pair; 37import android.util.Slog; 38import android.view.Display; 39import android.view.DisplayAdjustments; 40 41import com.android.internal.annotations.VisibleForTesting; 42import com.android.internal.util.ArrayUtils; 43 44import java.lang.ref.WeakReference; 45import java.util.ArrayList; 46import java.util.Objects; 47import java.util.WeakHashMap; 48import java.util.function.Predicate; 49 50/** @hide */ 51public 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 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 */ 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 145 public Configuration getConfiguration() { 146 synchronized (this) { 147 return mResConfiguration; 148 } 149 } 150 151 DisplayMetrics getDisplayMetrics() { 152 return getDisplayMetrics(Display.DEFAULT_DISPLAY); 153 } 154 155 /** 156 * Protected so that tests can override and returns something a fixed value. 157 */ 158 @VisibleForTesting 159 protected DisplayMetrics getDisplayMetrics(int displayId) { 160 DisplayMetrics dm = new DisplayMetrics(); 161 final Display display = 162 getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); 163 if (display != null) { 164 display.getMetrics(dm); 165 } else { 166 dm.setToDefaults(); 167 } 168 return dm; 169 } 170 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 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 */ 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 245 protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) { 246 AssetManager assets = new AssetManager(); 247 248 // resDir can be null if the 'android' package is creating a new Resources object. 249 // This is fine, since each AssetManager automatically loads the 'android' package 250 // already. 251 if (key.mResDir != null) { 252 if (assets.addAssetPath(key.mResDir) == 0) { 253 throw new IllegalArgumentException("failed to add asset path " + key.mResDir); 254 } 255 } 256 257 if (key.mSplitResDirs != null) { 258 for (final String splitResDir : key.mSplitResDirs) { 259 if (assets.addAssetPath(splitResDir) == 0) { 260 throw new IllegalArgumentException( 261 "failed to add split asset path " + splitResDir); 262 } 263 } 264 } 265 266 if (key.mOverlayDirs != null) { 267 for (final String idmapPath : key.mOverlayDirs) { 268 assets.addOverlayPath(idmapPath); 269 } 270 } 271 272 if (key.mLibDirs != null) { 273 for (final String libDir : key.mLibDirs) { 274 if (libDir.endsWith(".apk")) { 275 // Avoid opening files we know do not have resources, 276 // like code-only .jar files. 277 if (assets.addAssetPath(libDir) == 0) { 278 Log.w(TAG, "Asset path '" + libDir + 279 "' does not exist or contains no resources."); 280 } 281 } 282 } 283 } 284 return assets; 285 } 286 287 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { 288 Configuration config; 289 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); 290 final boolean hasOverrideConfig = key.hasOverrideConfiguration(); 291 if (!isDefaultDisplay || hasOverrideConfig) { 292 config = new Configuration(getConfiguration()); 293 if (!isDefaultDisplay) { 294 applyNonDefaultDisplayMetricsToConfiguration(dm, config); 295 } 296 if (hasOverrideConfig) { 297 config.updateFrom(key.mOverrideConfiguration); 298 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); 299 } 300 } else { 301 config = getConfiguration(); 302 } 303 return config; 304 } 305 306 private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { 307 AssetManager assets = createAssetManager(key); 308 DisplayMetrics dm = getDisplayMetrics(key.mDisplayId); 309 Configuration config = generateConfig(key, dm); 310 ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo); 311 if (DEBUG) { 312 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); 313 } 314 return impl; 315 } 316 317 /** 318 * Finds a cached ResourcesImpl object that matches the given ResourcesKey. 319 * 320 * @param key The key to match. 321 * @return a ResourcesImpl if the key matches a cache entry, null otherwise. 322 */ 323 private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) { 324 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); 325 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 326 if (impl != null && impl.getAssets().isUpToDate()) { 327 return impl; 328 } 329 return null; 330 } 331 332 /** 333 * Find the ResourcesKey that this ResourcesImpl object is associated with. 334 * @return the ResourcesKey or null if none was found. 335 */ 336 private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) { 337 final int refCount = mResourceImpls.size(); 338 for (int i = 0; i < refCount; i++) { 339 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); 340 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; 341 if (impl != null && resourceImpl == impl) { 342 return mResourceImpls.keyAt(i); 343 } 344 } 345 return null; 346 } 347 348 private ActivityResources getOrCreateActivityResourcesStructLocked( 349 @NonNull IBinder activityToken) { 350 ActivityResources activityResources = mActivityResourceReferences.get(activityToken); 351 if (activityResources == null) { 352 activityResources = new ActivityResources(); 353 mActivityResourceReferences.put(activityToken, activityResources); 354 } 355 return activityResources; 356 } 357 358 /** 359 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist 360 * or the class loader is different. 361 */ 362 private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, 363 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) { 364 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 365 activityToken); 366 367 final int refCount = activityResources.activityResources.size(); 368 for (int i = 0; i < refCount; i++) { 369 WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i); 370 Resources resources = weakResourceRef.get(); 371 372 if (resources != null 373 && Objects.equals(resources.getClassLoader(), classLoader) 374 && resources.getImpl() == impl) { 375 if (DEBUG) { 376 Slog.d(TAG, "- using existing ref=" + resources); 377 } 378 return resources; 379 } 380 } 381 382 Resources resources = new Resources(classLoader); 383 resources.setImpl(impl); 384 activityResources.activityResources.add(new WeakReference<>(resources)); 385 if (DEBUG) { 386 Slog.d(TAG, "- creating new ref=" + resources); 387 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 388 } 389 return resources; 390 } 391 392 /** 393 * Gets an existing Resources object if the class loader and ResourcesImpl are the same, 394 * otherwise creates a new Resources object. 395 */ 396 private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, 397 @NonNull ResourcesImpl impl) { 398 // Find an existing Resources that has this ResourcesImpl set. 399 final int refCount = mResourceReferences.size(); 400 for (int i = 0; i < refCount; i++) { 401 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); 402 Resources resources = weakResourceRef.get(); 403 if (resources != null && 404 Objects.equals(resources.getClassLoader(), classLoader) && 405 resources.getImpl() == impl) { 406 if (DEBUG) { 407 Slog.d(TAG, "- using existing ref=" + resources); 408 } 409 return resources; 410 } 411 } 412 413 // Create a new Resources reference and use the existing ResourcesImpl object. 414 Resources resources = new Resources(classLoader); 415 resources.setImpl(impl); 416 mResourceReferences.add(new WeakReference<>(resources)); 417 if (DEBUG) { 418 Slog.d(TAG, "- creating new ref=" + resources); 419 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 420 } 421 return resources; 422 } 423 424 /** 425 * Creates base resources for an Activity. Calls to 426 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 427 * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override 428 * configurations merged with the one specified here. 429 * 430 * @param activityToken Represents an Activity. 431 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 432 * @param splitResDirs An array of split resource paths. Can be null. 433 * @param overlayDirs An array of overlay paths. Can be null. 434 * @param libDirs An array of resource library paths. Can be null. 435 * @param displayId The ID of the display for which to create the resources. 436 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 437 * null. This provides the base override for this Activity. 438 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 439 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 440 * @param classLoader The class loader to use when inflating Resources. If null, the 441 * {@link ClassLoader#getSystemClassLoader()} is used. 442 * @return a Resources object from which to access resources. 443 */ 444 public Resources createBaseActivityResources(@NonNull IBinder activityToken, 445 @Nullable String resDir, 446 @Nullable String[] splitResDirs, 447 @Nullable String[] overlayDirs, 448 @Nullable String[] libDirs, 449 int displayId, 450 @Nullable Configuration overrideConfig, 451 @NonNull CompatibilityInfo compatInfo, 452 @Nullable ClassLoader classLoader) { 453 try { 454 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 455 "ResourcesManager#createBaseActivityResources"); 456 final ResourcesKey key = new ResourcesKey( 457 resDir, 458 splitResDirs, 459 overlayDirs, 460 libDirs, 461 displayId, 462 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 463 compatInfo); 464 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 465 466 if (DEBUG) { 467 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken 468 + " with key=" + key); 469 } 470 471 synchronized (this) { 472 // Force the creation of an ActivityResourcesStruct. 473 getOrCreateActivityResourcesStructLocked(activityToken); 474 } 475 476 // Update any existing Activity Resources references. 477 updateResourcesForActivity(activityToken, overrideConfig); 478 479 // Now request an actual Resources object. 480 return getOrCreateResources(activityToken, key, classLoader); 481 } finally { 482 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 483 } 484 } 485 486 /** 487 * Gets an existing Resources object set with a ResourcesImpl object matching the given key, 488 * or creates one if it doesn't exist. 489 * 490 * @param activityToken The Activity this Resources object should be associated with. 491 * @param key The key describing the parameters of the ResourcesImpl object. 492 * @param classLoader The classloader to use for the Resources object. 493 * If null, {@link ClassLoader#getSystemClassLoader()} is used. 494 * @return A Resources object that gets updated when 495 * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} 496 * is called. 497 */ 498 private Resources getOrCreateResources(@Nullable IBinder activityToken, 499 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { 500 synchronized (this) { 501 if (DEBUG) { 502 Throwable here = new Throwable(); 503 here.fillInStackTrace(); 504 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); 505 } 506 507 if (activityToken != null) { 508 final ActivityResources activityResources = 509 getOrCreateActivityResourcesStructLocked(activityToken); 510 511 // Clean up any dead references so they don't pile up. 512 ArrayUtils.unstableRemoveIf(activityResources.activityResources, 513 sEmptyReferencePredicate); 514 515 // Rebase the key's override config on top of the Activity's base override. 516 if (key.hasOverrideConfiguration() 517 && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { 518 final Configuration temp = new Configuration(activityResources.overrideConfig); 519 temp.updateFrom(key.mOverrideConfiguration); 520 key.mOverrideConfiguration.setTo(temp); 521 } 522 523 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 524 if (resourcesImpl != null) { 525 if (DEBUG) { 526 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 527 } 528 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, 529 resourcesImpl); 530 } 531 532 // We will create the ResourcesImpl object outside of holding this lock. 533 534 } else { 535 // Clean up any dead references so they don't pile up. 536 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 537 538 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 539 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 540 if (resourcesImpl != null) { 541 if (DEBUG) { 542 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 543 } 544 return getOrCreateResourcesLocked(classLoader, resourcesImpl); 545 } 546 547 // We will create the ResourcesImpl object outside of holding this lock. 548 } 549 } 550 551 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 552 ResourcesImpl resourcesImpl = createResourcesImpl(key); 553 554 synchronized (this) { 555 ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); 556 if (existingResourcesImpl != null) { 557 if (DEBUG) { 558 Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl 559 + " new impl=" + resourcesImpl); 560 } 561 resourcesImpl.getAssets().close(); 562 resourcesImpl = existingResourcesImpl; 563 } else { 564 // Add this ResourcesImpl to the cache. 565 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); 566 } 567 568 final Resources resources; 569 if (activityToken != null) { 570 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, 571 resourcesImpl); 572 } else { 573 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); 574 } 575 return resources; 576 } 577 } 578 579 /** 580 * Gets or creates a new Resources object associated with the IBinder token. References returned 581 * by this method live as long as the Activity, meaning they can be cached and used by the 582 * Activity even after a configuration change. If any other parameter is changed 583 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object 584 * is updated and handed back to the caller. However, changing the class loader will result in a 585 * new Resources object. 586 * <p/> 587 * If activityToken is null, a cached Resources object will be returned if it matches the 588 * input parameters. Otherwise a new Resources object that satisfies these parameters is 589 * returned. 590 * 591 * @param activityToken Represents an Activity. If null, global resources are assumed. 592 * @param resDir The base resource path. Can be null (only framework resources will be loaded). 593 * @param splitResDirs An array of split resource paths. Can be null. 594 * @param overlayDirs An array of overlay paths. Can be null. 595 * @param libDirs An array of resource library paths. Can be null. 596 * @param displayId The ID of the display for which to create the resources. 597 * @param overrideConfig The configuration to apply on top of the base configuration. Can be 598 * null. Mostly used with Activities that are in multi-window which may override width and 599 * height properties from the base config. 600 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is 601 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. 602 * @param classLoader The class loader to use when inflating Resources. If null, the 603 * {@link ClassLoader#getSystemClassLoader()} is used. 604 * @return a Resources object from which to access resources. 605 */ 606 public Resources getResources(@Nullable IBinder activityToken, 607 @Nullable String resDir, 608 @Nullable String[] splitResDirs, 609 @Nullable String[] overlayDirs, 610 @Nullable String[] libDirs, 611 int displayId, 612 @Nullable Configuration overrideConfig, 613 @NonNull CompatibilityInfo compatInfo, 614 @Nullable ClassLoader classLoader) { 615 try { 616 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 617 final ResourcesKey key = new ResourcesKey( 618 resDir, 619 splitResDirs, 620 overlayDirs, 621 libDirs, 622 displayId, 623 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 624 compatInfo); 625 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 626 return getOrCreateResources(activityToken, key, classLoader); 627 } finally { 628 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 629 } 630 } 631 632 /** 633 * Updates an Activity's Resources object with overrideConfig. The Resources object 634 * that was previously returned by 635 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, 636 * CompatibilityInfo, ClassLoader)} is 637 * still valid and will have the updated configuration. 638 * @param activityToken The Activity token. 639 * @param overrideConfig The configuration override to update. 640 */ 641 public void updateResourcesForActivity(@NonNull IBinder activityToken, 642 @Nullable Configuration overrideConfig) { 643 try { 644 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 645 "ResourcesManager#updateResourcesForActivity"); 646 synchronized (this) { 647 final ActivityResources activityResources = 648 getOrCreateActivityResourcesStructLocked(activityToken); 649 650 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) { 651 // They are the same, no work to do. 652 return; 653 } 654 655 // Grab a copy of the old configuration so we can create the delta's of each 656 // Resources object associated with this Activity. 657 final Configuration oldConfig = new Configuration(activityResources.overrideConfig); 658 659 // Update the Activity's base override. 660 if (overrideConfig != null) { 661 activityResources.overrideConfig.setTo(overrideConfig); 662 } else { 663 activityResources.overrideConfig.setToDefaults(); 664 } 665 666 if (DEBUG) { 667 Throwable here = new Throwable(); 668 here.fillInStackTrace(); 669 Slog.d(TAG, "updating resources override for activity=" + activityToken 670 + " from oldConfig=" 671 + Configuration.resourceQualifierString(oldConfig) 672 + " to newConfig=" 673 + Configuration.resourceQualifierString( 674 activityResources.overrideConfig), 675 here); 676 } 677 678 final boolean activityHasOverrideConfig = 679 !activityResources.overrideConfig.equals(Configuration.EMPTY); 680 681 // Rebase each Resources associated with this Activity. 682 final int refCount = activityResources.activityResources.size(); 683 for (int i = 0; i < refCount; i++) { 684 WeakReference<Resources> weakResRef = activityResources.activityResources.get( 685 i); 686 Resources resources = weakResRef.get(); 687 if (resources == null) { 688 continue; 689 } 690 691 // Extract the ResourcesKey that was last used to create the Resources for this 692 // activity. 693 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); 694 if (oldKey == null) { 695 Slog.e(TAG, "can't find ResourcesKey for resources impl=" 696 + resources.getImpl()); 697 continue; 698 } 699 700 // Build the new override configuration for this ResourcesKey. 701 final Configuration rebasedOverrideConfig = new Configuration(); 702 if (overrideConfig != null) { 703 rebasedOverrideConfig.setTo(overrideConfig); 704 } 705 706 if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) { 707 // Generate a delta between the old base Activity override configuration and 708 // the actual final override configuration that was used to figure out the 709 // real delta this Resources object wanted. 710 Configuration overrideOverrideConfig = Configuration.generateDelta( 711 oldConfig, oldKey.mOverrideConfiguration); 712 rebasedOverrideConfig.updateFrom(overrideOverrideConfig); 713 } 714 715 // Create the new ResourcesKey with the rebased override config. 716 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, 717 oldKey.mSplitResDirs, 718 oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId, 719 rebasedOverrideConfig, oldKey.mCompatInfo); 720 721 if (DEBUG) { 722 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey 723 + " to newKey=" + newKey); 724 } 725 726 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); 727 if (resourcesImpl == null) { 728 resourcesImpl = createResourcesImpl(newKey); 729 mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); 730 } 731 732 if (resourcesImpl != resources.getImpl()) { 733 // Set the ResourcesImpl, updating it for all users of this Resources 734 // object. 735 resources.setImpl(resourcesImpl); 736 } 737 } 738 } 739 } finally { 740 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 741 } 742 } 743 744 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, 745 @Nullable CompatibilityInfo compat) { 746 try { 747 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, 748 "ResourcesManager#applyConfigurationToResourcesLocked"); 749 750 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { 751 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" 752 + mResConfiguration.seq + ", newSeq=" + config.seq); 753 return false; 754 } 755 int changes = mResConfiguration.updateFrom(config); 756 // Things might have changed in display manager, so clear the cached displays. 757 mDisplays.clear(); 758 DisplayMetrics defaultDisplayMetrics = getDisplayMetrics(); 759 760 if (compat != null && (mResCompatibilityInfo == null || 761 !mResCompatibilityInfo.equals(compat))) { 762 mResCompatibilityInfo = compat; 763 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT 764 | ActivityInfo.CONFIG_SCREEN_SIZE 765 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; 766 } 767 768 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); 769 770 ApplicationPackageManager.configurationChanged(); 771 //Slog.i(TAG, "Configuration changed in " + currentPackageName()); 772 773 Configuration tmpConfig = null; 774 775 for (int i = mResourceImpls.size() - 1; i >= 0; i--) { 776 ResourcesKey key = mResourceImpls.keyAt(i); 777 ResourcesImpl r = mResourceImpls.valueAt(i).get(); 778 if (r != null) { 779 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " 780 + r + " config to: " + config); 781 int displayId = key.mDisplayId; 782 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); 783 DisplayMetrics dm = defaultDisplayMetrics; 784 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); 785 if (!isDefaultDisplay || hasOverrideConfiguration) { 786 if (tmpConfig == null) { 787 tmpConfig = new Configuration(); 788 } 789 tmpConfig.setTo(config); 790 if (!isDefaultDisplay) { 791 dm = getDisplayMetrics(displayId); 792 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); 793 } 794 if (hasOverrideConfiguration) { 795 tmpConfig.updateFrom(key.mOverrideConfiguration); 796 } 797 r.updateConfiguration(tmpConfig, dm, compat); 798 } else { 799 r.updateConfiguration(config, dm, compat); 800 } 801 //Slog.i(TAG, "Updated app resources " + v.getKey() 802 // + " " + r + ": " + r.getConfiguration()); 803 } else { 804 //Slog.i(TAG, "Removing old resources " + v.getKey()); 805 mResourceImpls.removeAt(i); 806 } 807 } 808 809 return changes != 0; 810 } finally { 811 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 812 } 813 } 814} 815