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