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