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