ResourcesManager.java revision 082614c6a57a115ee0c5975e3579bf34a178c0f8
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.util.ArrayMap;
33import android.util.DisplayMetrics;
34import android.util.LocaleList;
35import android.util.Log;
36import android.util.Pair;
37import android.util.Slog;
38import android.view.Display;
39import android.view.DisplayAdjustments;
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.util.ArrayUtils;
42
43import java.lang.ref.WeakReference;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.HashSet;
47import java.util.Objects;
48import java.util.WeakHashMap;
49import java.util.function.Predicate;
50
51/** @hide */
52public class ResourcesManager {
53    static final String TAG = "ResourcesManager";
54    private static final boolean DEBUG = false;
55
56    private static ResourcesManager sResourcesManager;
57
58    /**
59     * Predicate that returns true if a WeakReference is gc'ed.
60     */
61    private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
62            new Predicate<WeakReference<Resources>>() {
63                @Override
64                public boolean test(WeakReference<Resources> weakRef) {
65                    return weakRef == null || weakRef.get() == null;
66                }
67            };
68
69    private String[] mSystemLocales = {};
70    private final HashSet<String> mNonSystemLocales = new HashSet<>();
71    private boolean mHasNonSystemLocales = false;
72
73    /**
74     * The global compatibility settings.
75     */
76    private CompatibilityInfo mResCompatibilityInfo;
77
78    /**
79     * The global configuration upon which all Resources are based. Multi-window Resources
80     * apply their overrides to this configuration.
81     */
82    private final Configuration mResConfiguration = new Configuration();
83
84    /**
85     * A mapping of ResourceImpls and their configurations. These are heavy weight objects
86     * which should be reused as much as possible.
87     */
88    private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
89            new ArrayMap<>();
90
91    /**
92     * A list of Resource references that can be reused.
93     */
94    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
95
96    /**
97     * Each Activity may have only one Resources object.
98     */
99    private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
100            new WeakHashMap<>();
101
102    /**
103     * A cache of DisplayId to DisplayAdjustments.
104     */
105    private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
106            new ArrayMap<>();
107
108    public static ResourcesManager getInstance() {
109        synchronized (ResourcesManager.class) {
110            if (sResourcesManager == null) {
111                sResourcesManager = new ResourcesManager();
112            }
113            return sResourcesManager;
114        }
115    }
116
117    public Configuration getConfiguration() {
118        return mResConfiguration;
119    }
120
121    DisplayMetrics getDisplayMetricsLocked() {
122        return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
123    }
124
125    /**
126     * Protected so that tests can override and returns something a fixed value.
127     */
128    @VisibleForTesting
129    protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
130        DisplayMetrics dm = new DisplayMetrics();
131        final Display display =
132                getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
133        if (display != null) {
134            display.getMetrics(dm);
135        } else {
136            dm.setToDefaults();
137        }
138        return dm;
139    }
140
141    private static void applyNonDefaultDisplayMetricsToConfiguration(
142            @NonNull DisplayMetrics dm, @NonNull Configuration config) {
143        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
144        config.densityDpi = dm.densityDpi;
145        config.screenWidthDp = (int) (dm.widthPixels / dm.density);
146        config.screenHeightDp = (int) (dm.heightPixels / dm.density);
147        int sl = Configuration.resetScreenLayout(config.screenLayout);
148        if (dm.widthPixels > dm.heightPixels) {
149            config.orientation = Configuration.ORIENTATION_LANDSCAPE;
150            config.screenLayout = Configuration.reduceScreenLayout(sl,
151                    config.screenWidthDp, config.screenHeightDp);
152        } else {
153            config.orientation = Configuration.ORIENTATION_PORTRAIT;
154            config.screenLayout = Configuration.reduceScreenLayout(sl,
155                    config.screenHeightDp, config.screenWidthDp);
156        }
157        config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
158        config.compatScreenWidthDp = config.screenWidthDp;
159        config.compatScreenHeightDp = config.screenHeightDp;
160        config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
161    }
162
163    public boolean applyCompatConfigurationLocked(int displayDensity,
164            @NonNull Configuration compatConfiguration) {
165        if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
166            mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
167            return true;
168        }
169        return false;
170    }
171
172    /**
173     * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
174     * available.
175     *
176     * @param displayId display Id.
177     * @param displayAdjustments display adjustments.
178     */
179    public Display getAdjustedDisplay(final int displayId,
180            @Nullable DisplayAdjustments displayAdjustments) {
181        final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
182                ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
183        final Pair<Integer, DisplayAdjustments> key =
184                Pair.create(displayId, displayAdjustmentsCopy);
185        synchronized (this) {
186            WeakReference<Display> wd = mDisplays.get(key);
187            if (wd != null) {
188                final Display display = wd.get();
189                if (display != null) {
190                    return display;
191                }
192            }
193            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
194            if (dm == null) {
195                // may be null early in system startup
196                return null;
197            }
198            final Display display = dm.getCompatibleDisplay(displayId, key.second);
199            if (display != null) {
200                mDisplays.put(key, new WeakReference<>(display));
201            }
202            return display;
203        }
204    }
205
206    /**
207     * Creates an AssetManager from the paths within the ResourcesKey.
208     *
209     * This can be overridden in tests so as to avoid creating a real AssetManager with
210     * real APK paths.
211     * @param key The key containing the resource paths to add to the AssetManager.
212     * @return a new AssetManager.
213    */
214    @VisibleForTesting
215    protected AssetManager createAssetManager(@NonNull final ResourcesKey key) {
216        AssetManager assets = new AssetManager();
217
218        // resDir can be null if the 'android' package is creating a new Resources object.
219        // This is fine, since each AssetManager automatically loads the 'android' package
220        // already.
221        if (key.mResDir != null) {
222            if (assets.addAssetPath(key.mResDir) == 0) {
223                return null;
224            }
225        }
226
227        if (key.mSplitResDirs != null) {
228            for (final String splitResDir : key.mSplitResDirs) {
229                if (assets.addAssetPath(splitResDir) == 0) {
230                    return null;
231                }
232            }
233        }
234
235        if (key.mOverlayDirs != null) {
236            for (final String idmapPath : key.mOverlayDirs) {
237                assets.addOverlayPath(idmapPath);
238            }
239        }
240
241        if (key.mLibDirs != null) {
242            for (final String libDir : key.mLibDirs) {
243                if (libDir.endsWith(".apk")) {
244                    // Avoid opening files we know do not have resources,
245                    // like code-only .jar files.
246                    if (assets.addAssetPath(libDir) == 0) {
247                        Log.w(TAG, "Asset path '" + libDir +
248                                "' does not exist or contains no resources.");
249                    }
250                }
251            }
252        }
253        return assets;
254    }
255
256    private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
257        Configuration config;
258        final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
259        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
260        if (!isDefaultDisplay || hasOverrideConfig) {
261            config = new Configuration(getConfiguration());
262            if (!isDefaultDisplay) {
263                applyNonDefaultDisplayMetricsToConfiguration(dm, config);
264            }
265            if (hasOverrideConfig) {
266                config.updateFrom(key.mOverrideConfiguration);
267                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
268            }
269        } else {
270            config = getConfiguration();
271        }
272        return config;
273    }
274
275
276    private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
277        AssetManager assets = createAssetManager(key);
278        DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
279        Configuration config = generateConfig(key, dm);
280        ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
281        if (DEBUG) {
282            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
283        }
284        return impl;
285    }
286
287    /**
288     * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
289     *
290     * @param key The key to match.
291     * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
292     */
293    private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
294        WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
295        ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
296        if (impl != null && impl.getAssets().isUpToDate()) {
297            return impl;
298        }
299        return null;
300    }
301
302    /**
303     * Find the ResourcesKey that this ResourcesImpl object is associated with.
304     * @return the ResourcesKey or null if none was found.
305     */
306    private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) {
307        final int refCount = mResourceImpls.size();
308        for (int i = 0; i < refCount; i++) {
309            WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
310            ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
311            if (impl != null && resourceImpl == impl) {
312                return mResourceImpls.keyAt(i);
313            }
314        }
315        return null;
316    }
317
318    /**
319     * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
320     * or the class loader is different.
321     */
322    private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
323            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
324        // This is a request tied to an Activity, meaning we will need to update all
325        // Activity related Resources to match this configuration.
326        WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken);
327        Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
328        if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) {
329            resources = new Resources(classLoader);
330            mActivityResourceReferences.put(activityToken, new WeakReference<>(resources));
331            if (DEBUG) {
332                Slog.d(TAG, "- creating new ref=" + resources);
333            }
334        } else {
335            if (DEBUG) {
336                Slog.d(TAG, "- using existing ref=" + resources);
337            }
338        }
339
340        if (resources.getImpl() != impl) {
341            if (DEBUG) {
342                Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
343            }
344
345            // Setting an impl is expensive because we update all ThemeImpl references.
346            // too.
347            resources.setImpl(impl);
348        }
349        return resources;
350    }
351
352    /**
353     * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
354     * otherwise creates a new Resources object.
355     */
356    private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
357            @NonNull ResourcesImpl impl) {
358        // Find an existing Resources that has this ResourcesImpl set.
359        final int refCount = mResourceReferences.size();
360        for (int i = 0; i < refCount; i++) {
361            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
362            Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
363            if (resources != null &&
364                    Objects.equals(resources.getClassLoader(), classLoader) &&
365                    resources.getImpl() == impl) {
366                if (DEBUG) {
367                    Slog.d(TAG, "- using existing ref=" + resources);
368                }
369                return resources;
370            }
371        }
372
373        // Create a new Resources reference and use the existing ResourcesImpl object.
374        Resources resources = new Resources(classLoader);
375        resources.setImpl(impl);
376        mResourceReferences.add(new WeakReference<>(resources));
377        if (DEBUG) {
378            Slog.d(TAG, "- creating new ref=" + resources);
379            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
380        }
381        return resources;
382    }
383
384    /**
385     * Gets or creates a new Resources object associated with the IBinder token. References returned
386     * by this method live as long as the Activity, meaning they can be cached and used by the
387     * Activity even after a configuration change. If any other parameter is changed
388     * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
389     * is updated and handed back to the caller. However, changing the class loader will result in a
390     * new Resources object.
391     * <p/>
392     * If activityToken is null, a cached Resources object will be returned if it matches the
393     * input parameters. Otherwise a new Resources object that satisfies these parameters is
394     * returned.
395     *
396     * @param activityToken Represents an Activity. If null, global resources are assumed.
397     * @param resDir The base resource path. Can be null (only framework resources will be loaded).
398     * @param splitResDirs An array of split resource paths. Can be null.
399     * @param overlayDirs An array of overlay paths. Can be null.
400     * @param libDirs An array of resource library paths. Can be null.
401     * @param displayId The ID of the display for which to create the resources.
402     * @param overrideConfig The configuration to apply on top of the base configuration. Can be
403     * null. Mostly used with Activities that are in multi-window which may override width and
404     * height properties from the base config.
405     * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
406     * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
407     * @param classLoader The class loader to use when inflating Resources. If null, the
408     * {@link ClassLoader#getSystemClassLoader()} is used.
409     * @return a Resources object from which to access resources.
410     */
411    public Resources getResources(@Nullable IBinder activityToken,
412            @Nullable String resDir,
413            @Nullable String[] splitResDirs,
414            @Nullable String[] overlayDirs,
415            @Nullable String[] libDirs,
416            int displayId,
417            @Nullable Configuration overrideConfig,
418            @NonNull CompatibilityInfo compatInfo,
419            @Nullable ClassLoader classLoader) {
420        final ResourcesKey key = new ResourcesKey(
421                resDir,
422                splitResDirs,
423                overlayDirs,
424                libDirs,
425                displayId,
426                overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
427                compatInfo);
428
429        classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
430
431        final boolean findSystemLocales;
432        final boolean hasNonSystemLocales;
433        synchronized (this) {
434            findSystemLocales = (mSystemLocales.length == 0);
435            hasNonSystemLocales = mHasNonSystemLocales;
436
437            if (DEBUG) {
438                Throwable here = new Throwable();
439                here.fillInStackTrace();
440                Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
441            }
442
443            if (activityToken != null) {
444                ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
445                if (resourcesImpl != null) {
446                    if (DEBUG) {
447                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
448                    }
449                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
450                            resourcesImpl);
451                }
452
453                // We will create the ResourcesImpl object outside of holding this lock.
454
455            } else {
456                // Clean up any dead references so they don't pile up.
457                ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
458
459                // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
460                ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
461                if (resourcesImpl != null) {
462                    if (DEBUG) {
463                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
464                    }
465                    return getOrCreateResourcesLocked(classLoader, resourcesImpl);
466                }
467
468                // We will create the ResourcesImpl object outside of holding this lock.
469            }
470        }
471
472        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
473        ResourcesImpl resourcesImpl = createResourcesImpl(key);
474
475        final String[] systemLocales = findSystemLocales
476                ? AssetManager.getSystem().getLocales() : null;
477        final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
478        // Avoid checking for non-pseudo-locales if we already know there were some from a previous
479        // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
480        // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
481        // able to affect mHasNonSystemLocales.
482        final boolean isPseudoLocalesOnly = hasNonSystemLocales ||
483                LocaleList.isPseudoLocalesOnly(nonSystemLocales);
484
485        synchronized (this) {
486            if (mSystemLocales.length == 0) {
487                mSystemLocales = systemLocales;
488            }
489            mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
490            mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
491
492            ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
493            if (existingResourcesImpl != null) {
494                if (DEBUG) {
495                    Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
496                            + " new impl=" + resourcesImpl);
497                }
498                resourcesImpl.getAssets().close();
499                resourcesImpl = existingResourcesImpl;
500            } else {
501                // Add this ResourcesImpl to the cache.
502                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
503            }
504
505            final Resources resources;
506            if (activityToken != null) {
507                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
508                        resourcesImpl);
509            } else {
510                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
511            }
512            return resources;
513        }
514    }
515
516    /**
517     * Updates an Activity's Resources object with overrideConfig. The Resources object
518     * that was previously returned by
519     * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
520     * CompatibilityInfo, ClassLoader)} is
521     * still valid and will have the updated configuration.
522     * @param activityToken The Activity token.
523     * @param overrideConfig The configuration override to update.
524     */
525    public void updateResourcesForActivity(@NonNull IBinder activityToken,
526            @Nullable Configuration overrideConfig) {
527        final ClassLoader classLoader;
528        final ResourcesKey oldKey;
529        synchronized (this) {
530            // Extract the ResourcesKey that was last used to create the Resources for this
531            // activity.
532            WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken);
533            final Resources resources = weakResRef != null ? weakResRef.get() : null;
534            if (resources == null) {
535                Slog.e(TAG, "can't update resources for uncached activity " + activityToken);
536                return;
537            }
538
539            classLoader = resources.getClassLoader();
540            oldKey = findKeyForResourceImpl(resources.getImpl());
541            if (oldKey == null) {
542                Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
543                return;
544            }
545        }
546
547        // Update the Resources object with the new override config and all of the existing
548        // settings.
549        getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs,
550                oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo,
551                classLoader);
552    }
553
554    /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
555        final int bestLocale;
556        if (mHasNonSystemLocales) {
557            bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
558        } else {
559            // We fallback to system locales if there was no locale specifically supported by the
560            // assets. This is to properly support apps that only rely on the shared system assets
561            // and don't need assets of their own.
562            bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales);
563        }
564        // set it for Java, this also affects newly created Resources
565        LocaleList.setDefault(locales, bestLocale);
566    }
567
568    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
569                                                             @Nullable CompatibilityInfo compat) {
570        if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
571            if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
572                    + mResConfiguration.seq + ", newSeq=" + config.seq);
573            return false;
574        }
575        int changes = mResConfiguration.updateFrom(config);
576        // Things might have changed in display manager, so clear the cached displays.
577        mDisplays.clear();
578        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
579
580        if (compat != null && (mResCompatibilityInfo == null ||
581                !mResCompatibilityInfo.equals(compat))) {
582            mResCompatibilityInfo = compat;
583            changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
584                    | ActivityInfo.CONFIG_SCREEN_SIZE
585                    | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
586        }
587
588        Configuration localeAdjustedConfig = config;
589        final LocaleList configLocales = config.getLocales();
590        if (!configLocales.isEmpty()) {
591            setDefaultLocalesLocked(configLocales);
592            final LocaleList adjustedLocales = LocaleList.getAdjustedDefault();
593            if (adjustedLocales != configLocales) { // has the same result as .equals() in this case
594                // The first locale in the list was not chosen. So we create a modified
595                // configuration with the adjusted locales (which moves the chosen locale to the
596                // front).
597                localeAdjustedConfig = new Configuration();
598                localeAdjustedConfig.setTo(config);
599                localeAdjustedConfig.setLocales(adjustedLocales);
600                // Also adjust the locale list in mResConfiguration, so that the Resources created
601                // later would have the same locale list.
602                if (!mResConfiguration.getLocales().equals(adjustedLocales)) {
603                    mResConfiguration.setLocales(adjustedLocales);
604                    changes |= ActivityInfo.CONFIG_LOCALE;
605                }
606            }
607        }
608
609        Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat);
610
611        ApplicationPackageManager.configurationChanged();
612        //Slog.i(TAG, "Configuration changed in " + currentPackageName());
613
614        Configuration tmpConfig = null;
615
616        for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
617            ResourcesKey key = mResourceImpls.keyAt(i);
618            ResourcesImpl r = mResourceImpls.valueAt(i).get();
619            if (r != null) {
620                if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
621                        + r + " config to: " + localeAdjustedConfig);
622                int displayId = key.mDisplayId;
623                boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
624                DisplayMetrics dm = defaultDisplayMetrics;
625                final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
626                if (!isDefaultDisplay || hasOverrideConfiguration) {
627                    if (tmpConfig == null) {
628                        tmpConfig = new Configuration();
629                    }
630                    tmpConfig.setTo(localeAdjustedConfig);
631                    if (!isDefaultDisplay) {
632                        dm = getDisplayMetricsLocked(displayId);
633                        applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
634                    }
635                    if (hasOverrideConfiguration) {
636                        tmpConfig.updateFrom(key.mOverrideConfiguration);
637                    }
638                    r.updateConfiguration(tmpConfig, dm, compat);
639                } else {
640                    r.updateConfiguration(localeAdjustedConfig, dm, compat);
641                }
642                //Slog.i(TAG, "Updated app resources " + v.getKey()
643                //        + " " + r + ": " + r.getConfiguration());
644            } else {
645                //Slog.i(TAG, "Removing old resources " + v.getKey());
646                mResourceImpls.removeAt(i);
647            }
648        }
649
650        return changes != 0;
651    }
652}