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