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