• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
20 
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 @Nullable 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                 Log.e(TAG, "failed to add asset path " + key.mResDir);
254                 return null;
255             }
256         }
257 
258         if (key.mSplitResDirs != null) {
259             for (final String splitResDir : key.mSplitResDirs) {
260                 if (assets.addAssetPath(splitResDir) == 0) {
261                     Log.e(TAG, "failed to add split asset path " + splitResDir);
262                     return null;
263                 }
264             }
265         }
266 
267         if (key.mOverlayDirs != null) {
268             for (final String idmapPath : key.mOverlayDirs) {
269                 assets.addOverlayPath(idmapPath);
270             }
271         }
272 
273         if (key.mLibDirs != null) {
274             for (final String libDir : key.mLibDirs) {
275                 if (libDir.endsWith(".apk")) {
276                     // Avoid opening files we know do not have resources,
277                     // like code-only .jar files.
278                     if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
279                         Log.w(TAG, "Asset path '" + libDir +
280                                 "' does not exist or contains no resources.");
281                     }
282                 }
283             }
284         }
285         return assets;
286     }
287 
generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)288     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
289         Configuration config;
290         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
291         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
292         if (!isDefaultDisplay || hasOverrideConfig) {
293             config = new Configuration(getConfiguration());
294             if (!isDefaultDisplay) {
295                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
296             }
297             if (hasOverrideConfig) {
298                 config.updateFrom(key.mOverrideConfiguration);
299                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
300             }
301         } else {
302             config = getConfiguration();
303         }
304         return config;
305     }
306 
createResourcesImpl(@onNull ResourcesKey key)307     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
308         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
309         daj.setCompatibilityInfo(key.mCompatInfo);
310 
311         final AssetManager assets = createAssetManager(key);
312         if (assets == null) {
313             return null;
314         }
315 
316         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
317         final Configuration config = generateConfig(key, dm);
318         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
319         if (DEBUG) {
320             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
321         }
322         return impl;
323     }
324 
325     /**
326      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
327      *
328      * @param key The key to match.
329      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
330      */
findResourcesImplForKeyLocked(@onNull ResourcesKey key)331     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
332         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
333         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
334         if (impl != null && impl.getAssets().isUpToDate()) {
335             return impl;
336         }
337         return null;
338     }
339 
340     /**
341      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
342      * creates a new one and caches it for future use.
343      * @param key The key to match.
344      * @return a ResourcesImpl object matching the key.
345      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)346     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
347             @NonNull ResourcesKey key) {
348         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
349         if (impl == null) {
350             impl = createResourcesImpl(key);
351             if (impl != null) {
352                 mResourceImpls.put(key, new WeakReference<>(impl));
353             }
354         }
355         return impl;
356     }
357 
358     /**
359      * Find the ResourcesKey that this ResourcesImpl object is associated with.
360      * @return the ResourcesKey or null if none was found.
361      */
findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)362     private @Nullable ResourcesKey findKeyForResourceImplLocked(
363             @NonNull ResourcesImpl resourceImpl) {
364         final int refCount = mResourceImpls.size();
365         for (int i = 0; i < refCount; i++) {
366             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
367             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
368             if (impl != null && resourceImpl == impl) {
369                 return mResourceImpls.keyAt(i);
370             }
371         }
372         return null;
373     }
374 
375     /**
376      * Check if activity resources have same override config as the provided on.
377      * @param activityToken The Activity that resources should be associated with.
378      * @param overrideConfig The override configuration to be checked for equality with.
379      * @return true if activity resources override config matches the provided one or they are both
380      *         null, false otherwise.
381      */
isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)382     boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
383             @Nullable Configuration overrideConfig) {
384         synchronized (this) {
385             final ActivityResources activityResources
386                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
387             if (activityResources == null) {
388                 return overrideConfig == null;
389             } else {
390                 return Objects.equals(activityResources.overrideConfig, overrideConfig);
391             }
392         }
393     }
394 
getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)395     private ActivityResources getOrCreateActivityResourcesStructLocked(
396             @NonNull IBinder activityToken) {
397         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
398         if (activityResources == null) {
399             activityResources = new ActivityResources();
400             mActivityResourceReferences.put(activityToken, activityResources);
401         }
402         return activityResources;
403     }
404 
405     /**
406      * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
407      * or the class loader is different.
408      */
getOrCreateResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl)409     private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
410             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
411         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
412                 activityToken);
413 
414         final int refCount = activityResources.activityResources.size();
415         for (int i = 0; i < refCount; i++) {
416             WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
417             Resources resources = weakResourceRef.get();
418 
419             if (resources != null
420                     && Objects.equals(resources.getClassLoader(), classLoader)
421                     && resources.getImpl() == impl) {
422                 if (DEBUG) {
423                     Slog.d(TAG, "- using existing ref=" + resources);
424                 }
425                 return resources;
426             }
427         }
428 
429         Resources resources = new Resources(classLoader);
430         resources.setImpl(impl);
431         activityResources.activityResources.add(new WeakReference<>(resources));
432         if (DEBUG) {
433             Slog.d(TAG, "- creating new ref=" + resources);
434             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
435         }
436         return resources;
437     }
438 
439     /**
440      * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
441      * otherwise creates a new Resources object.
442      */
getOrCreateResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl)443     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
444             @NonNull ResourcesImpl impl) {
445         // Find an existing Resources that has this ResourcesImpl set.
446         final int refCount = mResourceReferences.size();
447         for (int i = 0; i < refCount; i++) {
448             WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
449             Resources resources = weakResourceRef.get();
450             if (resources != null &&
451                     Objects.equals(resources.getClassLoader(), classLoader) &&
452                     resources.getImpl() == impl) {
453                 if (DEBUG) {
454                     Slog.d(TAG, "- using existing ref=" + resources);
455                 }
456                 return resources;
457             }
458         }
459 
460         // Create a new Resources reference and use the existing ResourcesImpl object.
461         Resources resources = new Resources(classLoader);
462         resources.setImpl(impl);
463         mResourceReferences.add(new WeakReference<>(resources));
464         if (DEBUG) {
465             Slog.d(TAG, "- creating new ref=" + resources);
466             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
467         }
468         return resources;
469     }
470 
471     /**
472      * Creates base resources for an Activity. Calls to
473      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
474      * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
475      * configurations merged with the one specified here.
476      *
477      * @param activityToken Represents an Activity.
478      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
479      * @param splitResDirs An array of split resource paths. Can be null.
480      * @param overlayDirs An array of overlay paths. Can be null.
481      * @param libDirs An array of resource library paths. Can be null.
482      * @param displayId The ID of the display for which to create the resources.
483      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
484      *                       null. This provides the base override for this Activity.
485      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
486      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
487      * @param classLoader The class loader to use when inflating Resources. If null, the
488      *                    {@link ClassLoader#getSystemClassLoader()} is used.
489      * @return a Resources object from which to access resources.
490      */
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)491     public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
492             @Nullable String resDir,
493             @Nullable String[] splitResDirs,
494             @Nullable String[] overlayDirs,
495             @Nullable String[] libDirs,
496             int displayId,
497             @Nullable Configuration overrideConfig,
498             @NonNull CompatibilityInfo compatInfo,
499             @Nullable ClassLoader classLoader) {
500         try {
501             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
502                     "ResourcesManager#createBaseActivityResources");
503             final ResourcesKey key = new ResourcesKey(
504                     resDir,
505                     splitResDirs,
506                     overlayDirs,
507                     libDirs,
508                     displayId,
509                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
510                     compatInfo);
511             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
512 
513             if (DEBUG) {
514                 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
515                         + " with key=" + key);
516             }
517 
518             synchronized (this) {
519                 // Force the creation of an ActivityResourcesStruct.
520                 getOrCreateActivityResourcesStructLocked(activityToken);
521             }
522 
523             // Update any existing Activity Resources references.
524             updateResourcesForActivity(activityToken, overrideConfig);
525 
526             // Now request an actual Resources object.
527             return getOrCreateResources(activityToken, key, classLoader);
528         } finally {
529             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
530         }
531     }
532 
533     /**
534      * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
535      * or creates one if it doesn't exist.
536      *
537      * @param activityToken The Activity this Resources object should be associated with.
538      * @param key The key describing the parameters of the ResourcesImpl object.
539      * @param classLoader The classloader to use for the Resources object.
540      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
541      * @return A Resources object that gets updated when
542      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
543      *         is called.
544      */
getOrCreateResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader)545     private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
546             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
547         synchronized (this) {
548             if (DEBUG) {
549                 Throwable here = new Throwable();
550                 here.fillInStackTrace();
551                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
552             }
553 
554             if (activityToken != null) {
555                 final ActivityResources activityResources =
556                         getOrCreateActivityResourcesStructLocked(activityToken);
557 
558                 // Clean up any dead references so they don't pile up.
559                 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
560                         sEmptyReferencePredicate);
561 
562                 // Rebase the key's override config on top of the Activity's base override.
563                 if (key.hasOverrideConfiguration()
564                         && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
565                     final Configuration temp = new Configuration(activityResources.overrideConfig);
566                     temp.updateFrom(key.mOverrideConfiguration);
567                     key.mOverrideConfiguration.setTo(temp);
568                 }
569 
570                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
571                 if (resourcesImpl != null) {
572                     if (DEBUG) {
573                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
574                     }
575                     return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
576                             resourcesImpl);
577                 }
578 
579                 // We will create the ResourcesImpl object outside of holding this lock.
580 
581             } else {
582                 // Clean up any dead references so they don't pile up.
583                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
584 
585                 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
586                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
587                 if (resourcesImpl != null) {
588                     if (DEBUG) {
589                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
590                     }
591                     return getOrCreateResourcesLocked(classLoader, resourcesImpl);
592                 }
593 
594                 // We will create the ResourcesImpl object outside of holding this lock.
595             }
596         }
597 
598         // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
599         ResourcesImpl resourcesImpl = createResourcesImpl(key);
600         if (resourcesImpl == null) {
601             return null;
602         }
603 
604         synchronized (this) {
605             ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
606             if (existingResourcesImpl != null) {
607                 if (DEBUG) {
608                     Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
609                             + " new impl=" + resourcesImpl);
610                 }
611                 resourcesImpl.getAssets().close();
612                 resourcesImpl = existingResourcesImpl;
613             } else {
614                 // Add this ResourcesImpl to the cache.
615                 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
616             }
617 
618             final Resources resources;
619             if (activityToken != null) {
620                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
621                         resourcesImpl);
622             } else {
623                 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
624             }
625             return resources;
626         }
627     }
628 
629     /**
630      * Gets or creates a new Resources object associated with the IBinder token. References returned
631      * by this method live as long as the Activity, meaning they can be cached and used by the
632      * Activity even after a configuration change. If any other parameter is changed
633      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
634      * is updated and handed back to the caller. However, changing the class loader will result in a
635      * new Resources object.
636      * <p/>
637      * If activityToken is null, a cached Resources object will be returned if it matches the
638      * input parameters. Otherwise a new Resources object that satisfies these parameters is
639      * returned.
640      *
641      * @param activityToken Represents an Activity. If null, global resources are assumed.
642      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
643      * @param splitResDirs An array of split resource paths. Can be null.
644      * @param overlayDirs An array of overlay paths. Can be null.
645      * @param libDirs An array of resource library paths. Can be null.
646      * @param displayId The ID of the display for which to create the resources.
647      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
648      * null. Mostly used with Activities that are in multi-window which may override width and
649      * height properties from the base config.
650      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
651      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
652      * @param classLoader The class loader to use when inflating Resources. If null, the
653      * {@link ClassLoader#getSystemClassLoader()} is used.
654      * @return a Resources object from which to access resources.
655      */
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)656     public @Nullable Resources getResources(@Nullable IBinder activityToken,
657             @Nullable String resDir,
658             @Nullable String[] splitResDirs,
659             @Nullable String[] overlayDirs,
660             @Nullable String[] libDirs,
661             int displayId,
662             @Nullable Configuration overrideConfig,
663             @NonNull CompatibilityInfo compatInfo,
664             @Nullable ClassLoader classLoader) {
665         try {
666             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
667             final ResourcesKey key = new ResourcesKey(
668                     resDir,
669                     splitResDirs,
670                     overlayDirs,
671                     libDirs,
672                     displayId,
673                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
674                     compatInfo);
675             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
676             return getOrCreateResources(activityToken, key, classLoader);
677         } finally {
678             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
679         }
680     }
681 
682     /**
683      * Updates an Activity's Resources object with overrideConfig. The Resources object
684      * that was previously returned by
685      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
686      * CompatibilityInfo, ClassLoader)} is
687      * still valid and will have the updated configuration.
688      * @param activityToken The Activity token.
689      * @param overrideConfig The configuration override to update.
690      */
updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig)691     public void updateResourcesForActivity(@NonNull IBinder activityToken,
692             @Nullable Configuration overrideConfig) {
693         try {
694             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
695                     "ResourcesManager#updateResourcesForActivity");
696             synchronized (this) {
697                 final ActivityResources activityResources =
698                         getOrCreateActivityResourcesStructLocked(activityToken);
699 
700                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
701                     // They are the same, no work to do.
702                     return;
703                 }
704 
705                 // Grab a copy of the old configuration so we can create the delta's of each
706                 // Resources object associated with this Activity.
707                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
708 
709                 // Update the Activity's base override.
710                 if (overrideConfig != null) {
711                     activityResources.overrideConfig.setTo(overrideConfig);
712                 } else {
713                     activityResources.overrideConfig.setToDefaults();
714                 }
715 
716                 if (DEBUG) {
717                     Throwable here = new Throwable();
718                     here.fillInStackTrace();
719                     Slog.d(TAG, "updating resources override for activity=" + activityToken
720                             + " from oldConfig="
721                             + Configuration.resourceQualifierString(oldConfig)
722                             + " to newConfig="
723                             + Configuration.resourceQualifierString(
724                             activityResources.overrideConfig),
725                             here);
726                 }
727 
728                 final boolean activityHasOverrideConfig =
729                         !activityResources.overrideConfig.equals(Configuration.EMPTY);
730 
731                 // Rebase each Resources associated with this Activity.
732                 final int refCount = activityResources.activityResources.size();
733                 for (int i = 0; i < refCount; i++) {
734                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
735                             i);
736                     Resources resources = weakResRef.get();
737                     if (resources == null) {
738                         continue;
739                     }
740 
741                     // Extract the ResourcesKey that was last used to create the Resources for this
742                     // activity.
743                     final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
744                     if (oldKey == null) {
745                         Slog.e(TAG, "can't find ResourcesKey for resources impl="
746                                 + resources.getImpl());
747                         continue;
748                     }
749 
750                     // Build the new override configuration for this ResourcesKey.
751                     final Configuration rebasedOverrideConfig = new Configuration();
752                     if (overrideConfig != null) {
753                         rebasedOverrideConfig.setTo(overrideConfig);
754                     }
755 
756                     if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
757                         // Generate a delta between the old base Activity override configuration and
758                         // the actual final override configuration that was used to figure out the
759                         // real delta this Resources object wanted.
760                         Configuration overrideOverrideConfig = Configuration.generateDelta(
761                                 oldConfig, oldKey.mOverrideConfiguration);
762                         rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
763                     }
764 
765                     // Create the new ResourcesKey with the rebased override config.
766                     final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
767                             oldKey.mSplitResDirs,
768                             oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
769                             rebasedOverrideConfig, oldKey.mCompatInfo);
770 
771                     if (DEBUG) {
772                         Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
773                                 + " to newKey=" + newKey);
774                     }
775 
776                     ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
777                     if (resourcesImpl == null) {
778                         resourcesImpl = createResourcesImpl(newKey);
779                         if (resourcesImpl != null) {
780                             mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
781                         }
782                     }
783 
784                     if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
785                         // Set the ResourcesImpl, updating it for all users of this Resources
786                         // object.
787                         resources.setImpl(resourcesImpl);
788                     }
789                 }
790             }
791         } finally {
792             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
793         }
794     }
795 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)796     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
797                                                              @Nullable CompatibilityInfo compat) {
798         try {
799             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
800                     "ResourcesManager#applyConfigurationToResourcesLocked");
801 
802             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
803                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
804                         + mResConfiguration.seq + ", newSeq=" + config.seq);
805                 return false;
806             }
807             int changes = mResConfiguration.updateFrom(config);
808             // Things might have changed in display manager, so clear the cached displays.
809             mDisplays.clear();
810             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
811 
812             if (compat != null && (mResCompatibilityInfo == null ||
813                     !mResCompatibilityInfo.equals(compat))) {
814                 mResCompatibilityInfo = compat;
815                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
816                         | ActivityInfo.CONFIG_SCREEN_SIZE
817                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
818             }
819 
820             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
821 
822             ApplicationPackageManager.configurationChanged();
823             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
824 
825             Configuration tmpConfig = null;
826 
827             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
828                 ResourcesKey key = mResourceImpls.keyAt(i);
829                 ResourcesImpl r = mResourceImpls.valueAt(i).get();
830                 if (r != null) {
831                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
832                             + r + " config to: " + config);
833                     int displayId = key.mDisplayId;
834                     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
835                     DisplayMetrics dm = defaultDisplayMetrics;
836                     final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
837                     if (!isDefaultDisplay || hasOverrideConfiguration) {
838                         if (tmpConfig == null) {
839                             tmpConfig = new Configuration();
840                         }
841                         tmpConfig.setTo(config);
842                         if (!isDefaultDisplay) {
843                             // Get new DisplayMetrics based on the DisplayAdjustments given
844                             // to the ResourcesImpl. Udate a copy if the CompatibilityInfo
845                             // changed, because the ResourcesImpl object will handle the
846                             // update internally.
847                             DisplayAdjustments daj = r.getDisplayAdjustments();
848                             if (compat != null) {
849                                 daj = new DisplayAdjustments(daj);
850                                 daj.setCompatibilityInfo(compat);
851                             }
852                             dm = getDisplayMetrics(displayId, daj);
853                             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
854                         }
855                         if (hasOverrideConfiguration) {
856                             tmpConfig.updateFrom(key.mOverrideConfiguration);
857                         }
858                         r.updateConfiguration(tmpConfig, dm, compat);
859                     } else {
860                         r.updateConfiguration(config, dm, compat);
861                     }
862                     //Slog.i(TAG, "Updated app resources " + v.getKey()
863                     //        + " " + r + ": " + r.getConfiguration());
864                 } else {
865                     //Slog.i(TAG, "Removing old resources " + v.getKey());
866                     mResourceImpls.removeAt(i);
867                 }
868             }
869 
870             return changes != 0;
871         } finally {
872             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
873         }
874     }
875 
876     /**
877      * Appends the library asset path to any ResourcesImpl object that contains the main
878      * assetPath.
879      * @param assetPath The main asset path for which to add the library asset path.
880      * @param libAsset The library asset path to add.
881      */
appendLibAssetForMainAssetPath(String assetPath, String libAsset)882     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
883         synchronized (this) {
884             // Record which ResourcesImpl need updating
885             // (and what ResourcesKey they should update to).
886             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
887 
888             final int implCount = mResourceImpls.size();
889             for (int i = 0; i < implCount; i++) {
890                 final ResourcesImpl impl = mResourceImpls.valueAt(i).get();
891                 final ResourcesKey key = mResourceImpls.keyAt(i);
892                 if (impl != null && key.mResDir.equals(assetPath)) {
893                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
894                         final int newLibAssetCount = 1 +
895                                 (key.mLibDirs != null ? key.mLibDirs.length : 0);
896                         final String[] newLibAssets = new String[newLibAssetCount];
897                         if (key.mLibDirs != null) {
898                             System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
899                         }
900                         newLibAssets[newLibAssetCount - 1] = libAsset;
901 
902                         updatedResourceKeys.put(impl, new ResourcesKey(
903                                 key.mResDir,
904                                 key.mSplitResDirs,
905                                 key.mOverlayDirs,
906                                 newLibAssets,
907                                 key.mDisplayId,
908                                 key.mOverrideConfiguration,
909                                 key.mCompatInfo));
910                     }
911                 }
912             }
913 
914             // Bail early if there is no work to do.
915             if (updatedResourceKeys.isEmpty()) {
916                 return;
917             }
918 
919             // Update any references to ResourcesImpl that require reloading.
920             final int resourcesCount = mResourceReferences.size();
921             for (int i = 0; i < resourcesCount; i++) {
922                 final Resources r = mResourceReferences.get(i).get();
923                 if (r != null) {
924                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
925                     if (key != null) {
926                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
927                         if (impl == null) {
928                             throw new Resources.NotFoundException("failed to load " + libAsset);
929                         }
930                         r.setImpl(impl);
931                     }
932                 }
933             }
934 
935             // Update any references to ResourcesImpl that require reloading for each Activity.
936             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
937                 final int resCount = activityResources.activityResources.size();
938                 for (int i = 0; i < resCount; i++) {
939                     final Resources r = activityResources.activityResources.get(i).get();
940                     if (r != null) {
941                         final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
942                         if (key != null) {
943                             final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
944                             if (impl == null) {
945                                 throw new Resources.NotFoundException("failed to load " + libAsset);
946                             }
947                             r.setImpl(impl);
948                         }
949                     }
950                 }
951             }
952         }
953     }
954 }
955