ResourcesManager.java revision 98bf12f99989ba2550fac83ee48ecbb6f1582f07
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    }
154
155    /**
156     * Protected so that tests can override and returns something a fixed value.
157     */
158    @VisibleForTesting
159    protected DisplayMetrics getDisplayMetrics(int displayId) {
160        DisplayMetrics dm = new DisplayMetrics();
161        final Display display =
162                getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
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 @NonNull 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                throw new IllegalArgumentException("failed to add asset path " + key.mResDir);
254            }
255        }
256
257        if (key.mSplitResDirs != null) {
258            for (final String splitResDir : key.mSplitResDirs) {
259                if (assets.addAssetPath(splitResDir) == 0) {
260                    throw new IllegalArgumentException(
261                            "failed to add split asset path " + splitResDir);
262                }
263            }
264        }
265
266        if (key.mOverlayDirs != null) {
267            for (final String idmapPath : key.mOverlayDirs) {
268                assets.addOverlayPath(idmapPath);
269            }
270        }
271
272        if (key.mLibDirs != null) {
273            for (final String libDir : key.mLibDirs) {
274                if (libDir.endsWith(".apk")) {
275                    // Avoid opening files we know do not have resources,
276                    // like code-only .jar files.
277                    if (assets.addAssetPath(libDir) == 0) {
278                        Log.w(TAG, "Asset path '" + libDir +
279                                "' does not exist or contains no resources.");
280                    }
281                }
282            }
283        }
284        return assets;
285    }
286
287    private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
288        Configuration config;
289        final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
290        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
291        if (!isDefaultDisplay || hasOverrideConfig) {
292            config = new Configuration(getConfiguration());
293            if (!isDefaultDisplay) {
294                applyNonDefaultDisplayMetricsToConfiguration(dm, config);
295            }
296            if (hasOverrideConfig) {
297                config.updateFrom(key.mOverrideConfiguration);
298                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
299            }
300        } else {
301            config = getConfiguration();
302        }
303        return config;
304    }
305
306    private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
307        AssetManager assets = createAssetManager(key);
308        DisplayMetrics dm = getDisplayMetrics(key.mDisplayId);
309        Configuration config = generateConfig(key, dm);
310        ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
311        if (DEBUG) {
312            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
313        }
314        return impl;
315    }
316
317    /**
318     * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
319     *
320     * @param key The key to match.
321     * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
322     */
323    private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
324        WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
325        ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
326        if (impl != null && impl.getAssets().isUpToDate()) {
327            return impl;
328        }
329        return null;
330    }
331
332    /**
333     * Find the ResourcesKey that this ResourcesImpl object is associated with.
334     * @return the ResourcesKey or null if none was found.
335     */
336    private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) {
337        final int refCount = mResourceImpls.size();
338        for (int i = 0; i < refCount; i++) {
339            WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
340            ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
341            if (impl != null && resourceImpl == impl) {
342                return mResourceImpls.keyAt(i);
343            }
344        }
345        return null;
346    }
347
348    private ActivityResources getOrCreateActivityResourcesStructLocked(
349            @NonNull IBinder activityToken) {
350        ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
351        if (activityResources == null) {
352            activityResources = new ActivityResources();
353            mActivityResourceReferences.put(activityToken, activityResources);
354        }
355        return activityResources;
356    }
357
358    /**
359     * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
360     * or the class loader is different.
361     */
362    private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
363            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
364        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
365                activityToken);
366
367        final int refCount = activityResources.activityResources.size();
368        for (int i = 0; i < refCount; i++) {
369            WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
370            Resources resources = weakResourceRef.get();
371
372            if (resources != null
373                    && Objects.equals(resources.getClassLoader(), classLoader)
374                    && resources.getImpl() == impl) {
375                if (DEBUG) {
376                    Slog.d(TAG, "- using existing ref=" + resources);
377                }
378                return resources;
379            }
380        }
381
382        Resources resources = new Resources(classLoader);
383        resources.setImpl(impl);
384        activityResources.activityResources.add(new WeakReference<>(resources));
385        if (DEBUG) {
386            Slog.d(TAG, "- creating new ref=" + resources);
387            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
388        }
389        return resources;
390    }
391
392    /**
393     * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
394     * otherwise creates a new Resources object.
395     */
396    private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
397            @NonNull ResourcesImpl impl) {
398        // Find an existing Resources that has this ResourcesImpl set.
399        final int refCount = mResourceReferences.size();
400        for (int i = 0; i < refCount; i++) {
401            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
402            Resources resources = weakResourceRef.get();
403            if (resources != null &&
404                    Objects.equals(resources.getClassLoader(), classLoader) &&
405                    resources.getImpl() == impl) {
406                if (DEBUG) {
407                    Slog.d(TAG, "- using existing ref=" + resources);
408                }
409                return resources;
410            }
411        }
412
413        // Create a new Resources reference and use the existing ResourcesImpl object.
414        Resources resources = new Resources(classLoader);
415        resources.setImpl(impl);
416        mResourceReferences.add(new WeakReference<>(resources));
417        if (DEBUG) {
418            Slog.d(TAG, "- creating new ref=" + resources);
419            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
420        }
421        return resources;
422    }
423
424    /**
425     * Creates base resources for an Activity. Calls to
426     * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
427     * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
428     * configurations merged with the one specified here.
429     *
430     * @param activityToken Represents an Activity.
431     * @param resDir The base resource path. Can be null (only framework resources will be loaded).
432     * @param splitResDirs An array of split resource paths. Can be null.
433     * @param overlayDirs An array of overlay paths. Can be null.
434     * @param libDirs An array of resource library paths. Can be null.
435     * @param displayId The ID of the display for which to create the resources.
436     * @param overrideConfig The configuration to apply on top of the base configuration. Can be
437     *                       null. This provides the base override for this Activity.
438     * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
439     *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
440     * @param classLoader The class loader to use when inflating Resources. If null, the
441     *                    {@link ClassLoader#getSystemClassLoader()} is used.
442     * @return a Resources object from which to access resources.
443     */
444    public Resources createBaseActivityResources(@NonNull IBinder activityToken,
445            @Nullable String resDir,
446            @Nullable String[] splitResDirs,
447            @Nullable String[] overlayDirs,
448            @Nullable String[] libDirs,
449            int displayId,
450            @Nullable Configuration overrideConfig,
451            @NonNull CompatibilityInfo compatInfo,
452            @Nullable ClassLoader classLoader) {
453        try {
454            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
455                    "ResourcesManager#createBaseActivityResources");
456            final ResourcesKey key = new ResourcesKey(
457                    resDir,
458                    splitResDirs,
459                    overlayDirs,
460                    libDirs,
461                    displayId,
462                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
463                    compatInfo);
464            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
465
466            if (DEBUG) {
467                Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
468                        + " with key=" + key);
469            }
470
471            synchronized (this) {
472                // Force the creation of an ActivityResourcesStruct.
473                getOrCreateActivityResourcesStructLocked(activityToken);
474            }
475
476            // Update any existing Activity Resources references.
477            updateResourcesForActivity(activityToken, overrideConfig);
478
479            // Now request an actual Resources object.
480            return getOrCreateResources(activityToken, key, classLoader);
481        } finally {
482            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
483        }
484    }
485
486    /**
487     * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
488     * or creates one if it doesn't exist.
489     *
490     * @param activityToken The Activity this Resources object should be associated with.
491     * @param key The key describing the parameters of the ResourcesImpl object.
492     * @param classLoader The classloader to use for the Resources object.
493     *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
494     * @return A Resources object that gets updated when
495     *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
496     *         is called.
497     */
498    private Resources getOrCreateResources(@Nullable IBinder activityToken,
499            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
500        synchronized (this) {
501            if (DEBUG) {
502                Throwable here = new Throwable();
503                here.fillInStackTrace();
504                Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
505            }
506
507            if (activityToken != null) {
508                final ActivityResources activityResources =
509                        getOrCreateActivityResourcesStructLocked(activityToken);
510
511                // Clean up any dead references so they don't pile up.
512                ArrayUtils.unstableRemoveIf(activityResources.activityResources,
513                        sEmptyReferencePredicate);
514
515                // Rebase the key's override config on top of the Activity's base override.
516                if (key.hasOverrideConfiguration()
517                        && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
518                    final Configuration temp = new Configuration(activityResources.overrideConfig);
519                    temp.updateFrom(key.mOverrideConfiguration);
520                    key.mOverrideConfiguration.setTo(temp);
521                }
522
523                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
524                if (resourcesImpl != null) {
525                    if (DEBUG) {
526                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
527                    }
528                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
529                            resourcesImpl);
530                }
531
532                // We will create the ResourcesImpl object outside of holding this lock.
533
534            } else {
535                // Clean up any dead references so they don't pile up.
536                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
537
538                // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
539                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
540                if (resourcesImpl != null) {
541                    if (DEBUG) {
542                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
543                    }
544                    return getOrCreateResourcesLocked(classLoader, resourcesImpl);
545                }
546
547                // We will create the ResourcesImpl object outside of holding this lock.
548            }
549        }
550
551        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
552        ResourcesImpl resourcesImpl = createResourcesImpl(key);
553
554        synchronized (this) {
555            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
556            if (existingResourcesImpl != null) {
557                if (DEBUG) {
558                    Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
559                            + " new impl=" + resourcesImpl);
560                }
561                resourcesImpl.getAssets().close();
562                resourcesImpl = existingResourcesImpl;
563            } else {
564                // Add this ResourcesImpl to the cache.
565                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
566            }
567
568            final Resources resources;
569            if (activityToken != null) {
570                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
571                        resourcesImpl);
572            } else {
573                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
574            }
575            return resources;
576        }
577    }
578
579    /**
580     * Gets or creates a new Resources object associated with the IBinder token. References returned
581     * by this method live as long as the Activity, meaning they can be cached and used by the
582     * Activity even after a configuration change. If any other parameter is changed
583     * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
584     * is updated and handed back to the caller. However, changing the class loader will result in a
585     * new Resources object.
586     * <p/>
587     * If activityToken is null, a cached Resources object will be returned if it matches the
588     * input parameters. Otherwise a new Resources object that satisfies these parameters is
589     * returned.
590     *
591     * @param activityToken Represents an Activity. If null, global resources are assumed.
592     * @param resDir The base resource path. Can be null (only framework resources will be loaded).
593     * @param splitResDirs An array of split resource paths. Can be null.
594     * @param overlayDirs An array of overlay paths. Can be null.
595     * @param libDirs An array of resource library paths. Can be null.
596     * @param displayId The ID of the display for which to create the resources.
597     * @param overrideConfig The configuration to apply on top of the base configuration. Can be
598     * null. Mostly used with Activities that are in multi-window which may override width and
599     * height properties from the base config.
600     * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
601     * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
602     * @param classLoader The class loader to use when inflating Resources. If null, the
603     * {@link ClassLoader#getSystemClassLoader()} is used.
604     * @return a Resources object from which to access resources.
605     */
606    public Resources getResources(@Nullable IBinder activityToken,
607            @Nullable String resDir,
608            @Nullable String[] splitResDirs,
609            @Nullable String[] overlayDirs,
610            @Nullable String[] libDirs,
611            int displayId,
612            @Nullable Configuration overrideConfig,
613            @NonNull CompatibilityInfo compatInfo,
614            @Nullable ClassLoader classLoader) {
615        try {
616            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
617            final ResourcesKey key = new ResourcesKey(
618                    resDir,
619                    splitResDirs,
620                    overlayDirs,
621                    libDirs,
622                    displayId,
623                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
624                    compatInfo);
625            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
626            return getOrCreateResources(activityToken, key, classLoader);
627        } finally {
628            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
629        }
630    }
631
632    /**
633     * Updates an Activity's Resources object with overrideConfig. The Resources object
634     * that was previously returned by
635     * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
636     * CompatibilityInfo, ClassLoader)} is
637     * still valid and will have the updated configuration.
638     * @param activityToken The Activity token.
639     * @param overrideConfig The configuration override to update.
640     */
641    public void updateResourcesForActivity(@NonNull IBinder activityToken,
642            @Nullable Configuration overrideConfig) {
643        try {
644            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
645                    "ResourcesManager#updateResourcesForActivity");
646            synchronized (this) {
647                final ActivityResources activityResources =
648                        getOrCreateActivityResourcesStructLocked(activityToken);
649
650                if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
651                    // They are the same, no work to do.
652                    return;
653                }
654
655                // Grab a copy of the old configuration so we can create the delta's of each
656                // Resources object associated with this Activity.
657                final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
658
659                // Update the Activity's base override.
660                if (overrideConfig != null) {
661                    activityResources.overrideConfig.setTo(overrideConfig);
662                } else {
663                    activityResources.overrideConfig.setToDefaults();
664                }
665
666                if (DEBUG) {
667                    Throwable here = new Throwable();
668                    here.fillInStackTrace();
669                    Slog.d(TAG, "updating resources override for activity=" + activityToken
670                            + " from oldConfig="
671                            + Configuration.resourceQualifierString(oldConfig)
672                            + " to newConfig="
673                            + Configuration.resourceQualifierString(
674                            activityResources.overrideConfig),
675                            here);
676                }
677
678                final boolean activityHasOverrideConfig =
679                        !activityResources.overrideConfig.equals(Configuration.EMPTY);
680
681                // Rebase each Resources associated with this Activity.
682                final int refCount = activityResources.activityResources.size();
683                for (int i = 0; i < refCount; i++) {
684                    WeakReference<Resources> weakResRef = activityResources.activityResources.get(
685                            i);
686                    Resources resources = weakResRef.get();
687                    if (resources == null) {
688                        continue;
689                    }
690
691                    // Extract the ResourcesKey that was last used to create the Resources for this
692                    // activity.
693                    final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
694                    if (oldKey == null) {
695                        Slog.e(TAG, "can't find ResourcesKey for resources impl="
696                                + resources.getImpl());
697                        continue;
698                    }
699
700                    // Build the new override configuration for this ResourcesKey.
701                    final Configuration rebasedOverrideConfig = new Configuration();
702                    if (overrideConfig != null) {
703                        rebasedOverrideConfig.setTo(overrideConfig);
704                    }
705
706                    if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
707                        // Generate a delta between the old base Activity override configuration and
708                        // the actual final override configuration that was used to figure out the
709                        // real delta this Resources object wanted.
710                        Configuration overrideOverrideConfig = Configuration.generateDelta(
711                                oldConfig, oldKey.mOverrideConfiguration);
712                        rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
713                    }
714
715                    // Create the new ResourcesKey with the rebased override config.
716                    final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
717                            oldKey.mSplitResDirs,
718                            oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
719                            rebasedOverrideConfig, oldKey.mCompatInfo);
720
721                    if (DEBUG) {
722                        Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
723                                + " to newKey=" + newKey);
724                    }
725
726                    ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
727                    if (resourcesImpl == null) {
728                        resourcesImpl = createResourcesImpl(newKey);
729                        mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
730                    }
731
732                    if (resourcesImpl != resources.getImpl()) {
733                        // Set the ResourcesImpl, updating it for all users of this Resources
734                        // object.
735                        resources.setImpl(resourcesImpl);
736                    }
737                }
738            }
739        } finally {
740            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
741        }
742    }
743
744    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
745                                                             @Nullable CompatibilityInfo compat) {
746        try {
747            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
748                    "ResourcesManager#applyConfigurationToResourcesLocked");
749
750            if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
751                if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
752                        + mResConfiguration.seq + ", newSeq=" + config.seq);
753                return false;
754            }
755            int changes = mResConfiguration.updateFrom(config);
756            // Things might have changed in display manager, so clear the cached displays.
757            mDisplays.clear();
758            DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
759
760            if (compat != null && (mResCompatibilityInfo == null ||
761                    !mResCompatibilityInfo.equals(compat))) {
762                mResCompatibilityInfo = compat;
763                changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
764                        | ActivityInfo.CONFIG_SCREEN_SIZE
765                        | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
766            }
767
768            Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
769
770            ApplicationPackageManager.configurationChanged();
771            //Slog.i(TAG, "Configuration changed in " + currentPackageName());
772
773            Configuration tmpConfig = null;
774
775            for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
776                ResourcesKey key = mResourceImpls.keyAt(i);
777                ResourcesImpl r = mResourceImpls.valueAt(i).get();
778                if (r != null) {
779                    if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
780                            + r + " config to: " + config);
781                    int displayId = key.mDisplayId;
782                    boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
783                    DisplayMetrics dm = defaultDisplayMetrics;
784                    final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
785                    if (!isDefaultDisplay || hasOverrideConfiguration) {
786                        if (tmpConfig == null) {
787                            tmpConfig = new Configuration();
788                        }
789                        tmpConfig.setTo(config);
790                        if (!isDefaultDisplay) {
791                            dm = getDisplayMetrics(displayId);
792                            applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
793                        }
794                        if (hasOverrideConfiguration) {
795                            tmpConfig.updateFrom(key.mOverrideConfiguration);
796                        }
797                        r.updateConfiguration(tmpConfig, dm, compat);
798                    } else {
799                        r.updateConfiguration(config, dm, compat);
800                    }
801                    //Slog.i(TAG, "Updated app resources " + v.getKey()
802                    //        + " " + r + ": " + r.getConfiguration());
803                } else {
804                    //Slog.i(TAG, "Removing old resources " + v.getKey());
805                    mResourceImpls.removeAt(i);
806                }
807            }
808
809            return changes != 0;
810        } finally {
811            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
812        }
813    }
814}
815