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