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