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