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