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