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