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