• 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.annotation.TestApi;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.ApplicationInfo;
27 import android.content.res.ApkAssets;
28 import android.content.res.AssetManager;
29 import android.content.res.CompatResources;
30 import android.content.res.CompatibilityInfo;
31 import android.content.res.Configuration;
32 import android.content.res.Resources;
33 import android.content.res.ResourcesImpl;
34 import android.content.res.ResourcesKey;
35 import android.content.res.loader.ResourcesLoader;
36 import android.hardware.display.DisplayManagerGlobal;
37 import android.os.IBinder;
38 import android.os.Process;
39 import android.os.Trace;
40 import android.util.ArrayMap;
41 import android.util.DisplayMetrics;
42 import android.util.Log;
43 import android.util.Pair;
44 import android.util.Slog;
45 import android.view.Display;
46 import android.view.DisplayAdjustments;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.util.ArrayUtils;
50 import com.android.internal.util.IndentingPrintWriter;
51 
52 import java.io.IOException;
53 import java.io.PrintWriter;
54 import java.lang.ref.Reference;
55 import java.lang.ref.ReferenceQueue;
56 import java.lang.ref.WeakReference;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Objects;
63 import java.util.WeakHashMap;
64 import java.util.function.Consumer;
65 
66 /** @hide */
67 public class ResourcesManager {
68     static final String TAG = "ResourcesManager";
69     private static final boolean DEBUG = false;
70 
71     private static ResourcesManager sResourcesManager;
72 
73     /**
74      * The global compatibility settings.
75      */
76     private CompatibilityInfo mResCompatibilityInfo;
77 
78     /**
79      * The global configuration upon which all Resources are based. Multi-window Resources
80      * apply their overrides to this configuration.
81      */
82     @UnsupportedAppUsage
83     private final Configuration mResConfiguration = new Configuration();
84 
85     /**
86      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
87      * which should be reused as much as possible.
88      */
89     @UnsupportedAppUsage
90     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
91             new ArrayMap<>();
92 
93     /**
94      * A list of Resource references that can be reused.
95      */
96     @UnsupportedAppUsage
97     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
98     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
99 
100     private static class ApkKey {
101         public final String path;
102         public final boolean sharedLib;
103         public final boolean overlay;
104 
ApkKey(String path, boolean sharedLib, boolean overlay)105         ApkKey(String path, boolean sharedLib, boolean overlay) {
106             this.path = path;
107             this.sharedLib = sharedLib;
108             this.overlay = overlay;
109         }
110 
111         @Override
hashCode()112         public int hashCode() {
113             int result = 1;
114             result = 31 * result + this.path.hashCode();
115             result = 31 * result + Boolean.hashCode(this.sharedLib);
116             result = 31 * result + Boolean.hashCode(this.overlay);
117             return result;
118         }
119 
120         @Override
equals(Object obj)121         public boolean equals(Object obj) {
122             if (!(obj instanceof ApkKey)) {
123                 return false;
124             }
125             ApkKey other = (ApkKey) obj;
126             return this.path.equals(other.path) && this.sharedLib == other.sharedLib
127                     && this.overlay == other.overlay;
128         }
129     }
130 
131     /**
132      * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
133      * instance is alive and reachable.
134      */
135     private class ApkAssetsSupplier {
136         final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
137 
138         /**
139          * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
140          * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
141          * cache.
142          */
load(final ApkKey apkKey)143         ApkAssets load(final ApkKey apkKey) throws IOException {
144             ApkAssets apkAssets = mLocalCache.get(apkKey);
145             if (apkAssets == null) {
146                 apkAssets = loadApkAssets(apkKey);
147                 mLocalCache.put(apkKey, apkAssets);
148             }
149             return apkAssets;
150         }
151     }
152 
153     /**
154      * The ApkAssets that are being referenced in the wild that we can reuse.
155      */
156     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
157 
158     /**
159      * Resources and base configuration override associated with an Activity.
160      */
161     private static class ActivityResources {
162         @UnsupportedAppUsage
ActivityResources()163         private ActivityResources() {
164         }
165         public final Configuration overrideConfig = new Configuration();
166         public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
167         final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
168     }
169 
170     /**
171      * Each Activity may has a base override configuration that is applied to each Resources object,
172      * which in turn may have their own override configuration specified.
173      */
174     @UnsupportedAppUsage
175     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
176             new WeakHashMap<>();
177 
178     /**
179      * A cache of DisplayId, DisplayAdjustments to Display.
180      */
181     private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
182             mAdjustedDisplays = new ArrayMap<>();
183 
184     /**
185      * Callback implementation for handling updates to Resources objects.
186      */
187     private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
188 
189     @UnsupportedAppUsage
ResourcesManager()190     public ResourcesManager() {
191     }
192 
193     @UnsupportedAppUsage
getInstance()194     public static ResourcesManager getInstance() {
195         synchronized (ResourcesManager.class) {
196             if (sResourcesManager == null) {
197                 sResourcesManager = new ResourcesManager();
198             }
199             return sResourcesManager;
200         }
201     }
202 
203     /**
204      * Invalidate and destroy any resources that reference content under the
205      * given filesystem path. Typically used when unmounting a storage device to
206      * try as hard as possible to release any open FDs.
207      */
invalidatePath(String path)208     public void invalidatePath(String path) {
209         synchronized (this) {
210             int count = 0;
211 
212             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
213                 final ResourcesKey key = mResourceImpls.keyAt(i);
214                 if (key.isPathReferenced(path)) {
215                     ResourcesImpl impl = mResourceImpls.removeAt(i).get();
216                     if (impl != null) {
217                         impl.flushLayoutCache();
218                     }
219                     count++;
220                 }
221             }
222 
223             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
224 
225             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
226                 final ApkKey key = mCachedApkAssets.keyAt(i);
227                 if (key.path.equals(path)) {
228                     WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
229                     if (apkAssetsRef != null && apkAssetsRef.get() != null) {
230                         apkAssetsRef.get().close();
231                     }
232                 }
233             }
234         }
235     }
236 
getConfiguration()237     public Configuration getConfiguration() {
238         synchronized (this) {
239             return mResConfiguration;
240         }
241     }
242 
getDisplayMetrics()243     DisplayMetrics getDisplayMetrics() {
244         return getDisplayMetrics(Display.DEFAULT_DISPLAY,
245                 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
246     }
247 
248     /**
249      * Protected so that tests can override and returns something a fixed value.
250      */
251     @VisibleForTesting
getDisplayMetrics(int displayId, DisplayAdjustments da)252     protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
253         DisplayMetrics dm = new DisplayMetrics();
254         final Display display = getAdjustedDisplay(displayId, da);
255         if (display != null) {
256             display.getMetrics(dm);
257         } else {
258             dm.setToDefaults();
259         }
260         return dm;
261     }
262 
applyNonDefaultDisplayMetricsToConfiguration( @onNull DisplayMetrics dm, @NonNull Configuration config)263     private static void applyNonDefaultDisplayMetricsToConfiguration(
264             @NonNull DisplayMetrics dm, @NonNull Configuration config) {
265         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
266         config.densityDpi = dm.densityDpi;
267         config.screenWidthDp = (int) (dm.widthPixels / dm.density);
268         config.screenHeightDp = (int) (dm.heightPixels / dm.density);
269         int sl = Configuration.resetScreenLayout(config.screenLayout);
270         if (dm.widthPixels > dm.heightPixels) {
271             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
272             config.screenLayout = Configuration.reduceScreenLayout(sl,
273                     config.screenWidthDp, config.screenHeightDp);
274         } else {
275             config.orientation = Configuration.ORIENTATION_PORTRAIT;
276             config.screenLayout = Configuration.reduceScreenLayout(sl,
277                     config.screenHeightDp, config.screenWidthDp);
278         }
279         config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp);
280         config.compatScreenWidthDp = config.screenWidthDp;
281         config.compatScreenHeightDp = config.screenHeightDp;
282         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
283     }
284 
applyCompatConfigurationLocked(int displayDensity, @NonNull Configuration compatConfiguration)285     public boolean applyCompatConfigurationLocked(int displayDensity,
286             @NonNull Configuration compatConfiguration) {
287         if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
288             mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
289             return true;
290         }
291         return false;
292     }
293 
294     /**
295      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
296      * available. This method is only used within {@link ResourcesManager} to calculate display
297      * metrics based on a set {@link DisplayAdjustments}. All other usages should instead call
298      * {@link ResourcesManager#getAdjustedDisplay(int, Resources)}.
299      *
300      * @param displayId display Id.
301      * @param displayAdjustments display adjustments.
302      */
getAdjustedDisplay(final int displayId, @Nullable DisplayAdjustments displayAdjustments)303     private Display getAdjustedDisplay(final int displayId,
304             @Nullable DisplayAdjustments displayAdjustments) {
305         final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
306                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
307         final Pair<Integer, DisplayAdjustments> key =
308                 Pair.create(displayId, displayAdjustmentsCopy);
309         synchronized (this) {
310             WeakReference<Display> wd = mAdjustedDisplays.get(key);
311             if (wd != null) {
312                 final Display display = wd.get();
313                 if (display != null) {
314                     return display;
315                 }
316             }
317             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
318             if (dm == null) {
319                 // may be null early in system startup
320                 return null;
321             }
322             final Display display = dm.getCompatibleDisplay(displayId, key.second);
323             if (display != null) {
324                 mAdjustedDisplays.put(key, new WeakReference<>(display));
325             }
326             return display;
327         }
328     }
329 
330     /**
331      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
332      * available.
333      *
334      * @param displayId display Id.
335      * @param resources The {@link Resources} backing the display adjustments.
336      */
getAdjustedDisplay(final int displayId, Resources resources)337     public Display getAdjustedDisplay(final int displayId, Resources resources) {
338         synchronized (this) {
339             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
340             if (dm == null) {
341                 // may be null early in system startup
342                 return null;
343             }
344             return dm.getCompatibleDisplay(displayId, resources);
345         }
346     }
347 
overlayPathToIdmapPath(String path)348     private static String overlayPathToIdmapPath(String path) {
349         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
350     }
351 
loadApkAssets(@onNull final ApkKey key)352     private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
353         ApkAssets apkAssets;
354 
355         // Optimistically check if this ApkAssets exists somewhere else.
356         synchronized (this) {
357             final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
358             if (apkAssetsRef != null) {
359                 apkAssets = apkAssetsRef.get();
360                 if (apkAssets != null && apkAssets.isUpToDate()) {
361                     return apkAssets;
362                 } else {
363                     // Clean up the reference.
364                     mCachedApkAssets.remove(key);
365                 }
366             }
367         }
368 
369         // We must load this from disk.
370         if (key.overlay) {
371             apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path),
372                     0 /*flags*/);
373         } else {
374             apkAssets = ApkAssets.loadFromPath(key.path,
375                     key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
376         }
377 
378         synchronized (this) {
379             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
380         }
381 
382         return apkAssets;
383     }
384 
385     /**
386      * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
387      * AssetManagers mapped to the {@param key}.
388      */
extractApkKeys(@onNull final ResourcesKey key)389     private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
390         final ArrayList<ApkKey> apkKeys = new ArrayList<>();
391 
392         // resDir can be null if the 'android' package is creating a new Resources object.
393         // This is fine, since each AssetManager automatically loads the 'android' package
394         // already.
395         if (key.mResDir != null) {
396             apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
397         }
398 
399         if (key.mSplitResDirs != null) {
400             for (final String splitResDir : key.mSplitResDirs) {
401                 apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
402             }
403         }
404 
405         if (key.mLibDirs != null) {
406             for (final String libDir : key.mLibDirs) {
407                 // Avoid opening files we know do not have resources, like code-only .jar files.
408                 if (libDir.endsWith(".apk")) {
409                     apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
410                 }
411             }
412         }
413 
414         if (key.mOverlayDirs != null) {
415             for (final String idmapPath : key.mOverlayDirs) {
416                 apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
417             }
418         }
419 
420         return apkKeys;
421     }
422 
423     /**
424      * Creates an AssetManager from the paths within the ResourcesKey.
425      *
426      * This can be overridden in tests so as to avoid creating a real AssetManager with
427      * real APK paths.
428      * @param key The key containing the resource paths to add to the AssetManager.
429      * @return a new AssetManager.
430     */
431     @VisibleForTesting
432     @UnsupportedAppUsage
createAssetManager(@onNull final ResourcesKey key)433     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
434         return createAssetManager(key, /* apkSupplier */ null);
435     }
436 
437     /**
438      * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
439      * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
440      * {@link #loadApkAssets(ApkKey)}.
441      */
createAssetManager(@onNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)442     private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
443             @Nullable ApkAssetsSupplier apkSupplier) {
444         final AssetManager.Builder builder = new AssetManager.Builder();
445 
446         final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
447         for (int i = 0, n = apkKeys.size(); i < n; i++) {
448             final ApkKey apkKey = apkKeys.get(i);
449             try {
450                 builder.addApkAssets(
451                         (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
452             } catch (IOException e) {
453                 if (apkKey.overlay) {
454                     Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
455                 } else if (apkKey.sharedLib) {
456                     Log.w(TAG, String.format(
457                             "asset path '%s' does not exist or contains no resources",
458                             apkKey.path), e);
459                 } else {
460                     Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
461                     return null;
462                 }
463             }
464         }
465 
466         if (key.mLoaders != null) {
467             for (final ResourcesLoader loader : key.mLoaders) {
468                 builder.addLoader(loader);
469             }
470         }
471 
472         return builder.build();
473     }
474 
countLiveReferences(Collection<WeakReference<T>> collection)475     private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
476         int count = 0;
477         for (WeakReference<T> ref : collection) {
478             final T value = ref != null ? ref.get() : null;
479             if (value != null) {
480                 count++;
481             }
482         }
483         return count;
484     }
485 
486     /**
487      * @hide
488      */
dump(String prefix, PrintWriter printWriter)489     public void dump(String prefix, PrintWriter printWriter) {
490         synchronized (this) {
491             IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
492             for (int i = 0; i < prefix.length() / 2; i++) {
493                 pw.increaseIndent();
494             }
495 
496             pw.println("ResourcesManager:");
497             pw.increaseIndent();
498             pw.print("total apks: ");
499             pw.println(countLiveReferences(mCachedApkAssets.values()));
500 
501             pw.print("resources: ");
502 
503             int references = countLiveReferences(mResourceReferences);
504             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
505                 references += countLiveReferences(activityResources.activityResources);
506             }
507             pw.println(references);
508 
509             pw.print("resource impls: ");
510             pw.println(countLiveReferences(mResourceImpls.values()));
511         }
512     }
513 
generateConfig(@onNull ResourcesKey key, @NonNull DisplayMetrics dm)514     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
515         Configuration config;
516         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
517         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
518         if (!isDefaultDisplay || hasOverrideConfig) {
519             config = new Configuration(getConfiguration());
520             if (!isDefaultDisplay) {
521                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
522             }
523             if (hasOverrideConfig) {
524                 config.updateFrom(key.mOverrideConfiguration);
525                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
526             }
527         } else {
528             config = getConfiguration();
529         }
530         return config;
531     }
532 
createResourcesImpl(@onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)533     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
534             @Nullable ApkAssetsSupplier apkSupplier) {
535         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
536         daj.setCompatibilityInfo(key.mCompatInfo);
537 
538         final AssetManager assets = createAssetManager(key, apkSupplier);
539         if (assets == null) {
540             return null;
541         }
542 
543         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
544         final Configuration config = generateConfig(key, dm);
545         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
546 
547         if (DEBUG) {
548             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
549         }
550         return impl;
551     }
552 
553     /**
554      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
555      *
556      * @param key The key to match.
557      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
558      */
findResourcesImplForKeyLocked(@onNull ResourcesKey key)559     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
560         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
561         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
562         if (impl != null && impl.getAssets().isUpToDate()) {
563             return impl;
564         }
565         return null;
566     }
567 
568     /**
569      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
570      * creates a new one and caches it for future use.
571      * @param key The key to match.
572      * @return a ResourcesImpl object matching the key.
573      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)574     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
575             @NonNull ResourcesKey key) {
576         return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
577     }
578 
579     /**
580      * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
581      * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
582      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)583     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
584             @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
585         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
586         if (impl == null) {
587             impl = createResourcesImpl(key, apkSupplier);
588             if (impl != null) {
589                 mResourceImpls.put(key, new WeakReference<>(impl));
590             }
591         }
592         return impl;
593     }
594 
595     /**
596      * Find the ResourcesKey that this ResourcesImpl object is associated with.
597      * @return the ResourcesKey or null if none was found.
598      */
findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)599     private @Nullable ResourcesKey findKeyForResourceImplLocked(
600             @NonNull ResourcesImpl resourceImpl) {
601         int refCount = mResourceImpls.size();
602         for (int i = 0; i < refCount; i++) {
603             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
604             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
605             if (resourceImpl == impl) {
606                 return mResourceImpls.keyAt(i);
607             }
608         }
609         return null;
610     }
611 
612     /**
613      * Check if activity resources have same override config as the provided on.
614      * @param activityToken The Activity that resources should be associated with.
615      * @param overrideConfig The override configuration to be checked for equality with.
616      * @return true if activity resources override config matches the provided one or they are both
617      *         null, false otherwise.
618      */
isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)619     boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
620             @Nullable Configuration overrideConfig) {
621         synchronized (this) {
622             final ActivityResources activityResources
623                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
624             if (activityResources == null) {
625                 return overrideConfig == null;
626             } else {
627                 // The two configurations must either be equal or publicly equivalent to be
628                 // considered the same.
629                 return Objects.equals(activityResources.overrideConfig, overrideConfig)
630                         || (overrideConfig != null && activityResources.overrideConfig != null
631                                 && 0 == overrideConfig.diffPublicOnly(
632                                         activityResources.overrideConfig));
633             }
634         }
635     }
636 
getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)637     private ActivityResources getOrCreateActivityResourcesStructLocked(
638             @NonNull IBinder activityToken) {
639         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
640         if (activityResources == null) {
641             activityResources = new ActivityResources();
642             mActivityResourceReferences.put(activityToken, activityResources);
643         }
644         return activityResources;
645     }
646 
647     @Nullable
findResourcesForActivityLocked(@onNull IBinder targetActivityToken, @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader)648     private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
649             @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
650         ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
651                 targetActivityToken);
652 
653         final int size = activityResources.activityResources.size();
654         for (int index = 0; index < size; index++) {
655             WeakReference<Resources> ref = activityResources.activityResources.get(index);
656             Resources resources = ref.get();
657             ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
658                     resources.getImpl());
659 
660             if (key != null
661                     && Objects.equals(resources.getClassLoader(), targetClassLoader)
662                     && Objects.equals(key, targetKey)) {
663                 return resources;
664             }
665         }
666 
667         return null;
668     }
669 
createResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)670     private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
671             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
672             @NonNull CompatibilityInfo compatInfo) {
673         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
674                 activityToken);
675         cleanupReferences(activityResources.activityResources,
676                 activityResources.activityResourcesQueue);
677 
678         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
679                 : new Resources(classLoader);
680         resources.setImpl(impl);
681         resources.setCallbacks(mUpdateCallbacks);
682         activityResources.activityResources.add(
683                 new WeakReference<>(resources, activityResources.activityResourcesQueue));
684         if (DEBUG) {
685             Slog.d(TAG, "- creating new ref=" + resources);
686             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
687         }
688         return resources;
689     }
690 
createResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)691     private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
692             @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
693         cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
694 
695         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
696                 : new Resources(classLoader);
697         resources.setImpl(impl);
698         resources.setCallbacks(mUpdateCallbacks);
699         mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
700         if (DEBUG) {
701             Slog.d(TAG, "- creating new ref=" + resources);
702             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
703         }
704         return resources;
705     }
706 
707     /**
708      * Creates base resources for a binder token. Calls to
709      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
710      * CompatibilityInfo, ClassLoader, List)} with the same binder token will have their override
711      * configurations merged with the one specified here.
712      *
713      * @param token Represents an {@link Activity} or {@link WindowContext}.
714      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
715      * @param splitResDirs An array of split resource paths. Can be null.
716      * @param overlayDirs An array of overlay paths. Can be null.
717      * @param libDirs An array of resource library paths. Can be null.
718      * @param displayId The ID of the display for which to create the resources.
719      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
720      *                       {@code null}. This provides the base override for this token.
721      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
722      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
723      * @param classLoader The class loader to use when inflating Resources. If null, the
724      *                    {@link ClassLoader#getSystemClassLoader()} is used.
725      * @return a Resources object from which to access resources.
726      */
createBaseTokenResources(@onNull IBinder token, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)727     public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
728             @Nullable String resDir,
729             @Nullable String[] splitResDirs,
730             @Nullable String[] overlayDirs,
731             @Nullable String[] libDirs,
732             int displayId,
733             @Nullable Configuration overrideConfig,
734             @NonNull CompatibilityInfo compatInfo,
735             @Nullable ClassLoader classLoader,
736             @Nullable List<ResourcesLoader> loaders) {
737         try {
738             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
739                     "ResourcesManager#createBaseActivityResources");
740             final ResourcesKey key = new ResourcesKey(
741                     resDir,
742                     splitResDirs,
743                     overlayDirs,
744                     libDirs,
745                     displayId,
746                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
747                     compatInfo,
748                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
749             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
750 
751             if (DEBUG) {
752                 Slog.d(TAG, "createBaseActivityResources activity=" + token
753                         + " with key=" + key);
754             }
755 
756             synchronized (this) {
757                 // Force the creation of an ActivityResourcesStruct.
758                 getOrCreateActivityResourcesStructLocked(token);
759             }
760 
761             // Update any existing Activity Resources references.
762             updateResourcesForActivity(token, overrideConfig, displayId,
763                     false /* movedToDifferentDisplay */);
764 
765             rebaseKeyForActivity(token, key);
766 
767             synchronized (this) {
768                 Resources resources = findResourcesForActivityLocked(token, key,
769                         classLoader);
770                 if (resources != null) {
771                     return resources;
772                 }
773             }
774 
775             // Now request an actual Resources object.
776             return createResources(token, key, classLoader, /* apkSupplier */ null);
777         } finally {
778             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
779         }
780     }
781 
782     /**
783      * Rebases a key's override config on top of the Activity's base override.
784      */
rebaseKeyForActivity(IBinder activityToken, ResourcesKey key)785     private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
786         synchronized (this) {
787             final ActivityResources activityResources =
788                     getOrCreateActivityResourcesStructLocked(activityToken);
789 
790             // Rebase the key's override config on top of the Activity's base override.
791             if (key.hasOverrideConfiguration()
792                     && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
793                 final Configuration temp = new Configuration(activityResources.overrideConfig);
794                 temp.updateFrom(key.mOverrideConfiguration);
795                 key.mOverrideConfiguration.setTo(temp);
796             }
797         }
798     }
799 
800     /**
801      * Check WeakReferences and remove any dead references so they don't pile up.
802      */
cleanupReferences(ArrayList<WeakReference<T>> references, ReferenceQueue<T> referenceQueue)803     private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
804             ReferenceQueue<T> referenceQueue) {
805         Reference<? extends T> enduedRef = referenceQueue.poll();
806         if (enduedRef == null) {
807             return;
808         }
809 
810         final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
811         for (; enduedRef != null; enduedRef = referenceQueue.poll()) {
812             deadReferences.add(enduedRef);
813         }
814 
815         ArrayUtils.unstableRemoveIf(references,
816                 (ref) -> ref == null || deadReferences.contains(ref));
817     }
818 
819     /**
820      * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
821      * into the supplier. This should be done while the lock is not held to prevent performing I/O
822      * while holding the lock.
823      */
createApkAssetsSupplierNotLocked(@onNull ResourcesKey key)824     private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
825         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
826                 "ResourcesManager#createApkAssetsSupplierNotLocked");
827         try {
828             final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
829             final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
830             for (int i = 0, n = apkKeys.size(); i < n; i++) {
831                 final ApkKey apkKey = apkKeys.get(i);
832                 try {
833                     supplier.load(apkKey);
834                 } catch (IOException e) {
835                     Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
836                 }
837             }
838             return supplier;
839         } finally {
840             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
841         }
842     }
843 
844     /**
845      * Creates a Resources object set with a ResourcesImpl object matching the given key.
846      *
847      * @param activityToken The Activity this Resources object should be associated with.
848      * @param key The key describing the parameters of the ResourcesImpl object.
849      * @param classLoader The classloader to use for the Resources object.
850      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
851      * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object.
852      * @return A Resources object that gets updated when
853      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
854      *         is called.
855      */
createResources(@ullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)856     private @Nullable Resources createResources(@Nullable IBinder activityToken,
857             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
858             @Nullable ApkAssetsSupplier apkSupplier) {
859         synchronized (this) {
860             if (DEBUG) {
861                 Throwable here = new Throwable();
862                 here.fillInStackTrace();
863                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
864             }
865 
866             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
867             if (resourcesImpl == null) {
868                 return null;
869             }
870 
871             if (activityToken != null) {
872                 return createResourcesForActivityLocked(activityToken, classLoader,
873                         resourcesImpl, key.mCompatInfo);
874             } else {
875                 return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
876             }
877         }
878     }
879 
880     /**
881      * Gets or creates a new Resources object associated with the IBinder token. References returned
882      * by this method live as long as the Activity, meaning they can be cached and used by the
883      * Activity even after a configuration change. If any other parameter is changed
884      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
885      * is updated and handed back to the caller. However, changing the class loader will result in a
886      * new Resources object.
887      * <p/>
888      * If activityToken is null, a cached Resources object will be returned if it matches the
889      * input parameters. Otherwise a new Resources object that satisfies these parameters is
890      * returned.
891      *
892      * @param activityToken Represents an Activity. If null, global resources are assumed.
893      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
894      * @param splitResDirs An array of split resource paths. Can be null.
895      * @param overlayDirs An array of overlay paths. Can be null.
896      * @param libDirs An array of resource library paths. Can be null.
897      * @param displayId The ID of the display for which to create the resources.
898      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
899      * null. Mostly used with Activities that are in multi-window which may override width and
900      * height properties from the base config.
901      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
902      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
903      * @param classLoader The class loader to use when inflating Resources. If null, the
904      * {@link ClassLoader#getSystemClassLoader()} is used.
905      * @return a Resources object from which to access resources.
906      */
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, @Nullable List<ResourcesLoader> loaders)907     public @Nullable Resources getResources(
908             @Nullable IBinder activityToken,
909             @Nullable String resDir,
910             @Nullable String[] splitResDirs,
911             @Nullable String[] overlayDirs,
912             @Nullable String[] libDirs,
913             int displayId,
914             @Nullable Configuration overrideConfig,
915             @NonNull CompatibilityInfo compatInfo,
916             @Nullable ClassLoader classLoader,
917             @Nullable List<ResourcesLoader> loaders) {
918         try {
919             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
920             final ResourcesKey key = new ResourcesKey(
921                     resDir,
922                     splitResDirs,
923                     overlayDirs,
924                     libDirs,
925                     displayId,
926                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
927                     compatInfo,
928                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
929             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
930 
931             if (activityToken != null) {
932                 rebaseKeyForActivity(activityToken, key);
933             }
934 
935             // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
936             // ResourcesManager lock is held.
937             final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
938             return createResources(activityToken, key, classLoader, assetsSupplier);
939         } finally {
940             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
941         }
942     }
943 
944     /**
945      * Updates an Activity's Resources object with overrideConfig. The Resources object
946      * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
947      * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will
948      * have the updated configuration.
949      *
950      * @param activityToken The Activity token.
951      * @param overrideConfig The configuration override to update.
952      * @param displayId Id of the display where activity currently resides.
953      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
954      */
updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig, int displayId, boolean movedToDifferentDisplay)955     public void updateResourcesForActivity(@NonNull IBinder activityToken,
956             @Nullable Configuration overrideConfig, int displayId,
957             boolean movedToDifferentDisplay) {
958         try {
959             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
960                     "ResourcesManager#updateResourcesForActivity");
961             synchronized (this) {
962                 final ActivityResources activityResources =
963                         getOrCreateActivityResourcesStructLocked(activityToken);
964 
965                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
966                         && !movedToDifferentDisplay) {
967                     // They are the same and no change of display id, no work to do.
968                     return;
969                 }
970 
971                 // Grab a copy of the old configuration so we can create the delta's of each
972                 // Resources object associated with this Activity.
973                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
974 
975                 // Update the Activity's base override.
976                 if (overrideConfig != null) {
977                     activityResources.overrideConfig.setTo(overrideConfig);
978                 } else {
979                     activityResources.overrideConfig.unset();
980                 }
981 
982                 if (DEBUG) {
983                     Throwable here = new Throwable();
984                     here.fillInStackTrace();
985                     Slog.d(TAG, "updating resources override for activity=" + activityToken
986                             + " from oldConfig="
987                             + Configuration.resourceQualifierString(oldConfig)
988                             + " to newConfig="
989                             + Configuration.resourceQualifierString(
990                             activityResources.overrideConfig) + " displayId=" + displayId,
991                             here);
992                 }
993 
994 
995                 // Rebase each Resources associated with this Activity.
996                 final int refCount = activityResources.activityResources.size();
997                 for (int i = 0; i < refCount; i++) {
998                     final WeakReference<Resources> weakResRef =
999                             activityResources.activityResources.get(i);
1000 
1001                     final Resources resources = weakResRef.get();
1002                     if (resources == null) {
1003                         continue;
1004                     }
1005 
1006                     final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
1007                             overrideConfig, displayId);
1008                     if (newKey != null) {
1009                         final ResourcesImpl resourcesImpl =
1010                                 findOrCreateResourcesImplForKeyLocked(newKey);
1011                         if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
1012                             // Set the ResourcesImpl, updating it for all users of this Resources
1013                             // object.
1014                             resources.setImpl(resourcesImpl);
1015                         }
1016                     }
1017                 }
1018             }
1019         } finally {
1020             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1021         }
1022     }
1023 
1024     /**
1025      * Rebases an updated override config over any old override config and returns the new one
1026      * that an Activity's Resources should be set to.
1027      */
1028     @Nullable
rebaseActivityOverrideConfig(@onNull Resources resources, @NonNull Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig, int displayId)1029     private ResourcesKey rebaseActivityOverrideConfig(@NonNull Resources resources,
1030             @NonNull Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
1031             int displayId) {
1032         // Extract the ResourcesKey that was last used to create the Resources for this
1033         // activity.
1034         final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1035         if (oldKey == null) {
1036             Slog.e(TAG, "can't find ResourcesKey for resources impl="
1037                     + resources.getImpl());
1038             return null;
1039         }
1040 
1041         // Build the new override configuration for this ResourcesKey.
1042         final Configuration rebasedOverrideConfig = new Configuration();
1043         if (newOverrideConfig != null) {
1044             rebasedOverrideConfig.setTo(newOverrideConfig);
1045         }
1046 
1047         final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
1048         if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
1049             // Generate a delta between the old base Activity override configuration and
1050             // the actual final override configuration that was used to figure out the
1051             // real delta this Resources object wanted.
1052             Configuration overrideOverrideConfig = Configuration.generateDelta(
1053                     oldOverrideConfig, oldKey.mOverrideConfiguration);
1054             rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
1055         }
1056 
1057         // Create the new ResourcesKey with the rebased override config.
1058         final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
1059                 oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs,
1060                 displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
1061 
1062         if (DEBUG) {
1063             Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
1064                     + " to newKey=" + newKey + ", displayId=" + displayId);
1065         }
1066 
1067         return newKey;
1068     }
1069 
1070     @TestApi
applyConfigurationToResources(@onNull Configuration config, @Nullable CompatibilityInfo compat)1071     public final boolean applyConfigurationToResources(@NonNull Configuration config,
1072             @Nullable CompatibilityInfo compat) {
1073         synchronized(this) {
1074             return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
1075         }
1076     }
1077 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat)1078     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
1079             @Nullable CompatibilityInfo compat) {
1080         return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
1081     }
1082 
1083     /** Applies the global configuration to the managed resources. */
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments)1084     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
1085             @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
1086         try {
1087             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1088                     "ResourcesManager#applyConfigurationToResourcesLocked");
1089 
1090             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
1091                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
1092                         + mResConfiguration.seq + ", newSeq=" + config.seq);
1093                 return false;
1094             }
1095             int changes = mResConfiguration.updateFrom(config);
1096             // Things might have changed in display manager, so clear the cached displays.
1097             mAdjustedDisplays.clear();
1098 
1099             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
1100 
1101             if (compat != null && (mResCompatibilityInfo == null ||
1102                     !mResCompatibilityInfo.equals(compat))) {
1103                 mResCompatibilityInfo = compat;
1104                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
1105                         | ActivityInfo.CONFIG_SCREEN_SIZE
1106                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
1107             }
1108 
1109             if (adjustments != null) {
1110                 // Currently the only case where the adjustment takes effect is to simulate placing
1111                 // an app in a rotated display.
1112                 adjustments.adjustGlobalAppMetrics(defaultDisplayMetrics);
1113             }
1114             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
1115 
1116             ApplicationPackageManager.configurationChanged();
1117             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
1118 
1119             Configuration tmpConfig = new Configuration();
1120 
1121             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1122                 ResourcesKey key = mResourceImpls.keyAt(i);
1123                 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1124                 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
1125                 if (r != null) {
1126                     applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
1127                 } else {
1128                     mResourceImpls.removeAt(i);
1129                 }
1130             }
1131 
1132             return changes != 0;
1133         } finally {
1134             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1135         }
1136     }
1137 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat, Configuration tmpConfig, ResourcesKey key, ResourcesImpl resourcesImpl)1138     private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
1139             @Nullable CompatibilityInfo compat, Configuration tmpConfig,
1140             ResourcesKey key, ResourcesImpl resourcesImpl) {
1141         if (DEBUG || DEBUG_CONFIGURATION) {
1142             Slog.v(TAG, "Changing resources "
1143                     + resourcesImpl + " config to: " + config);
1144         }
1145 
1146         tmpConfig.setTo(config);
1147 
1148         // Apply the override configuration before setting the display adjustments to ensure that
1149         // the process config does not override activity display adjustments.
1150         final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
1151         if (hasOverrideConfiguration) {
1152             tmpConfig.updateFrom(key.mOverrideConfiguration);
1153         }
1154 
1155         // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
1156         // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
1157         // update internally.
1158         DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
1159         if (compat != null) {
1160             daj = new DisplayAdjustments(daj);
1161             daj.setCompatibilityInfo(compat);
1162         }
1163 
1164         final int displayId = key.mDisplayId;
1165         if (displayId == Display.DEFAULT_DISPLAY) {
1166             daj.setConfiguration(tmpConfig);
1167         }
1168         DisplayMetrics dm = getDisplayMetrics(displayId, daj);
1169         if (displayId != Display.DEFAULT_DISPLAY) {
1170             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
1171 
1172             // Re-apply the override configuration to ensure that configuration contexts based on
1173             // a display context (ex: createDisplayContext().createConfigurationContext()) have the
1174             // correct override.
1175             if (hasOverrideConfiguration) {
1176                 tmpConfig.updateFrom(key.mOverrideConfiguration);
1177             }
1178         }
1179 
1180         resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
1181     }
1182 
1183     /**
1184      * Appends the library asset path to any ResourcesImpl object that contains the main
1185      * assetPath.
1186      * @param assetPath The main asset path for which to add the library asset path.
1187      * @param libAsset The library asset path to add.
1188      */
1189     @UnsupportedAppUsage
appendLibAssetForMainAssetPath(String assetPath, String libAsset)1190     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
1191         appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset });
1192     }
1193 
1194     /**
1195      * Appends the library asset paths to any ResourcesImpl object that contains the main
1196      * assetPath.
1197      * @param assetPath The main asset path for which to add the library asset path.
1198      * @param libAssets The library asset paths to add.
1199      */
appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets)1200     public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
1201         synchronized (this) {
1202             // Record which ResourcesImpl need updating
1203             // (and what ResourcesKey they should update to).
1204             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1205 
1206             final int implCount = mResourceImpls.size();
1207             for (int i = 0; i < implCount; i++) {
1208                 final ResourcesKey key = mResourceImpls.keyAt(i);
1209                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1210                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1211                 if (impl != null && Objects.equals(key.mResDir, assetPath)) {
1212                     String[] newLibAssets = key.mLibDirs;
1213                     for (String libAsset : libAssets) {
1214                         newLibAssets =
1215                                 ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
1216                     }
1217 
1218                     if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
1219                         updatedResourceKeys.put(impl, new ResourcesKey(
1220                                 key.mResDir,
1221                                 key.mSplitResDirs,
1222                                 key.mOverlayDirs,
1223                                 newLibAssets,
1224                                 key.mDisplayId,
1225                                 key.mOverrideConfiguration,
1226                                 key.mCompatInfo,
1227                                 key.mLoaders));
1228                     }
1229                 }
1230             }
1231 
1232             redirectResourcesToNewImplLocked(updatedResourceKeys);
1233         }
1234     }
1235 
1236     // TODO(adamlesinski): Make this accept more than just overlay directories.
applyNewResourceDirsLocked(@onNull final ApplicationInfo appInfo, @Nullable final String[] oldPaths)1237     final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
1238             @Nullable final String[] oldPaths) {
1239         try {
1240             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1241                     "ResourcesManager#applyNewResourceDirsLocked");
1242 
1243             String baseCodePath = appInfo.getBaseCodePath();
1244 
1245             final int myUid = Process.myUid();
1246             String[] newSplitDirs = appInfo.uid == myUid
1247                     ? appInfo.splitSourceDirs
1248                     : appInfo.splitPublicSourceDirs;
1249 
1250             // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
1251             String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
1252             String[] copiedResourceDirs = ArrayUtils.cloneOrNull(appInfo.resourceDirs);
1253 
1254             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1255             final int implCount = mResourceImpls.size();
1256             for (int i = 0; i < implCount; i++) {
1257                 final ResourcesKey key = mResourceImpls.keyAt(i);
1258                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1259                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1260 
1261                 if (impl == null) {
1262                     continue;
1263                 }
1264 
1265                 if (key.mResDir == null
1266                         || key.mResDir.equals(baseCodePath)
1267                         || ArrayUtils.contains(oldPaths, key.mResDir)) {
1268                     updatedResourceKeys.put(impl, new ResourcesKey(
1269                             baseCodePath,
1270                             copiedSplitDirs,
1271                             copiedResourceDirs,
1272                             key.mLibDirs,
1273                             key.mDisplayId,
1274                             key.mOverrideConfiguration,
1275                             key.mCompatInfo,
1276                             key.mLoaders
1277                     ));
1278                 }
1279             }
1280 
1281             redirectResourcesToNewImplLocked(updatedResourceKeys);
1282         } finally {
1283             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1284         }
1285     }
1286 
redirectResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1287     private void redirectResourcesToNewImplLocked(
1288             @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
1289         // Bail early if there is no work to do.
1290         if (updatedResourceKeys.isEmpty()) {
1291             return;
1292         }
1293 
1294         // Update any references to ResourcesImpl that require reloading.
1295         final int resourcesCount = mResourceReferences.size();
1296         for (int i = 0; i < resourcesCount; i++) {
1297             final WeakReference<Resources> ref = mResourceReferences.get(i);
1298             final Resources r = ref != null ? ref.get() : null;
1299             if (r != null) {
1300                 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1301                 if (key != null) {
1302                     final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1303                     if (impl == null) {
1304                         throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
1305                     }
1306                     r.setImpl(impl);
1307                 }
1308             }
1309         }
1310 
1311         // Update any references to ResourcesImpl that require reloading for each Activity.
1312         for (ActivityResources activityResources : mActivityResourceReferences.values()) {
1313             final int resCount = activityResources.activityResources.size();
1314             for (int i = 0; i < resCount; i++) {
1315                 final WeakReference<Resources> ref = activityResources.activityResources.get(i);
1316                 final Resources r = ref != null ? ref.get() : null;
1317                 if (r != null) {
1318                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1319                     if (key != null) {
1320                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1321                         if (impl == null) {
1322                             throw new Resources.NotFoundException(
1323                                     "failed to redirect ResourcesImpl");
1324                         }
1325                         r.setImpl(impl);
1326                     }
1327                 }
1328             }
1329         }
1330     }
1331 
1332     /**
1333      * Overrides the display adjustments of all resources which are associated with the given token.
1334      *
1335      * @param token The token that owns the resources.
1336      * @param override The operation to override the existing display adjustments. If it is null,
1337      *                 the override adjustments will be cleared.
1338      * @return {@code true} if the override takes effect.
1339      */
overrideTokenDisplayAdjustments(IBinder token, @Nullable Consumer<DisplayAdjustments> override)1340     public boolean overrideTokenDisplayAdjustments(IBinder token,
1341             @Nullable Consumer<DisplayAdjustments> override) {
1342         boolean handled = false;
1343         synchronized (this) {
1344             final ActivityResources tokenResources = mActivityResourceReferences.get(token);
1345             if (tokenResources == null) {
1346                 return false;
1347             }
1348             final ArrayList<WeakReference<Resources>> resourcesRefs =
1349                     tokenResources.activityResources;
1350             for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
1351                 final Resources res = resourcesRefs.get(i).get();
1352                 if (res != null) {
1353                     res.overrideDisplayAdjustments(override);
1354                     handled = true;
1355                 }
1356             }
1357         }
1358         return handled;
1359     }
1360 
1361     private class UpdateHandler implements Resources.UpdateCallbacks {
1362 
1363         /**
1364          * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
1365          * instance uses.
1366          */
1367         @Override
onLoadersChanged(@onNull Resources resources, @NonNull List<ResourcesLoader> newLoader)1368         public void onLoadersChanged(@NonNull Resources resources,
1369                 @NonNull List<ResourcesLoader> newLoader) {
1370             synchronized (ResourcesManager.this) {
1371                 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1372                 if (oldKey == null) {
1373                     throw new IllegalArgumentException("Cannot modify resource loaders of"
1374                             + " ResourcesImpl not registered with ResourcesManager");
1375                 }
1376 
1377                 final ResourcesKey newKey = new ResourcesKey(
1378                         oldKey.mResDir,
1379                         oldKey.mSplitResDirs,
1380                         oldKey.mOverlayDirs,
1381                         oldKey.mLibDirs,
1382                         oldKey.mDisplayId,
1383                         oldKey.mOverrideConfiguration,
1384                         oldKey.mCompatInfo,
1385                         newLoader.toArray(new ResourcesLoader[0]));
1386 
1387                 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
1388                 resources.setImpl(impl);
1389             }
1390         }
1391 
1392         /**
1393          * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
1394          * {@code loader} to apply any changes of the set of {@link ApkAssets}.
1395          **/
1396         @Override
onLoaderUpdated(@onNull ResourcesLoader loader)1397         public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
1398             synchronized (ResourcesManager.this) {
1399                 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
1400                         new ArrayMap<>();
1401 
1402                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1403                     final ResourcesKey key = mResourceImpls.keyAt(i);
1404                     final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
1405                     if (impl == null || impl.get() == null
1406                             || !ArrayUtils.contains(key.mLoaders, loader)) {
1407                         continue;
1408                     }
1409 
1410                     mResourceImpls.remove(key);
1411                     updatedResourceImplKeys.put(impl.get(), key);
1412                 }
1413 
1414                 redirectResourcesToNewImplLocked(updatedResourceImplKeys);
1415             }
1416         }
1417     }
1418 }
1419