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