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