• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.Display.INVALID_DISPLAY;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.pm.ActivityInfo;
27 import android.content.pm.ApplicationInfo;
28 import android.content.res.ApkAssets;
29 import android.content.res.AssetManager;
30 import android.content.res.CompatResources;
31 import android.content.res.CompatibilityInfo;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.content.res.ResourcesImpl;
35 import android.content.res.ResourcesKey;
36 import android.content.res.loader.ResourcesLoader;
37 import android.hardware.display.DisplayManagerGlobal;
38 import android.os.IBinder;
39 import android.os.Process;
40 import android.os.Trace;
41 import android.util.ArrayMap;
42 import android.util.ArraySet;
43 import android.util.DisplayMetrics;
44 import android.util.IndentingPrintWriter;
45 import android.util.Log;
46 import android.util.Pair;
47 import android.util.Slog;
48 import android.view.Display;
49 import android.view.DisplayAdjustments;
50 import android.view.DisplayInfo;
51 import android.window.WindowContext;
52 
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.util.ArrayUtils;
55 
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.lang.ref.Reference;
59 import java.lang.ref.ReferenceQueue;
60 import java.lang.ref.WeakReference;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.WeakHashMap;
68 import java.util.function.Function;
69 
70 /** @hide */
71 public class ResourcesManager {
72     static final String TAG = "ResourcesManager";
73     private static final boolean DEBUG = false;
74 
75     private static ResourcesManager sResourcesManager;
76 
77     /**
78      * Internal lock object
79      */
80     private final Object mLock = new Object();
81 
82     /**
83      * The global compatibility settings.
84      */
85     private CompatibilityInfo mResCompatibilityInfo;
86 
87     /**
88      * The global configuration upon which all Resources are based. Multi-window Resources
89      * apply their overrides to this configuration.
90      */
91     @UnsupportedAppUsage
92     private final Configuration mResConfiguration = new Configuration();
93 
94     /**
95      * The display upon which all Resources are based. Activity, window token, and display context
96      * resources apply their overrides to this display id.
97      */
98     private int mResDisplayId = DEFAULT_DISPLAY;
99 
100     /**
101      * ApplicationInfo changes that need to be applied to Resources when the next configuration
102      * change occurs.
103      */
104     private ArrayList<Pair<String[], ApplicationInfo>> mPendingAppInfoUpdates;
105 
106     /**
107      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
108      * which should be reused as much as possible.
109      */
110     @UnsupportedAppUsage
111     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
112             new ArrayMap<>();
113 
114     /**
115      * A list of Resource references that can be reused.
116      */
117     @UnsupportedAppUsage
118     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
119     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
120 
121     private static class ApkKey {
122         public final String path;
123         public final boolean sharedLib;
124         public final boolean overlay;
125 
ApkKey(String path, boolean sharedLib, boolean overlay)126         ApkKey(String path, boolean sharedLib, boolean overlay) {
127             this.path = path;
128             this.sharedLib = sharedLib;
129             this.overlay = overlay;
130         }
131 
132         @Override
hashCode()133         public int hashCode() {
134             int result = 1;
135             result = 31 * result + this.path.hashCode();
136             result = 31 * result + Boolean.hashCode(this.sharedLib);
137             result = 31 * result + Boolean.hashCode(this.overlay);
138             return result;
139         }
140 
141         @Override
equals(@ullable Object obj)142         public boolean equals(@Nullable Object obj) {
143             if (!(obj instanceof ApkKey)) {
144                 return false;
145             }
146             ApkKey other = (ApkKey) obj;
147             return this.path.equals(other.path) && this.sharedLib == other.sharedLib
148                     && this.overlay == other.overlay;
149         }
150     }
151 
152     /**
153      * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
154      * instance is alive and reachable.
155      */
156     private class ApkAssetsSupplier {
157         final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
158 
159         /**
160          * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
161          * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
162          * cache.
163          */
load(final ApkKey apkKey)164         ApkAssets load(final ApkKey apkKey) throws IOException {
165             ApkAssets apkAssets = mLocalCache.get(apkKey);
166             if (apkAssets == null) {
167                 apkAssets = loadApkAssets(apkKey);
168                 mLocalCache.put(apkKey, apkAssets);
169             }
170             return apkAssets;
171         }
172     }
173 
174     /**
175      * The ApkAssets that are being referenced in the wild that we can reuse.
176      * Used as a lock for itself as well.
177      */
178     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
179 
180     /**
181      * Class containing the base configuration override and set of resources associated with an
182      * {@link Activity} or a {@link WindowContext}.
183      */
184     private static class ActivityResources {
185         /**
186          * Override config to apply to all resources associated with the token this instance is
187          * based on.
188          *
189          * @see #activityResources
190          * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer,
191          * Configuration, CompatibilityInfo, ClassLoader, List)
192          */
193         public final Configuration overrideConfig = new Configuration();
194 
195         /**
196          * The display to apply to all resources associated with the token this instance is based
197          * on.
198          */
199         public int overrideDisplayId;
200 
201         /** List of {@link ActivityResource} associated with the token this instance is based on. */
202         public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
203 
204         public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
205 
206         @UnsupportedAppUsage
ActivityResources()207         private ActivityResources() {}
208 
209         /** Returns the number of live resource references within {@code activityResources}. */
countLiveReferences()210         public int countLiveReferences() {
211             int count = 0;
212             for (int i = 0; i < activityResources.size(); i++) {
213                 WeakReference<Resources> resources = activityResources.get(i).resources;
214                 if (resources != null && resources.get() != null) {
215                     count++;
216                 }
217             }
218             return count;
219         }
220     }
221 
222     /**
223      * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information
224      * about how this resource expects its configuration to differ from the token's.
225      *
226      * @see ActivityResources
227      */
228     // TODO: Ideally this class should be called something token related, like TokenBasedResource.
229     private static class ActivityResource {
230         /**
231          * The override configuration applied on top of the token's override config for this
232          * resource.
233          */
234         public final Configuration overrideConfig = new Configuration();
235 
236         /**
237          * If non-null this resource expects its configuration to override the display from the
238          * token's configuration.
239          *
240          * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
241          */
242         @Nullable
243         public Integer overrideDisplayId;
244 
245         @Nullable
246         public WeakReference<Resources> resources;
247 
ActivityResource()248         private ActivityResource() {}
249     }
250 
251     /**
252      * Each Activity or WindowToken may has a base override configuration that is applied to each
253      * Resources object, which in turn may have their own override configuration specified.
254      */
255     @UnsupportedAppUsage
256     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
257             new WeakHashMap<>();
258 
259     /**
260      * Callback implementation for handling updates to Resources objects.
261      */
262     private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
263 
264     /**
265      * The set of APK paths belonging to this process. This is used to disable incremental
266      * installation crash protections on these APKs so the app either behaves as expects or crashes.
267      */
268     private final ArraySet<String> mApplicationOwnedApks = new ArraySet<>();
269 
270     @UnsupportedAppUsage
ResourcesManager()271     public ResourcesManager() {
272     }
273 
274     @UnsupportedAppUsage
getInstance()275     public static ResourcesManager getInstance() {
276         synchronized (ResourcesManager.class) {
277             if (sResourcesManager == null) {
278                 sResourcesManager = new ResourcesManager();
279             }
280             return sResourcesManager;
281         }
282     }
283 
284     /**
285      * Invalidate and destroy any resources that reference content under the
286      * given filesystem path. Typically used when unmounting a storage device to
287      * try as hard as possible to release any open FDs.
288      */
invalidatePath(String path)289     public void invalidatePath(String path) {
290         final List<ResourcesImpl> implsToFlush = new ArrayList<>();
291         synchronized (mLock) {
292             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
293                 final ResourcesKey key = mResourceImpls.keyAt(i);
294                 if (key.isPathReferenced(path)) {
295                     ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
296                     if (resImpl != null) {
297                         implsToFlush.add(resImpl);
298                     }
299                 }
300             }
301         }
302         for (int i = 0; i < implsToFlush.size(); i++) {
303             implsToFlush.get(i).flushLayoutCache();
304         }
305         final List<ApkAssets> assetsToClose = new ArrayList<>();
306         synchronized (mCachedApkAssets) {
307             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
308                 final ApkKey key = mCachedApkAssets.keyAt(i);
309                 if (key.path.equals(path)) {
310                     final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
311                     final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
312                     if (apkAssets != null) {
313                         assetsToClose.add(apkAssets);
314                     }
315                 }
316             }
317         }
318         for (int i = 0; i < assetsToClose.size(); i++) {
319             assetsToClose.get(i).close();
320         }
321         Log.i(TAG,
322                 "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
323     }
324 
getConfiguration()325     public Configuration getConfiguration() {
326         return mResConfiguration;
327     }
328 
329     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getDisplayMetrics()330     public DisplayMetrics getDisplayMetrics() {
331         return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
332     }
333 
334     /**
335      * Protected so that tests can override and returns something a fixed value.
336      */
337     @VisibleForTesting
getDisplayMetrics(int displayId, DisplayAdjustments da)338     protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
339         final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
340         final DisplayMetrics dm = new DisplayMetrics();
341         final DisplayInfo displayInfo = displayManagerGlobal != null
342                 ? displayManagerGlobal.getDisplayInfo(displayId) : null;
343         if (displayInfo != null) {
344             displayInfo.getAppMetrics(dm, da);
345         } else {
346             dm.setToDefaults();
347         }
348         return dm;
349     }
350 
351     /**
352      * Like getDisplayMetrics, but will adjust the result based on the display information in
353      * config. This is used to make sure that the global configuration matches the activity's
354      * apparent display.
355      */
getDisplayMetrics(Configuration config)356     private DisplayMetrics getDisplayMetrics(Configuration config) {
357         final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
358         final DisplayMetrics dm = new DisplayMetrics();
359         final DisplayInfo displayInfo = displayManagerGlobal != null
360                 ? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null;
361         if (displayInfo != null) {
362             displayInfo.getAppMetrics(dm,
363                     DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config);
364         } else {
365             dm.setToDefaults();
366         }
367         return dm;
368     }
369 
applyDisplayMetricsToConfiguration(@onNull DisplayMetrics dm, @NonNull Configuration config)370     private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
371             @NonNull Configuration config) {
372         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
373         config.densityDpi = dm.densityDpi;
374         config.screenWidthDp = (int) (dm.widthPixels / dm.density + 0.5f);
375         config.screenHeightDp = (int) (dm.heightPixels / dm.density + 0.5f);
376         int sl = Configuration.resetScreenLayout(config.screenLayout);
377         if (dm.widthPixels > dm.heightPixels) {
378             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
379             config.screenLayout = Configuration.reduceScreenLayout(sl,
380                     config.screenWidthDp, config.screenHeightDp);
381         } else {
382             config.orientation = Configuration.ORIENTATION_PORTRAIT;
383             config.screenLayout = Configuration.reduceScreenLayout(sl,
384                     config.screenHeightDp, config.screenWidthDp);
385         }
386         config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp);
387         config.compatScreenWidthDp = config.screenWidthDp;
388         config.compatScreenHeightDp = config.screenHeightDp;
389         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
390     }
391 
applyCompatConfiguration(int displayDensity, @NonNull Configuration compatConfiguration)392     public boolean applyCompatConfiguration(int displayDensity,
393             @NonNull Configuration compatConfiguration) {
394         synchronized (mLock) {
395             if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
396                 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
397                 return true;
398             }
399             return false;
400         }
401     }
402 
403     /**
404      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
405      * available.
406      *
407      * @param displayId display Id.
408      * @param resources The {@link Resources} backing the display adjustments.
409      */
getAdjustedDisplay(final int displayId, Resources resources)410     public Display getAdjustedDisplay(final int displayId, Resources resources) {
411         final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
412         if (dm == null) {
413             // may be null early in system startup
414             return null;
415         }
416         return dm.getCompatibleDisplay(displayId, resources);
417     }
418 
419     /**
420      * Initializes the set of APKs owned by the application running in this process.
421      */
initializeApplicationPaths(@onNull String sourceDir, @Nullable String[] splitDirs)422     public void initializeApplicationPaths(@NonNull String sourceDir,
423             @Nullable String[] splitDirs) {
424         synchronized (mLock) {
425             if (mApplicationOwnedApks.isEmpty()) {
426                 addApplicationPathsLocked(sourceDir, splitDirs);
427             }
428         }
429     }
430 
431     /**
432      * Updates the set of APKs owned by the application running in this process.
433      *
434      * This method only appends to the set of APKs owned by this process because the previous APKs
435      * paths still belong to the application running in this process.
436      */
addApplicationPathsLocked(@onNull String sourceDir, @Nullable String[] splitDirs)437     private void addApplicationPathsLocked(@NonNull String sourceDir,
438             @Nullable String[] splitDirs) {
439         mApplicationOwnedApks.add(sourceDir);
440         if (splitDirs != null) {
441             mApplicationOwnedApks.addAll(Arrays.asList(splitDirs));
442         }
443     }
444 
overlayPathToIdmapPath(String path)445     private static String overlayPathToIdmapPath(String path) {
446         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
447     }
448 
loadApkAssets(@onNull final ApkKey key)449     private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
450         ApkAssets apkAssets;
451 
452         // Optimistically check if this ApkAssets exists somewhere else.
453         final WeakReference<ApkAssets> apkAssetsRef;
454         synchronized (mCachedApkAssets) {
455             apkAssetsRef = mCachedApkAssets.get(key);
456         }
457         if (apkAssetsRef != null) {
458             apkAssets = apkAssetsRef.get();
459             if (apkAssets != null && apkAssets.isUpToDate()) {
460                 return apkAssets;
461             }
462         }
463 
464         int flags = 0;
465         if (key.sharedLib) {
466             flags |= ApkAssets.PROPERTY_DYNAMIC;
467         }
468         if (mApplicationOwnedApks.contains(key.path)) {
469             flags |= ApkAssets.PROPERTY_DISABLE_INCREMENTAL_HARDENING;
470         }
471         if (key.overlay) {
472             apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), flags);
473         } else {
474             apkAssets = ApkAssets.loadFromPath(key.path, flags);
475         }
476 
477         synchronized (mCachedApkAssets) {
478             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
479         }
480 
481         return apkAssets;
482     }
483 
484     /**
485      * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
486      * AssetManagers mapped to the {@param key}.
487      */
extractApkKeys(@onNull final ResourcesKey key)488     private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
489         final ArrayList<ApkKey> apkKeys = new ArrayList<>();
490 
491         // resDir can be null if the 'android' package is creating a new Resources object.
492         // This is fine, since each AssetManager automatically loads the 'android' package
493         // already.
494         if (key.mResDir != null) {
495             apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
496         }
497 
498         if (key.mSplitResDirs != null) {
499             for (final String splitResDir : key.mSplitResDirs) {
500                 apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
501             }
502         }
503 
504         if (key.mLibDirs != null) {
505             for (final String libDir : key.mLibDirs) {
506                 // Avoid opening files we know do not have resources, like code-only .jar files.
507                 if (libDir.endsWith(".apk")) {
508                     apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
509                 }
510             }
511         }
512 
513         if (key.mOverlayPaths != null) {
514             for (final String idmapPath : key.mOverlayPaths) {
515                 apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
516             }
517         }
518 
519         return apkKeys;
520     }
521 
522     /**
523      * Creates an AssetManager from the paths within the ResourcesKey.
524      *
525      * This can be overridden in tests so as to avoid creating a real AssetManager with
526      * real APK paths.
527      * @param key The key containing the resource paths to add to the AssetManager.
528      * @return a new AssetManager.
529     */
530     @VisibleForTesting
531     @UnsupportedAppUsage
createAssetManager(@onNull final ResourcesKey key)532     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
533         return createAssetManager(key, /* apkSupplier */ null);
534     }
535 
536     /**
537      * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
538      * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
539      * {@link #loadApkAssets(ApkKey)}.
540      */
createAssetManager(@onNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)541     private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
542             @Nullable ApkAssetsSupplier apkSupplier) {
543         final AssetManager.Builder builder = new AssetManager.Builder();
544 
545         final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
546         for (int i = 0, n = apkKeys.size(); i < n; i++) {
547             final ApkKey apkKey = apkKeys.get(i);
548             try {
549                 builder.addApkAssets(
550                         (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
551             } catch (IOException e) {
552                 if (apkKey.overlay) {
553                     Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
554                 } else if (apkKey.sharedLib) {
555                     Log.w(TAG, String.format(
556                             "asset path '%s' does not exist or contains no resources",
557                             apkKey.path), e);
558                 } else {
559                     Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
560                     return null;
561                 }
562             }
563         }
564 
565         if (key.mLoaders != null) {
566             for (final ResourcesLoader loader : key.mLoaders) {
567                 builder.addLoader(loader);
568             }
569         }
570 
571         return builder.build();
572     }
573 
countLiveReferences(Collection<WeakReference<T>> collection)574     private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
575         int count = 0;
576         for (WeakReference<T> ref : collection) {
577             final T value = ref != null ? ref.get() : null;
578             if (value != null) {
579                 count++;
580             }
581         }
582         return count;
583     }
584 
585     /**
586      * @hide
587      */
dump(String prefix, PrintWriter printWriter)588     public void dump(String prefix, PrintWriter printWriter) {
589         final int references;
590         final int resImpls;
591         synchronized (mLock) {
592             int refs = countLiveReferences(mResourceReferences);
593             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
594                 refs += activityResources.countLiveReferences();
595             }
596             references = refs;
597             resImpls = countLiveReferences(mResourceImpls.values());
598         }
599         final int liveAssets;
600         synchronized (mCachedApkAssets) {
601             liveAssets = countLiveReferences(mCachedApkAssets.values());
602         }
603 
604         final var pw = new IndentingPrintWriter(printWriter, "  ");
605         for (int i = 0; i < prefix.length() / 2; i++) {
606             pw.increaseIndent();
607         }
608         pw.println("ResourcesManager:");
609         pw.increaseIndent();
610         pw.print("total apks: ");
611         pw.println(liveAssets);
612         pw.print("resources: ");
613         pw.println(references);
614         pw.print("resource impls: ");
615         pw.println(resImpls);
616     }
617 
generateConfig(@onNull ResourcesKey key)618     private Configuration generateConfig(@NonNull ResourcesKey key) {
619         Configuration config;
620         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
621         if (hasOverrideConfig) {
622             config = new Configuration(getConfiguration());
623             config.updateFrom(key.mOverrideConfiguration);
624             if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
625         } else {
626             config = getConfiguration();
627         }
628         return config;
629     }
630 
generateDisplayId(@onNull ResourcesKey key)631     private int generateDisplayId(@NonNull ResourcesKey key) {
632         return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId;
633     }
634 
createResourcesImpl(@onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)635     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
636             @Nullable ApkAssetsSupplier apkSupplier) {
637         final AssetManager assets = createAssetManager(key, apkSupplier);
638         if (assets == null) {
639             return null;
640         }
641 
642         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
643         daj.setCompatibilityInfo(key.mCompatInfo);
644 
645         final Configuration config = generateConfig(key);
646         final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
647         final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
648 
649         if (DEBUG) {
650             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
651         }
652         return impl;
653     }
654 
655     /**
656      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
657      *
658      * @param key The key to match.
659      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
660      */
findResourcesImplForKeyLocked(@onNull ResourcesKey key)661     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
662         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
663         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
664         if (impl != null && impl.getAssets().isUpToDate()) {
665             return impl;
666         }
667         return null;
668     }
669 
670     /**
671      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
672      * creates a new one and caches it for future use.
673      * @param key The key to match.
674      * @return a ResourcesImpl object matching the key.
675      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)676     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
677             @NonNull ResourcesKey key) {
678         return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
679     }
680 
681     /**
682      * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
683      * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
684      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)685     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
686             @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
687         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
688         if (impl == null) {
689             impl = createResourcesImpl(key, apkSupplier);
690             if (impl != null) {
691                 mResourceImpls.put(key, new WeakReference<>(impl));
692             }
693         }
694         return impl;
695     }
696 
697     /**
698      * Find the ResourcesKey that this ResourcesImpl object is associated with.
699      * @return the ResourcesKey or null if none was found.
700      */
findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)701     private @Nullable ResourcesKey findKeyForResourceImplLocked(
702             @NonNull ResourcesImpl resourceImpl) {
703         int refCount = mResourceImpls.size();
704         for (int i = 0; i < refCount; i++) {
705             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
706             if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) {
707                 return mResourceImpls.keyAt(i);
708             }
709         }
710         return null;
711     }
712 
713     /**
714      * Check if activity resources have same override config as the provided on.
715      * @param activityToken The Activity that resources should be associated with.
716      * @param overrideConfig The override configuration to be checked for equality with.
717      * @return true if activity resources override config matches the provided one or they are both
718      *         null, false otherwise.
719      */
isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)720     public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
721             @Nullable Configuration overrideConfig) {
722         synchronized (mLock) {
723             final ActivityResources activityResources
724                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
725             if (activityResources == null) {
726                 return overrideConfig == null;
727             } else {
728                 // The two configurations must either be equal or publicly equivalent to be
729                 // considered the same.
730                 return Objects.equals(activityResources.overrideConfig, overrideConfig)
731                         || (overrideConfig != null && activityResources.overrideConfig != null
732                                 && 0 == overrideConfig.diffPublicOnly(
733                                         activityResources.overrideConfig));
734             }
735         }
736     }
737 
getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)738     private ActivityResources getOrCreateActivityResourcesStructLocked(
739             @NonNull IBinder activityToken) {
740         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
741         if (activityResources == null) {
742             activityResources = new ActivityResources();
743             mActivityResourceReferences.put(activityToken, activityResources);
744         }
745         return activityResources;
746     }
747 
748     @Nullable
findResourcesForActivityLocked(@onNull IBinder targetActivityToken, @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader)749     private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
750             @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
751         ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
752                 targetActivityToken);
753 
754         final int size = activityResources.activityResources.size();
755         for (int index = 0; index < size; index++) {
756             ActivityResource activityResource = activityResources.activityResources.get(index);
757             Resources resources = activityResource.resources.get();
758             ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
759                     resources.getImpl());
760 
761             if (key != null
762                     && Objects.equals(resources.getClassLoader(), targetClassLoader)
763                     && Objects.equals(key, targetKey)) {
764                 return resources;
765             }
766         }
767 
768         return null;
769     }
770 
771     @NonNull
createResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)772     private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
773             @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
774             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
775             @NonNull CompatibilityInfo compatInfo) {
776         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
777                 activityToken);
778         cleanupReferences(activityResources.activityResources,
779                 activityResources.activityResourcesQueue,
780                 (r) -> r.resources);
781 
782         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
783                 : new Resources(classLoader);
784         resources.setImpl(impl);
785         resources.setCallbacks(mUpdateCallbacks);
786 
787         ActivityResource activityResource = new ActivityResource();
788         activityResource.resources = new WeakReference<>(resources,
789                 activityResources.activityResourcesQueue);
790         activityResource.overrideConfig.setTo(initialOverrideConfig);
791         activityResource.overrideDisplayId = overrideDisplayId;
792         activityResources.activityResources.add(activityResource);
793         if (DEBUG) {
794             Slog.d(TAG, "- creating new ref=" + resources);
795             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
796         }
797         return resources;
798     }
799 
createResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)800     private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
801             @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
802         cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
803 
804         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
805                 : new Resources(classLoader);
806         resources.setImpl(impl);
807         resources.setCallbacks(mUpdateCallbacks);
808         mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
809         if (DEBUG) {
810             Slog.d(TAG, "- creating new ref=" + resources);
811             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
812         }
813         return resources;
814     }
815 
816     /**
817      * Creates base resources for a binder token. Calls to
818      *
819      * {@link #getResources(IBinder, String, String[], String[], String[], String[], Integer,
820      * Configuration, CompatibilityInfo, ClassLoader, List)} with the same binder token will have
821      * their override configurations merged with the one specified here.
822      *
823      * @param token Represents an {@link Activity} or {@link WindowContext}.
824      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
825      * @param splitResDirs An array of split resource paths. Can be null.
826      * @param legacyOverlayDirs An array of overlay APK paths. Can be null.
827      * @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
828      * @param libDirs An array of resource library paths. Can be null.
829      * @param displayId The ID of the display for which to create the resources.
830      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
831      *                       {@code null}. This provides the base override for this token.
832      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
833      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
834      * @param classLoader The class loader to use when inflating Resources. If null, the
835      *                    {@link ClassLoader#getSystemClassLoader()} is used.
836      * @return a Resources object from which to access resources.
837      */
createBaseTokenResources(@onNull IBinder token, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)838     public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
839             @Nullable String resDir,
840             @Nullable String[] splitResDirs,
841             @Nullable String[] legacyOverlayDirs,
842             @Nullable String[] overlayPaths,
843             @Nullable String[] libDirs,
844             int displayId,
845             @Nullable Configuration overrideConfig,
846             @NonNull CompatibilityInfo compatInfo,
847             @Nullable ClassLoader classLoader,
848             @Nullable List<ResourcesLoader> loaders) {
849         try {
850             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
851                     "ResourcesManager#createBaseActivityResources");
852             final ResourcesKey key = new ResourcesKey(
853                     resDir,
854                     splitResDirs,
855                     combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
856                     libDirs,
857                     displayId,
858                     overrideConfig,
859                     compatInfo,
860                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
861             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
862 
863             if (DEBUG) {
864                 Slog.d(TAG, "createBaseActivityResources activity=" + token
865                         + " with key=" + key);
866             }
867 
868             synchronized (mLock) {
869                 // Force the creation of an ActivityResourcesStruct.
870                 getOrCreateActivityResourcesStructLocked(token);
871             }
872 
873             // Update any existing Activity Resources references.
874             updateResourcesForActivity(token, overrideConfig, displayId);
875 
876             synchronized (mLock) {
877                 Resources resources = findResourcesForActivityLocked(token, key,
878                         classLoader);
879                 if (resources != null) {
880                     return resources;
881                 }
882             }
883 
884             // Now request an actual Resources object.
885             return createResourcesForActivity(token, key,
886                     /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
887                     classLoader, /* apkSupplier */ null);
888         } finally {
889             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
890         }
891     }
892 
893     /**
894      * Rebases a key's override config on top of the Activity's base override.
895      *
896      * @param activityToken the token the supplied {@code key} is derived from.
897      * @param key the key to rebase
898      * @param overridesActivityDisplay whether this key is overriding the display from the token
899      */
rebaseKeyForActivity(IBinder activityToken, ResourcesKey key, boolean overridesActivityDisplay)900     private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key,
901             boolean overridesActivityDisplay) {
902         synchronized (mLock) {
903             final ActivityResources activityResources =
904                     getOrCreateActivityResourcesStructLocked(activityToken);
905 
906             if (key.mDisplayId == INVALID_DISPLAY) {
907                 key.mDisplayId = activityResources.overrideDisplayId;
908             }
909 
910             Configuration config;
911             if (key.hasOverrideConfiguration()) {
912                 config = new Configuration(activityResources.overrideConfig);
913                 config.updateFrom(key.mOverrideConfiguration);
914             } else {
915                 config = activityResources.overrideConfig;
916             }
917 
918             if (overridesActivityDisplay
919                     && key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) {
920                 if (!key.hasOverrideConfiguration()) {
921                     // Make a copy to handle the case where the override config is set to defaults.
922                     config = new Configuration(config);
923                 }
924 
925                 // If this key is overriding the display from the token and the key's
926                 // window config app bounds is null we need to explicitly override this to
927                 // ensure the display adjustments are as expected.
928                 config.windowConfiguration.setAppBounds(null);
929             }
930 
931             key.mOverrideConfiguration.setTo(config);
932         }
933     }
934 
935     /**
936      * Rebases a key's override config with display metrics of the {@code overrideDisplay} paired
937      * with the {code displayAdjustments}.
938      *
939      * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
940      */
rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay)941     private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) {
942         final Configuration temp = new Configuration();
943 
944         DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
945         daj.setCompatibilityInfo(key.mCompatInfo);
946 
947         final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj);
948         applyDisplayMetricsToConfiguration(dm, temp);
949 
950         if (key.hasOverrideConfiguration()) {
951             temp.updateFrom(key.mOverrideConfiguration);
952         }
953         key.mOverrideConfiguration.setTo(temp);
954     }
955 
956     /**
957      * Check WeakReferences and remove any dead references so they don't pile up.
958      */
cleanupReferences(ArrayList<WeakReference<T>> references, ReferenceQueue<T> referenceQueue)959     private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
960             ReferenceQueue<T> referenceQueue) {
961         cleanupReferences(references, referenceQueue, Function.identity());
962     }
963 
964     /**
965      * Check WeakReferences and remove any dead references so they don't pile up.
966      */
cleanupReferences(ArrayList<C> referenceContainers, ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction)967     private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers,
968             ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) {
969         Reference<? extends T> enqueuedRef = referenceQueue.poll();
970         if (enqueuedRef == null) {
971             return;
972         }
973 
974         final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
975         for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) {
976             deadReferences.add(enqueuedRef);
977         }
978 
979         ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> {
980             WeakReference<T> ref = unwrappingFunction.apply(refContainer);
981             return ref == null || deadReferences.contains(ref);
982         });
983     }
984 
985     /**
986      * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
987      * into the supplier. This should be done while the lock is not held to prevent performing I/O
988      * while holding the lock.
989      */
createApkAssetsSupplierNotLocked(@onNull ResourcesKey key)990     private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
991         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
992                 "ResourcesManager#createApkAssetsSupplierNotLocked");
993         try {
994             if (DEBUG && Thread.holdsLock(mLock)) {
995                 Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
996                     + " is holding mLock", new Throwable());
997             }
998 
999             final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
1000             final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
1001             for (int i = 0, n = apkKeys.size(); i < n; i++) {
1002                 final ApkKey apkKey = apkKeys.get(i);
1003                 try {
1004                     supplier.load(apkKey);
1005                 } catch (IOException e) {
1006                     Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
1007                 }
1008             }
1009             return supplier;
1010         } finally {
1011             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1012         }
1013     }
1014 
1015     /**
1016      * Creates a Resources object set with a ResourcesImpl object matching the given key.
1017      *
1018      * @param key The key describing the parameters of the ResourcesImpl object.
1019      * @param classLoader The classloader to use for the Resources object.
1020      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
1021      * @return A Resources object that gets updated when
1022      *         {@link #applyConfigurationToResources(Configuration, CompatibilityInfo)}
1023      *         is called.
1024      */
1025     @Nullable
createResources(@onNull ResourcesKey key, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1026     private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
1027             @Nullable ApkAssetsSupplier apkSupplier) {
1028         synchronized (mLock) {
1029             if (DEBUG) {
1030                 Throwable here = new Throwable();
1031                 here.fillInStackTrace();
1032                 Slog.w(TAG, "!! Create resources for key=" + key, here);
1033             }
1034 
1035             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
1036             if (resourcesImpl == null) {
1037                 return null;
1038             }
1039 
1040             return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
1041         }
1042     }
1043 
1044     @Nullable
createResourcesForActivity(@onNull IBinder activityToken, @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1045     private Resources createResourcesForActivity(@NonNull IBinder activityToken,
1046             @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
1047             @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
1048             @Nullable ApkAssetsSupplier apkSupplier) {
1049         synchronized (mLock) {
1050             if (DEBUG) {
1051                 Throwable here = new Throwable();
1052                 here.fillInStackTrace();
1053                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
1054             }
1055 
1056             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
1057             if (resourcesImpl == null) {
1058                 return null;
1059             }
1060 
1061             return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
1062                     overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
1063         }
1064     }
1065 
1066     /**
1067      * Gets or creates a new Resources object associated with the IBinder token. References returned
1068      * by this method live as long as the Activity, meaning they can be cached and used by the
1069      * Activity even after a configuration change. If any other parameter is changed
1070      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
1071      * is updated and handed back to the caller. However, changing the class loader will result in a
1072      * new Resources object.
1073      * <p/>
1074      * If activityToken is null, a cached Resources object will be returned if it matches the
1075      * input parameters. Otherwise a new Resources object that satisfies these parameters is
1076      * returned.
1077      *
1078      * @param activityToken Represents an Activity. If null, global resources are assumed.
1079      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
1080      * @param splitResDirs An array of split resource paths. Can be null.
1081      * @param legacyOverlayDirs An array of overlay APK paths. Can be null.
1082      * @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
1083      * @param libDirs An array of resource library paths. Can be null.
1084      * @param overrideDisplayId The ID of the display for which the returned Resources should be
1085      * based. This will cause display-based configuration properties to override those of the base
1086      * Resources for the {@code activityToken}, or the global configuration if {@code activityToken}
1087      * is null.
1088      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
1089      * null. Mostly used with Activities that are in multi-window which may override width and
1090      * height properties from the base config.
1091      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
1092      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
1093      * @param classLoader The class loader to use when inflating Resources. If null, the
1094      * {@link ClassLoader#getSystemClassLoader()} is used.
1095      * @return a Resources object from which to access resources.
1096      */
1097     @Nullable
getResources( @ullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, @Nullable Integer overrideDisplayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)1098     public Resources getResources(
1099             @Nullable IBinder activityToken,
1100             @Nullable String resDir,
1101             @Nullable String[] splitResDirs,
1102             @Nullable String[] legacyOverlayDirs,
1103             @Nullable String[] overlayPaths,
1104             @Nullable String[] libDirs,
1105             @Nullable Integer overrideDisplayId,
1106             @Nullable Configuration overrideConfig,
1107             @NonNull CompatibilityInfo compatInfo,
1108             @Nullable ClassLoader classLoader,
1109             @Nullable List<ResourcesLoader> loaders) {
1110         try {
1111             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
1112             final ResourcesKey key = new ResourcesKey(
1113                     resDir,
1114                     splitResDirs,
1115                     combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
1116                     libDirs,
1117                     overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
1118                     overrideConfig,
1119                     compatInfo,
1120                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
1121             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
1122 
1123             // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
1124             // ResourcesManager lock is held.
1125             final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
1126 
1127             if (overrideDisplayId != null) {
1128                 rebaseKeyForDisplay(key, overrideDisplayId);
1129             }
1130 
1131             Resources resources;
1132             if (activityToken != null) {
1133                 Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
1134                 rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
1135                 resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
1136                         overrideDisplayId, classLoader, assetsSupplier);
1137             } else {
1138                 resources = createResources(key, classLoader, assetsSupplier);
1139             }
1140             return resources;
1141         } finally {
1142             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1143         }
1144     }
1145 
1146     /**
1147      * Updates an Activity's Resources object with overrideConfig. The Resources object
1148      * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
1149      * String[], String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still
1150      * valid and will have the updated configuration.
1151      *
1152      * @param activityToken The Activity token.
1153      * @param overrideConfig The configuration override to update.
1154      * @param displayId Id of the display where activity currently resides.
1155      */
updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig, int displayId)1156     public void updateResourcesForActivity(@NonNull IBinder activityToken,
1157             @Nullable Configuration overrideConfig, int displayId) {
1158         try {
1159             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1160                     "ResourcesManager#updateResourcesForActivity");
1161             if (displayId == INVALID_DISPLAY) {
1162                 throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY");
1163             }
1164             synchronized (mLock) {
1165                 final ActivityResources activityResources =
1166                         getOrCreateActivityResourcesStructLocked(activityToken);
1167 
1168                 boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
1169                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
1170                         && !movedToDifferentDisplay) {
1171                     // They are the same and no change of display id, no work to do.
1172                     return;
1173                 }
1174 
1175                 // Grab a copy of the old configuration so we can create the delta's of each
1176                 // Resources object associated with this Activity.
1177                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
1178 
1179                 // Update the Activity's base override.
1180                 if (overrideConfig != null) {
1181                     activityResources.overrideConfig.setTo(overrideConfig);
1182                 } else {
1183                     activityResources.overrideConfig.unset();
1184                 }
1185 
1186                 // Update the Activity's override display id.
1187                 activityResources.overrideDisplayId = displayId;
1188 
1189                 // If a application info update was scheduled to occur in this process but has not
1190                 // occurred yet, apply it now so the resources objects will have updated paths if
1191                 // the assets sequence changed.
1192                 applyAllPendingAppInfoUpdates();
1193 
1194                 if (DEBUG) {
1195                     Throwable here = new Throwable();
1196                     here.fillInStackTrace();
1197                     Slog.d(TAG, "updating resources override for activity=" + activityToken
1198                             + " from oldConfig="
1199                             + Configuration.resourceQualifierString(oldConfig)
1200                             + " to newConfig="
1201                             + Configuration.resourceQualifierString(
1202                             activityResources.overrideConfig) + " displayId=" + displayId,
1203                             here);
1204                 }
1205 
1206 
1207                 // Rebase each Resources associated with this Activity.
1208                 final int refCount = activityResources.activityResources.size();
1209                 for (int i = 0; i < refCount; i++) {
1210                     final ActivityResource activityResource =
1211                             activityResources.activityResources.get(i);
1212 
1213                     final Resources resources = activityResource.resources.get();
1214                     if (resources == null) {
1215                         continue;
1216                     }
1217 
1218                     final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
1219                             overrideConfig, displayId);
1220                     if (newKey == null) {
1221                         continue;
1222                     }
1223 
1224                     // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
1225                     // constructions.
1226                     final ResourcesImpl resourcesImpl =
1227                             findOrCreateResourcesImplForKeyLocked(newKey);
1228                     if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
1229                         // Set the ResourcesImpl, updating it for all users of this Resources
1230                         // object.
1231                         resources.setImpl(resourcesImpl);
1232                     }
1233                 }
1234             }
1235         } finally {
1236             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1237         }
1238     }
1239 
1240     /**
1241      * Rebases an updated override config over any old override config and returns the new one
1242      * that an Activity's Resources should be set to.
1243      */
1244     @Nullable
rebaseActivityOverrideConfig(@onNull ActivityResource activityResource, @Nullable Configuration newOverrideConfig, int displayId)1245     private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource,
1246             @Nullable Configuration newOverrideConfig, int displayId) {
1247         final Resources resources = activityResource.resources.get();
1248         if (resources == null) {
1249             return null;
1250         }
1251 
1252         // Extract the ResourcesKey that was last used to create the Resources for this
1253         // activity.
1254         final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1255         if (oldKey == null) {
1256             Slog.e(TAG, "can't find ResourcesKey for resources impl="
1257                     + resources.getImpl());
1258             return null;
1259         }
1260 
1261         // Build the new override configuration for this ResourcesKey.
1262         final Configuration rebasedOverrideConfig = new Configuration();
1263         if (newOverrideConfig != null) {
1264             rebasedOverrideConfig.setTo(newOverrideConfig);
1265         }
1266 
1267         final Integer overrideDisplayId = activityResource.overrideDisplayId;
1268         if (overrideDisplayId != null) {
1269             DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig);
1270             displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig);
1271             displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo);
1272 
1273             DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments);
1274             applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig);
1275         }
1276 
1277         final boolean hasOverrideConfig =
1278                 !activityResource.overrideConfig.equals(Configuration.EMPTY);
1279         if (hasOverrideConfig) {
1280             rebasedOverrideConfig.updateFrom(activityResource.overrideConfig);
1281         }
1282 
1283         if (activityResource.overrideDisplayId != null
1284                 && activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) {
1285             // If this activity resource is overriding the display from the token and the key's
1286             // window config app bounds is null we need to explicitly override this to
1287             // ensure the display adjustments are as expected.
1288             rebasedOverrideConfig.windowConfiguration.setAppBounds(null);
1289         }
1290 
1291         // Ensure the new key keeps the expected override display instead of the new token display.
1292         displayId = overrideDisplayId != null ? overrideDisplayId : displayId;
1293 
1294         // Create the new ResourcesKey with the rebased override config.
1295         final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
1296                 oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs,
1297                 displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
1298 
1299         if (DEBUG) {
1300             Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
1301                     + " to newKey=" + newKey + ", displayId=" + displayId);
1302         }
1303 
1304         return newKey;
1305     }
1306 
appendPendingAppInfoUpdate(@onNull String[] oldSourceDirs, @NonNull ApplicationInfo appInfo)1307     public void appendPendingAppInfoUpdate(@NonNull String[] oldSourceDirs,
1308             @NonNull ApplicationInfo appInfo) {
1309         synchronized (mLock) {
1310             if (mPendingAppInfoUpdates == null) {
1311                 mPendingAppInfoUpdates = new ArrayList<>();
1312             }
1313             // Clear previous app info changes for a package to prevent multiple ResourcesImpl
1314             // recreations when the recreation caused by this update completely overrides the
1315             // previous pending changes.
1316             for (int i = mPendingAppInfoUpdates.size() - 1; i >= 0; i--) {
1317                 if (ArrayUtils.containsAll(oldSourceDirs, mPendingAppInfoUpdates.get(i).first)) {
1318                     mPendingAppInfoUpdates.remove(i);
1319                 }
1320             }
1321             mPendingAppInfoUpdates.add(new Pair<>(oldSourceDirs, appInfo));
1322         }
1323     }
1324 
applyAllPendingAppInfoUpdates()1325     public final void applyAllPendingAppInfoUpdates() {
1326         synchronized (mLock) {
1327             if (mPendingAppInfoUpdates != null) {
1328                 for (int i = 0, n = mPendingAppInfoUpdates.size(); i < n; i++) {
1329                     final Pair<String[], ApplicationInfo> appInfo = mPendingAppInfoUpdates.get(i);
1330                     applyNewResourceDirsLocked(appInfo.first, appInfo.second);
1331                 }
1332                 mPendingAppInfoUpdates = null;
1333             }
1334         }
1335     }
1336 
applyConfigurationToResources(@onNull Configuration config, @Nullable CompatibilityInfo compat)1337     public final boolean applyConfigurationToResources(@NonNull Configuration config,
1338             @Nullable CompatibilityInfo compat) {
1339         synchronized (mLock) {
1340             try {
1341                 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1342                         "ResourcesManager#applyConfigurationToResources");
1343 
1344                 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
1345                     if (DEBUG || DEBUG_CONFIGURATION) {
1346                         Slog.v(TAG, "Skipping new config: curSeq="
1347                                 + mResConfiguration.seq + ", newSeq=" + config.seq);
1348                     }
1349                     return false;
1350                 }
1351 
1352                 int changes = mResConfiguration.updateFrom(config);
1353                 if (compat != null && (mResCompatibilityInfo == null
1354                         || !mResCompatibilityInfo.equals(compat))) {
1355                     mResCompatibilityInfo = compat;
1356                     changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
1357                             | ActivityInfo.CONFIG_SCREEN_SIZE
1358                             | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
1359                 }
1360 
1361                 // If a application info update was scheduled to occur in this process but has not
1362                 // occurred yet, apply it now so the resources objects will have updated paths when
1363                 // the assets sequence changes.
1364                 if ((changes & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
1365                     applyAllPendingAppInfoUpdates();
1366                 }
1367 
1368                 final DisplayMetrics displayMetrics = getDisplayMetrics(config);
1369                 Resources.updateSystemConfiguration(config, displayMetrics, compat);
1370 
1371                 ApplicationPackageManager.configurationChanged();
1372 
1373                 Configuration tmpConfig = new Configuration();
1374 
1375                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1376                     ResourcesKey key = mResourceImpls.keyAt(i);
1377                     WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1378                     ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
1379                     if (r != null) {
1380                         applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
1381                     } else {
1382                         mResourceImpls.removeAt(i);
1383                     }
1384                 }
1385 
1386                 return changes != 0;
1387             } finally {
1388                 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1389             }
1390         }
1391     }
1392 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat, Configuration tmpConfig, ResourcesKey key, ResourcesImpl resourcesImpl)1393     private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
1394             @Nullable CompatibilityInfo compat, Configuration tmpConfig,
1395             ResourcesKey key, ResourcesImpl resourcesImpl) {
1396         if (DEBUG || DEBUG_CONFIGURATION) {
1397             Slog.v(TAG, "Changing resources "
1398                     + resourcesImpl + " config to: " + config);
1399         }
1400 
1401         tmpConfig.setTo(config);
1402         if (key.hasOverrideConfiguration()) {
1403             tmpConfig.updateFrom(key.mOverrideConfiguration);
1404         }
1405 
1406         // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
1407         // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
1408         // update internally.
1409         DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
1410         if (compat != null) {
1411             daj = new DisplayAdjustments(daj);
1412             daj.setCompatibilityInfo(compat);
1413         }
1414         daj.setConfiguration(tmpConfig);
1415         DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj);
1416 
1417         resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
1418     }
1419 
1420     /**
1421      * Appends the library asset path to any ResourcesImpl object that contains the main
1422      * assetPath.
1423      * @param assetPath The main asset path for which to add the library asset path.
1424      * @param libAsset The library asset path to add.
1425      */
1426     @UnsupportedAppUsage
appendLibAssetForMainAssetPath(String assetPath, String libAsset)1427     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
1428         appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset });
1429     }
1430 
1431     /**
1432      * Appends the library asset paths to any ResourcesImpl object that contains the main
1433      * assetPath.
1434      * @param assetPath The main asset path for which to add the library asset path.
1435      * @param libAssets The library asset paths to add.
1436      */
appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets)1437     public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
1438         synchronized (mLock) {
1439             // Record which ResourcesImpl need updating
1440             // (and what ResourcesKey they should update to).
1441             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1442 
1443             final int implCount = mResourceImpls.size();
1444             for (int i = 0; i < implCount; i++) {
1445                 final ResourcesKey key = mResourceImpls.keyAt(i);
1446                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1447                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1448                 if (impl != null && Objects.equals(key.mResDir, assetPath)) {
1449                     String[] newLibAssets = key.mLibDirs;
1450                     for (String libAsset : libAssets) {
1451                         newLibAssets =
1452                                 ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
1453                     }
1454 
1455                     if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
1456                         updatedResourceKeys.put(impl, new ResourcesKey(
1457                                 key.mResDir,
1458                                 key.mSplitResDirs,
1459                                 key.mOverlayPaths,
1460                                 newLibAssets,
1461                                 key.mDisplayId,
1462                                 key.mOverrideConfiguration,
1463                                 key.mCompatInfo,
1464                                 key.mLoaders));
1465                     }
1466                 }
1467             }
1468 
1469             redirectResourcesToNewImplLocked(updatedResourceKeys);
1470         }
1471     }
1472 
applyNewResourceDirsLocked(@ullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo)1473     private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
1474             @NonNull final ApplicationInfo appInfo) {
1475         try {
1476             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1477                     "ResourcesManager#applyNewResourceDirsLocked");
1478 
1479             String baseCodePath = appInfo.getBaseCodePath();
1480 
1481             final int myUid = Process.myUid();
1482             String[] newSplitDirs = appInfo.uid == myUid
1483                     ? appInfo.splitSourceDirs
1484                     : appInfo.splitPublicSourceDirs;
1485 
1486             // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
1487             String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
1488             String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs,
1489                     appInfo.overlayPaths);
1490 
1491             if (appInfo.uid == myUid) {
1492                 addApplicationPathsLocked(baseCodePath, copiedSplitDirs);
1493             }
1494 
1495             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1496             final int implCount = mResourceImpls.size();
1497             for (int i = 0; i < implCount; i++) {
1498                 final ResourcesKey key = mResourceImpls.keyAt(i);
1499                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1500                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1501 
1502                 if (impl == null) {
1503                     continue;
1504                 }
1505 
1506                 if (key.mResDir == null
1507                         || key.mResDir.equals(baseCodePath)
1508                         || ArrayUtils.contains(oldSourceDirs, key.mResDir)) {
1509                     updatedResourceKeys.put(impl, new ResourcesKey(
1510                             baseCodePath,
1511                             copiedSplitDirs,
1512                             copiedResourceDirs,
1513                             key.mLibDirs,
1514                             key.mDisplayId,
1515                             key.mOverrideConfiguration,
1516                             key.mCompatInfo,
1517                             key.mLoaders
1518                     ));
1519                 }
1520             }
1521 
1522             redirectResourcesToNewImplLocked(updatedResourceKeys);
1523         } finally {
1524             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1525         }
1526     }
1527 
1528     /**
1529      * Creates an array with the contents of {@param overlayPaths} and the unique elements of
1530      * {@param resourceDirs}.
1531      *
1532      * {@link ApplicationInfo#resourceDirs} only contains paths of overlays APKs.
1533      * {@link ApplicationInfo#overlayPaths} was created to contain paths of overlay of varying file
1534      * formats. It also contains the contents of {@code resourceDirs} because the order of loaded
1535      * overlays matter. In case {@code resourceDirs} contains overlay APK paths that are not present
1536      * in overlayPaths (perhaps an app inserted an additional overlay path into a
1537      * {@code resourceDirs}), this method is used to combine the contents of {@code resourceDirs}
1538      * that do not exist in {@code overlayPaths}} and {@code overlayPaths}}.
1539      */
1540     @Nullable
combinedOverlayPaths(@ullable String[] resourceDirs, @Nullable String[] overlayPaths)1541     private static String[] combinedOverlayPaths(@Nullable String[] resourceDirs,
1542             @Nullable String[] overlayPaths) {
1543         if (resourceDirs == null) {
1544             return ArrayUtils.cloneOrNull(overlayPaths);
1545         } else if(overlayPaths == null) {
1546             return ArrayUtils.cloneOrNull(resourceDirs);
1547         } else {
1548             final ArrayList<String> paths = new ArrayList<>();
1549             for (final String path : overlayPaths) {
1550                 paths.add(path);
1551             }
1552             for (final String path : resourceDirs) {
1553                 if (!paths.contains(path)) {
1554                     paths.add(path);
1555                 }
1556             }
1557             return paths.toArray(new String[0]);
1558         }
1559     }
1560 
redirectResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1561     private void redirectResourcesToNewImplLocked(
1562             @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
1563         // Bail early if there is no work to do.
1564         if (updatedResourceKeys.isEmpty()) {
1565             return;
1566         }
1567 
1568         // Update any references to ResourcesImpl that require reloading.
1569         final int resourcesCount = mResourceReferences.size();
1570         for (int i = 0; i < resourcesCount; i++) {
1571             final WeakReference<Resources> ref = mResourceReferences.get(i);
1572             final Resources r = ref != null ? ref.get() : null;
1573             if (r != null) {
1574                 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1575                 if (key != null) {
1576                     final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1577                     if (impl == null) {
1578                         throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
1579                     }
1580                     r.setImpl(impl);
1581                 }
1582             }
1583         }
1584 
1585         // Update any references to ResourcesImpl that require reloading for each Activity.
1586         for (ActivityResources activityResources : mActivityResourceReferences.values()) {
1587             final int resCount = activityResources.activityResources.size();
1588             for (int i = 0; i < resCount; i++) {
1589                 final ActivityResource activityResource =
1590                         activityResources.activityResources.get(i);
1591                 final Resources r = activityResource != null
1592                         ? activityResource.resources.get() : null;
1593                 if (r != null) {
1594                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1595                     if (key != null) {
1596                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1597                         if (impl == null) {
1598                             throw new Resources.NotFoundException(
1599                                     "failed to redirect ResourcesImpl");
1600                         }
1601                         r.setImpl(impl);
1602                     }
1603                 }
1604             }
1605         }
1606     }
1607 
1608     private class UpdateHandler implements Resources.UpdateCallbacks {
1609 
1610         /**
1611          * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
1612          * instance uses.
1613          */
1614         @Override
onLoadersChanged(@onNull Resources resources, @NonNull List<ResourcesLoader> newLoader)1615         public void onLoadersChanged(@NonNull Resources resources,
1616                 @NonNull List<ResourcesLoader> newLoader) {
1617             synchronized (mLock) {
1618                 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1619                 if (oldKey == null) {
1620                     throw new IllegalArgumentException("Cannot modify resource loaders of"
1621                             + " ResourcesImpl not registered with ResourcesManager");
1622                 }
1623 
1624                 final ResourcesKey newKey = new ResourcesKey(
1625                         oldKey.mResDir,
1626                         oldKey.mSplitResDirs,
1627                         oldKey.mOverlayPaths,
1628                         oldKey.mLibDirs,
1629                         oldKey.mDisplayId,
1630                         oldKey.mOverrideConfiguration,
1631                         oldKey.mCompatInfo,
1632                         newLoader.toArray(new ResourcesLoader[0]));
1633 
1634                 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
1635                 resources.setImpl(impl);
1636             }
1637         }
1638 
1639         /**
1640          * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
1641          * {@code loader} to apply any changes of the set of {@link ApkAssets}.
1642          **/
1643         @Override
onLoaderUpdated(@onNull ResourcesLoader loader)1644         public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
1645             synchronized (mLock) {
1646                 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
1647                         new ArrayMap<>();
1648 
1649                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1650                     final ResourcesKey key = mResourceImpls.keyAt(i);
1651                     final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
1652                     if (impl == null || impl.refersTo(null)
1653                             || !ArrayUtils.contains(key.mLoaders, loader)) {
1654                         continue;
1655                     }
1656 
1657                     mResourceImpls.remove(key);
1658                     updatedResourceImplKeys.put(impl.get(), key);
1659                 }
1660 
1661                 redirectResourcesToNewImplLocked(updatedResourceImplKeys);
1662             }
1663         }
1664     }
1665 }
1666