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