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.AssetManager;
25 import android.content.res.CompatibilityInfo;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.content.res.ResourcesImpl;
29 import android.content.res.ResourcesKey;
30 import android.hardware.display.DisplayManagerGlobal;
31 import android.os.IBinder;
32 import android.os.Trace;
33 import android.util.ArrayMap;
34 import android.util.DisplayMetrics;
35 import android.util.Log;
36 import android.util.Pair;
37 import android.util.Slog;
38 import android.view.Display;
39 import android.view.DisplayAdjustments;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.util.ArrayUtils;
43 
44 import java.lang.ref.WeakReference;
45 import java.util.ArrayList;
46 import java.util.Objects;
47 import java.util.WeakHashMap;
48 import java.util.function.Predicate;
49 
50 /** @hide */
51 public class ResourcesManager {
52     static final String TAG = "ResourcesManager";
53     private static final boolean DEBUG = false;
54 
55     private static ResourcesManager sResourcesManager;
56 
57     /**
58      * Predicate that returns true if a WeakReference is gc'ed.
59      */
60     private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
61             new Predicate<WeakReference<Resources>>() {
62                 @Override
63                 public boolean test(WeakReference<Resources> weakRef) {
64                     return weakRef == null || weakRef.get() == null;
65                 }
66             };
67 
68     /**
69      * The global compatibility settings.
70      */
71     private CompatibilityInfo mResCompatibilityInfo;
72 
73     /**
74      * The global configuration upon which all Resources are based. Multi-window Resources
75      * apply their overrides to this configuration.
76      */
77     private final Configuration mResConfiguration = new Configuration();
78 
79     /**
80      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
81      * which should be reused as much as possible.
82      */
83     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
84             new ArrayMap<>();
85 
86     /**
87      * A list of Resource references that can be reused.
88      */
89     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
90 
91     /**
92      * Resources and base configuration override associated with an Activity.
93      */
94     private static class ActivityResources {
95         public final Configuration overrideConfig = new Configuration();
96         public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
97     }
98 
99     /**
100      * Each Activity may has a base override configuration that is applied to each Resources object,
101      * which in turn may have their own override configuration specified.
102      */
103     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
104             new WeakHashMap<>();
105 
106     /**
107      * A cache of DisplayId to DisplayAdjustments.
108      */
109     private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
110             new ArrayMap<>();
111 
getInstance()112     public static ResourcesManager getInstance() {
113         synchronized (ResourcesManager.class) {
114             if (sResourcesManager == null) {
115                 sResourcesManager = new ResourcesManager();
116             }
117             return sResourcesManager;
118         }
119     }
120 
121     /**
122      * Invalidate and destroy any resources that reference content under the
123      * given filesystem path. Typically used when unmounting a storage device to
124      * try as hard as possible to release any open FDs.
125      */
invalidatePath(String path)126     public void invalidatePath(String path) {
127         synchronized (this) {
128             int count = 0;
129             for (int i = 0; i < mResourceImpls.size();) {
130                 final ResourcesKey key = mResourceImpls.keyAt(i);
131                 if (key.isPathReferenced(path)) {
132                     final ResourcesImpl res = mResourceImpls.removeAt(i).get();
133                     if (res != null) {
134                         res.flushLayoutCache();
135                     }
136                     count++;
137                 } else {
138                     i++;
139                 }
140             }
141             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
142         }
143     }
144 
getConfiguration()145     public Configuration getConfiguration() {
146         synchronized (this) {
147             return mResConfiguration;
148         }
149     }
150 
getDisplayMetrics()151     DisplayMetrics getDisplayMetrics() {
152         return getDisplayMetrics(Display.DEFAULT_DISPLAY,
153                 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
154     }
155 
156     /**
157      * Protected so that tests can override and returns something a fixed value.
158      */
159     @VisibleForTesting
getDisplayMetrics(int displayId, DisplayAdjustments da)160     protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
161         DisplayMetrics dm = new DisplayMetrics();
162         final Display display = getAdjustedDisplay(displayId, da);
163         if (display != null) {
164             display.getMetrics(dm);
165         } else {
166             dm.setToDefaults();
167         }
168         return dm;
169     }
170 
applyNonDefaultDisplayMetricsToConfiguration( @onNull DisplayMetrics dm, @NonNull Configuration config)171     private static void applyNonDefaultDisplayMetricsToConfiguration(
172             @NonNull DisplayMetrics dm, @NonNull Configuration config) {
173         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
174         config.densityDpi = dm.densityDpi;
175         config.screenWidthDp = (int) (dm.widthPixels / dm.density);
176         config.screenHeightDp = (int) (dm.heightPixels / dm.density);
177         int sl = Configuration.resetScreenLayout(config.screenLayout);
178         if (dm.widthPixels > dm.heightPixels) {
179             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
180             config.screenLayout = Configuration.reduceScreenLayout(sl,
181                     config.screenWidthDp, config.screenHeightDp);
182         } else {
183             config.orientation = Configuration.ORIENTATION_PORTRAIT;
184             config.screenLayout = Configuration.reduceScreenLayout(sl,
185                     config.screenHeightDp, config.screenWidthDp);
186         }
187         config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
188         config.compatScreenWidthDp = config.screenWidthDp;
189         config.compatScreenHeightDp = config.screenHeightDp;
190         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
191     }
192 
applyCompatConfigurationLocked(int displayDensity, @NonNull Configuration compatConfiguration)193     public boolean applyCompatConfigurationLocked(int displayDensity,
194             @NonNull Configuration compatConfiguration) {
195         if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
196             mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
197             return true;
198         }
199         return false;
200     }
201 
202     /**
203      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
204      * available.
205      *
206      * @param displayId display Id.
207      * @param displayAdjustments display adjustments.
208      */
getAdjustedDisplay(final int displayId, @Nullable DisplayAdjustments displayAdjustments)209     public Display getAdjustedDisplay(final int displayId,
210             @Nullable DisplayAdjustments displayAdjustments) {
211         final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
212                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
213         final Pair<Integer, DisplayAdjustments> key =
214                 Pair.create(displayId, displayAdjustmentsCopy);
215         synchronized (this) {
216             WeakReference<Display> wd = mDisplays.get(key);
217             if (wd != null) {
218                 final Display display = wd.get();
219                 if (display != null) {
220                     return display;
221                 }
222             }
223             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
224             if (dm == null) {
225                 // may be null early in system startup
226                 return null;
227             }
228             final Display display = dm.getCompatibleDisplay(displayId, key.second);
229             if (display != null) {
230                 mDisplays.put(key, new WeakReference<>(display));
231             }
232             return display;
233         }
234     }
235 
236     /**
237      * Creates an AssetManager from the paths within the ResourcesKey.
238      *
239      * This can be overridden in tests so as to avoid creating a real AssetManager with
240      * real APK paths.
241      * @param key The key containing the resource paths to add to the AssetManager.
242      * @return a new AssetManager.
243     */
244     @VisibleForTesting
createAssetManager(@onNull final ResourcesKey key)245     protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
246         AssetManager assets = new AssetManager();
247 
248         // resDir can be null if the 'android' package is creating a new Resources object.
249         // This is fine, since each AssetManager automatically loads the 'android' package
250         // already.
251         if (key.mResDir != null) {
252             if (assets.addAssetPath(key.mResDir) == 0) {
253                 throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
254             }
255         }
256 
257         if (key.mSplitResDirs != null) {
258             for (final String splitResDir : key.mSplitResDirs) {
259                 if (assets.addAssetPath(splitResDir) == 0) {
260                     throw new Resources.NotFoundException(
261                             "failed to add split asset path " + splitResDir);
262                 }
263             }
264         }
265 
266         if (key.mOverlayDirs != null) {
267             for (final String idmapPath : key.mOverlayDirs) {
268                 assets.addOverlayPath(idmapPath);
269             }
270         }
271 
272         if (key.mLibDirs != null) {
273             for (final String libDir : key.mLibDirs) {
274                 if (libDir.endsWith(".apk")) {
275                     // Avoid opening files we know do not have resources,
276                     // like code-only .jar files.
277                     if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
278                         Log.w(TAG, "Asset path '" + libDir +
279                                 "' does not exist or contains no resources.");
280                     }
281                 }
282             }
283         }
284         return assets;
285     }
286 
generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)287     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
288         Configuration config;
289         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
290         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
291         if (!isDefaultDisplay || hasOverrideConfig) {
292             config = new Configuration(getConfiguration());
293             if (!isDefaultDisplay) {
294                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
295             }
296             if (hasOverrideConfig) {
297                 config.updateFrom(key.mOverrideConfiguration);
298                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
299             }
300         } else {
301             config = getConfiguration();
302         }
303         return config;
304     }
305 
createResourcesImpl(@onNull ResourcesKey key)306     private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
307         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
308         daj.setCompatibilityInfo(key.mCompatInfo);
309 
310         final AssetManager assets = createAssetManager(key);
311         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
312         final Configuration config = generateConfig(key, dm);
313         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
314         if (DEBUG) {
315             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
316         }
317         return impl;
318     }
319 
320     /**
321      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
322      *
323      * @param key The key to match.
324      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
325      */
findResourcesImplForKeyLocked(@onNull ResourcesKey key)326     private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
327         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
328         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
329         if (impl != null && impl.getAssets().isUpToDate()) {
330             return impl;
331         }
332         return null;
333     }
334 
335     /**
336      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
337      * creates a new one and caches it for future use.
338      * @param key The key to match.
339      * @return a ResourcesImpl object matching the key.
340      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)341     private @NonNull ResourcesImpl findOrCreateResourcesImplForKeyLocked(
342             @NonNull ResourcesKey key) {
343         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
344         if (impl == null) {
345             impl = createResourcesImpl(key);
346             mResourceImpls.put(key, new WeakReference<>(impl));
347         }
348         return impl;
349     }
350 
351     /**
352      * Find the ResourcesKey that this ResourcesImpl object is associated with.
353      * @return the ResourcesKey or null if none was found.
354      */
findKeyForResourceImplLocked(@onNull ResourcesImpl resourceImpl)355     private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) {
356         final int refCount = mResourceImpls.size();
357         for (int i = 0; i < refCount; i++) {
358             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
359             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
360             if (impl != null && resourceImpl == impl) {
361                 return mResourceImpls.keyAt(i);
362             }
363         }
364         return null;
365     }
366 
getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)367     private ActivityResources getOrCreateActivityResourcesStructLocked(
368             @NonNull IBinder activityToken) {
369         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
370         if (activityResources == null) {
371             activityResources = new ActivityResources();
372             mActivityResourceReferences.put(activityToken, activityResources);
373         }
374         return activityResources;
375     }
376 
377     /**
378      * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
379      * or the class loader is different.
380      */
getOrCreateResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl)381     private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
382             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
383         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
384                 activityToken);
385 
386         final int refCount = activityResources.activityResources.size();
387         for (int i = 0; i < refCount; i++) {
388             WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
389             Resources resources = weakResourceRef.get();
390 
391             if (resources != null
392                     && Objects.equals(resources.getClassLoader(), classLoader)
393                     && resources.getImpl() == impl) {
394                 if (DEBUG) {
395                     Slog.d(TAG, "- using existing ref=" + resources);
396                 }
397                 return resources;
398             }
399         }
400 
401         Resources resources = new Resources(classLoader);
402         resources.setImpl(impl);
403         activityResources.activityResources.add(new WeakReference<>(resources));
404         if (DEBUG) {
405             Slog.d(TAG, "- creating new ref=" + resources);
406             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
407         }
408         return resources;
409     }
410 
411     /**
412      * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
413      * otherwise creates a new Resources object.
414      */
getOrCreateResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl)415     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
416             @NonNull ResourcesImpl impl) {
417         // Find an existing Resources that has this ResourcesImpl set.
418         final int refCount = mResourceReferences.size();
419         for (int i = 0; i < refCount; i++) {
420             WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
421             Resources resources = weakResourceRef.get();
422             if (resources != null &&
423                     Objects.equals(resources.getClassLoader(), classLoader) &&
424                     resources.getImpl() == impl) {
425                 if (DEBUG) {
426                     Slog.d(TAG, "- using existing ref=" + resources);
427                 }
428                 return resources;
429             }
430         }
431 
432         // Create a new Resources reference and use the existing ResourcesImpl object.
433         Resources resources = new Resources(classLoader);
434         resources.setImpl(impl);
435         mResourceReferences.add(new WeakReference<>(resources));
436         if (DEBUG) {
437             Slog.d(TAG, "- creating new ref=" + resources);
438             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
439         }
440         return resources;
441     }
442 
443     /**
444      * Creates base resources for an Activity. Calls to
445      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
446      * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
447      * configurations merged with the one specified here.
448      *
449      * @param activityToken Represents an Activity.
450      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
451      * @param splitResDirs An array of split resource paths. Can be null.
452      * @param overlayDirs An array of overlay paths. Can be null.
453      * @param libDirs An array of resource library paths. Can be null.
454      * @param displayId The ID of the display for which to create the resources.
455      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
456      *                       null. This provides the base override for this Activity.
457      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
458      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
459      * @param classLoader The class loader to use when inflating Resources. If null, the
460      *                    {@link ClassLoader#getSystemClassLoader()} is used.
461      * @return a Resources object from which to access resources.
462      */
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)463     public @NonNull Resources createBaseActivityResources(@NonNull IBinder activityToken,
464             @Nullable String resDir,
465             @Nullable String[] splitResDirs,
466             @Nullable String[] overlayDirs,
467             @Nullable String[] libDirs,
468             int displayId,
469             @Nullable Configuration overrideConfig,
470             @NonNull CompatibilityInfo compatInfo,
471             @Nullable ClassLoader classLoader) {
472         try {
473             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
474                     "ResourcesManager#createBaseActivityResources");
475             final ResourcesKey key = new ResourcesKey(
476                     resDir,
477                     splitResDirs,
478                     overlayDirs,
479                     libDirs,
480                     displayId,
481                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
482                     compatInfo);
483             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
484 
485             if (DEBUG) {
486                 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
487                         + " with key=" + key);
488             }
489 
490             synchronized (this) {
491                 // Force the creation of an ActivityResourcesStruct.
492                 getOrCreateActivityResourcesStructLocked(activityToken);
493             }
494 
495             // Update any existing Activity Resources references.
496             updateResourcesForActivity(activityToken, overrideConfig);
497 
498             // Now request an actual Resources object.
499             return getOrCreateResources(activityToken, key, classLoader);
500         } finally {
501             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
502         }
503     }
504 
505     /**
506      * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
507      * or creates one if it doesn't exist.
508      *
509      * @param activityToken The Activity this Resources object should be associated with.
510      * @param key The key describing the parameters of the ResourcesImpl object.
511      * @param classLoader The classloader to use for the Resources object.
512      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
513      * @return A Resources object that gets updated when
514      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
515      *         is called.
516      */
getOrCreateResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader)517     private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
518             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
519         synchronized (this) {
520             if (DEBUG) {
521                 Throwable here = new Throwable();
522                 here.fillInStackTrace();
523                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
524             }
525 
526             if (activityToken != null) {
527                 final ActivityResources activityResources =
528                         getOrCreateActivityResourcesStructLocked(activityToken);
529 
530                 // Clean up any dead references so they don't pile up.
531                 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
532                         sEmptyReferencePredicate);
533 
534                 // Rebase the key's override config on top of the Activity's base override.
535                 if (key.hasOverrideConfiguration()
536                         && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
537                     final Configuration temp = new Configuration(activityResources.overrideConfig);
538                     temp.updateFrom(key.mOverrideConfiguration);
539                     key.mOverrideConfiguration.setTo(temp);
540                 }
541 
542                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
543                 if (resourcesImpl != null) {
544                     if (DEBUG) {
545                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
546                     }
547                     return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
548                             resourcesImpl);
549                 }
550 
551                 // We will create the ResourcesImpl object outside of holding this lock.
552 
553             } else {
554                 // Clean up any dead references so they don't pile up.
555                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
556 
557                 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
558                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
559                 if (resourcesImpl != null) {
560                     if (DEBUG) {
561                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
562                     }
563                     return getOrCreateResourcesLocked(classLoader, resourcesImpl);
564                 }
565 
566                 // We will create the ResourcesImpl object outside of holding this lock.
567             }
568         }
569 
570         // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
571         ResourcesImpl resourcesImpl = createResourcesImpl(key);
572 
573         synchronized (this) {
574             ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
575             if (existingResourcesImpl != null) {
576                 if (DEBUG) {
577                     Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
578                             + " new impl=" + resourcesImpl);
579                 }
580                 resourcesImpl.getAssets().close();
581                 resourcesImpl = existingResourcesImpl;
582             } else {
583                 // Add this ResourcesImpl to the cache.
584                 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
585             }
586 
587             final Resources resources;
588             if (activityToken != null) {
589                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
590                         resourcesImpl);
591             } else {
592                 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
593             }
594             return resources;
595         }
596     }
597 
598     /**
599      * Gets or creates a new Resources object associated with the IBinder token. References returned
600      * by this method live as long as the Activity, meaning they can be cached and used by the
601      * Activity even after a configuration change. If any other parameter is changed
602      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
603      * is updated and handed back to the caller. However, changing the class loader will result in a
604      * new Resources object.
605      * <p/>
606      * If activityToken is null, a cached Resources object will be returned if it matches the
607      * input parameters. Otherwise a new Resources object that satisfies these parameters is
608      * returned.
609      *
610      * @param activityToken Represents an Activity. If null, global resources are assumed.
611      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
612      * @param splitResDirs An array of split resource paths. Can be null.
613      * @param overlayDirs An array of overlay paths. Can be null.
614      * @param libDirs An array of resource library paths. Can be null.
615      * @param displayId The ID of the display for which to create the resources.
616      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
617      * null. Mostly used with Activities that are in multi-window which may override width and
618      * height properties from the base config.
619      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
620      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
621      * @param classLoader The class loader to use when inflating Resources. If null, the
622      * {@link ClassLoader#getSystemClassLoader()} is used.
623      * @return a Resources object from which to access resources.
624      */
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)625     public @NonNull Resources getResources(@Nullable IBinder activityToken,
626             @Nullable String resDir,
627             @Nullable String[] splitResDirs,
628             @Nullable String[] overlayDirs,
629             @Nullable String[] libDirs,
630             int displayId,
631             @Nullable Configuration overrideConfig,
632             @NonNull CompatibilityInfo compatInfo,
633             @Nullable ClassLoader classLoader) {
634         try {
635             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
636             final ResourcesKey key = new ResourcesKey(
637                     resDir,
638                     splitResDirs,
639                     overlayDirs,
640                     libDirs,
641                     displayId,
642                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
643                     compatInfo);
644             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
645             return getOrCreateResources(activityToken, key, classLoader);
646         } finally {
647             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
648         }
649     }
650 
651     /**
652      * Updates an Activity's Resources object with overrideConfig. The Resources object
653      * that was previously returned by
654      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
655      * CompatibilityInfo, ClassLoader)} is
656      * still valid and will have the updated configuration.
657      * @param activityToken The Activity token.
658      * @param overrideConfig The configuration override to update.
659      */
updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig)660     public void updateResourcesForActivity(@NonNull IBinder activityToken,
661             @Nullable Configuration overrideConfig) {
662         try {
663             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
664                     "ResourcesManager#updateResourcesForActivity");
665             synchronized (this) {
666                 final ActivityResources activityResources =
667                         getOrCreateActivityResourcesStructLocked(activityToken);
668 
669                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
670                     // They are the same, no work to do.
671                     return;
672                 }
673 
674                 // Grab a copy of the old configuration so we can create the delta's of each
675                 // Resources object associated with this Activity.
676                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
677 
678                 // Update the Activity's base override.
679                 if (overrideConfig != null) {
680                     activityResources.overrideConfig.setTo(overrideConfig);
681                 } else {
682                     activityResources.overrideConfig.setToDefaults();
683                 }
684 
685                 if (DEBUG) {
686                     Throwable here = new Throwable();
687                     here.fillInStackTrace();
688                     Slog.d(TAG, "updating resources override for activity=" + activityToken
689                             + " from oldConfig="
690                             + Configuration.resourceQualifierString(oldConfig)
691                             + " to newConfig="
692                             + Configuration.resourceQualifierString(
693                             activityResources.overrideConfig),
694                             here);
695                 }
696 
697                 final boolean activityHasOverrideConfig =
698                         !activityResources.overrideConfig.equals(Configuration.EMPTY);
699 
700                 // Rebase each Resources associated with this Activity.
701                 final int refCount = activityResources.activityResources.size();
702                 for (int i = 0; i < refCount; i++) {
703                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
704                             i);
705                     Resources resources = weakResRef.get();
706                     if (resources == null) {
707                         continue;
708                     }
709 
710                     // Extract the ResourcesKey that was last used to create the Resources for this
711                     // activity.
712                     final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
713                     if (oldKey == null) {
714                         Slog.e(TAG, "can't find ResourcesKey for resources impl="
715                                 + resources.getImpl());
716                         continue;
717                     }
718 
719                     // Build the new override configuration for this ResourcesKey.
720                     final Configuration rebasedOverrideConfig = new Configuration();
721                     if (overrideConfig != null) {
722                         rebasedOverrideConfig.setTo(overrideConfig);
723                     }
724 
725                     if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
726                         // Generate a delta between the old base Activity override configuration and
727                         // the actual final override configuration that was used to figure out the
728                         // real delta this Resources object wanted.
729                         Configuration overrideOverrideConfig = Configuration.generateDelta(
730                                 oldConfig, oldKey.mOverrideConfiguration);
731                         rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
732                     }
733 
734                     // Create the new ResourcesKey with the rebased override config.
735                     final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
736                             oldKey.mSplitResDirs,
737                             oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
738                             rebasedOverrideConfig, oldKey.mCompatInfo);
739 
740                     if (DEBUG) {
741                         Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
742                                 + " to newKey=" + newKey);
743                     }
744 
745                     ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
746                     if (resourcesImpl == null) {
747                         resourcesImpl = createResourcesImpl(newKey);
748                         mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
749                     }
750 
751                     if (resourcesImpl != resources.getImpl()) {
752                         // Set the ResourcesImpl, updating it for all users of this Resources
753                         // object.
754                         resources.setImpl(resourcesImpl);
755                     }
756                 }
757             }
758         } finally {
759             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
760         }
761     }
762 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)763     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
764                                                              @Nullable CompatibilityInfo compat) {
765         try {
766             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
767                     "ResourcesManager#applyConfigurationToResourcesLocked");
768 
769             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
770                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
771                         + mResConfiguration.seq + ", newSeq=" + config.seq);
772                 return false;
773             }
774             int changes = mResConfiguration.updateFrom(config);
775             // Things might have changed in display manager, so clear the cached displays.
776             mDisplays.clear();
777             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
778 
779             if (compat != null && (mResCompatibilityInfo == null ||
780                     !mResCompatibilityInfo.equals(compat))) {
781                 mResCompatibilityInfo = compat;
782                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
783                         | ActivityInfo.CONFIG_SCREEN_SIZE
784                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
785             }
786 
787             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
788 
789             ApplicationPackageManager.configurationChanged();
790             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
791 
792             Configuration tmpConfig = null;
793 
794             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
795                 ResourcesKey key = mResourceImpls.keyAt(i);
796                 ResourcesImpl r = mResourceImpls.valueAt(i).get();
797                 if (r != null) {
798                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
799                             + r + " config to: " + config);
800                     int displayId = key.mDisplayId;
801                     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
802                     DisplayMetrics dm = defaultDisplayMetrics;
803                     final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
804                     if (!isDefaultDisplay || hasOverrideConfiguration) {
805                         if (tmpConfig == null) {
806                             tmpConfig = new Configuration();
807                         }
808                         tmpConfig.setTo(config);
809                         if (!isDefaultDisplay) {
810                             // Get new DisplayMetrics based on the DisplayAdjustments given
811                             // to the ResourcesImpl. Udate a copy if the CompatibilityInfo
812                             // changed, because the ResourcesImpl object will handle the
813                             // update internally.
814                             DisplayAdjustments daj = r.getDisplayAdjustments();
815                             if (compat != null) {
816                                 daj = new DisplayAdjustments(daj);
817                                 daj.setCompatibilityInfo(compat);
818                             }
819                             dm = getDisplayMetrics(displayId, daj);
820                             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
821                         }
822                         if (hasOverrideConfiguration) {
823                             tmpConfig.updateFrom(key.mOverrideConfiguration);
824                         }
825                         r.updateConfiguration(tmpConfig, dm, compat);
826                     } else {
827                         r.updateConfiguration(config, dm, compat);
828                     }
829                     //Slog.i(TAG, "Updated app resources " + v.getKey()
830                     //        + " " + r + ": " + r.getConfiguration());
831                 } else {
832                     //Slog.i(TAG, "Removing old resources " + v.getKey());
833                     mResourceImpls.removeAt(i);
834                 }
835             }
836 
837             return changes != 0;
838         } finally {
839             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
840         }
841     }
842 
843     /**
844      * Appends the library asset path to any ResourcesImpl object that contains the main
845      * assetPath.
846      * @param assetPath The main asset path for which to add the library asset path.
847      * @param libAsset The library asset path to add.
848      */
appendLibAssetForMainAssetPath(String assetPath, String libAsset)849     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
850         synchronized (this) {
851             // Record which ResourcesImpl need updating
852             // (and what ResourcesKey they should update to).
853             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
854 
855             final int implCount = mResourceImpls.size();
856             for (int i = 0; i < implCount; i++) {
857                 final ResourcesImpl impl = mResourceImpls.valueAt(i).get();
858                 final ResourcesKey key = mResourceImpls.keyAt(i);
859                 if (impl != null && key.mResDir.equals(assetPath)) {
860                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
861                         final int newLibAssetCount = 1 +
862                                 (key.mLibDirs != null ? key.mLibDirs.length : 0);
863                         final String[] newLibAssets = new String[newLibAssetCount];
864                         if (key.mLibDirs != null) {
865                             System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
866                         }
867                         newLibAssets[newLibAssetCount - 1] = libAsset;
868 
869                         updatedResourceKeys.put(impl, new ResourcesKey(
870                                 key.mResDir,
871                                 key.mSplitResDirs,
872                                 key.mOverlayDirs,
873                                 newLibAssets,
874                                 key.mDisplayId,
875                                 key.mOverrideConfiguration,
876                                 key.mCompatInfo));
877                     }
878                 }
879             }
880 
881             // Bail early if there is no work to do.
882             if (updatedResourceKeys.isEmpty()) {
883                 return;
884             }
885 
886             // Update any references to ResourcesImpl that require reloading.
887             final int resourcesCount = mResourceReferences.size();
888             for (int i = 0; i < resourcesCount; i++) {
889                 final Resources r = mResourceReferences.get(i).get();
890                 if (r != null) {
891                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
892                     if (key != null) {
893                         r.setImpl(findOrCreateResourcesImplForKeyLocked(key));
894                     }
895                 }
896             }
897 
898             // Update any references to ResourcesImpl that require reloading for each Activity.
899             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
900                 final int resCount = activityResources.activityResources.size();
901                 for (int i = 0; i < resCount; i++) {
902                     final Resources r = activityResources.activityResources.get(i).get();
903                     if (r != null) {
904                         final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
905                         if (key != null) {
906                             r.setImpl(findOrCreateResourcesImplForKeyLocked(key));
907                         }
908                     }
909                 }
910             }
911         }
912     }
913 }
914