ResourcesManager.java revision fb302ccd8e0610a09691ea5503ff8111dc7a2e41
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.content.pm.ActivityInfo;
22import android.content.res.AssetManager;
23import android.content.res.CompatibilityInfo;
24import android.content.res.Configuration;
25import android.content.res.Resources;
26import android.content.res.ResourcesImpl;
27import android.content.res.ResourcesKey;
28import android.hardware.display.DisplayManagerGlobal;
29import android.util.ArrayMap;
30import android.util.DisplayMetrics;
31import android.util.LocaleList;
32import android.util.Log;
33import android.util.Pair;
34import android.util.Slog;
35import android.view.Display;
36import android.view.DisplayAdjustments;
37
38import java.lang.ref.WeakReference;
39import java.util.Arrays;
40import java.util.HashSet;
41import java.util.List;
42
43/** @hide */
44public class ResourcesManager {
45    static final String TAG = "ResourcesManager";
46    private static final boolean DEBUG = false;
47
48    private static ResourcesManager sResourcesManager;
49    private final ArrayMap<ResourcesKey, WeakReference<Resources>> mActiveResources =
50            new ArrayMap<>();
51    private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
52            new ArrayMap<>();
53
54    private String[] mSystemLocales = {};
55    private final HashSet<String> mNonSystemLocales = new HashSet<String>();
56    private boolean mHasNonSystemLocales = false;
57
58    CompatibilityInfo mResCompatibilityInfo;
59
60    Configuration mResConfiguration;
61
62    public static ResourcesManager getInstance() {
63        synchronized (ResourcesManager.class) {
64            if (sResourcesManager == null) {
65                sResourcesManager = new ResourcesManager();
66            }
67            return sResourcesManager;
68        }
69    }
70
71    public Configuration getConfiguration() {
72        return mResConfiguration;
73    }
74
75    DisplayMetrics getDisplayMetricsLocked() {
76        return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
77    }
78
79    DisplayMetrics getDisplayMetricsLocked(int displayId) {
80        DisplayMetrics dm = new DisplayMetrics();
81        final Display display =
82                getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
83        if (display != null) {
84            display.getMetrics(dm);
85        } else {
86            dm.setToDefaults();
87        }
88        return dm;
89    }
90
91    final void applyNonDefaultDisplayMetricsToConfigurationLocked(
92            DisplayMetrics dm, Configuration config) {
93        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
94        config.densityDpi = dm.densityDpi;
95        config.screenWidthDp = (int)(dm.widthPixels / dm.density);
96        config.screenHeightDp = (int)(dm.heightPixels / dm.density);
97        int sl = Configuration.resetScreenLayout(config.screenLayout);
98        if (dm.widthPixels > dm.heightPixels) {
99            config.orientation = Configuration.ORIENTATION_LANDSCAPE;
100            config.screenLayout = Configuration.reduceScreenLayout(sl,
101                    config.screenWidthDp, config.screenHeightDp);
102        } else {
103            config.orientation = Configuration.ORIENTATION_PORTRAIT;
104            config.screenLayout = Configuration.reduceScreenLayout(sl,
105                    config.screenHeightDp, config.screenWidthDp);
106        }
107        config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
108        config.compatScreenWidthDp = config.screenWidthDp;
109        config.compatScreenHeightDp = config.screenHeightDp;
110        config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
111    }
112
113    public boolean applyCompatConfiguration(int displayDensity,
114            Configuration compatConfiguration) {
115        if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
116            mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
117            return true;
118        }
119        return false;
120    }
121
122    /**
123     * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
124     * available.
125     *
126     * @param displayId display Id.
127     * @param displayAdjustments display adjustments.
128     */
129    public Display getAdjustedDisplay(final int displayId, DisplayAdjustments displayAdjustments) {
130        final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
131                ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
132        final Pair<Integer, DisplayAdjustments> key =
133                Pair.create(displayId, displayAdjustmentsCopy);
134        synchronized (this) {
135            WeakReference<Display> wd = mDisplays.get(key);
136            if (wd != null) {
137                final Display display = wd.get();
138                if (display != null) {
139                    return display;
140                }
141            }
142            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
143            if (dm == null) {
144                // may be null early in system startup
145                return null;
146            }
147            final Display display = dm.getCompatibleDisplay(displayId, key.second);
148            if (display != null) {
149                mDisplays.put(key, new WeakReference<>(display));
150            }
151            return display;
152        }
153    }
154
155    /**
156     * Creates the top level Resources for applications with the given compatibility info.
157     *
158     * @param resDir the resource directory.
159     * @param splitResDirs split resource directories.
160     * @param overlayDirs the resource overlay directories.
161     * @param libDirs the shared library resource dirs this app references.
162     * @param displayId display Id.
163     * @param overrideConfiguration override configurations.
164     * @param compatInfo the compatibility info. Must not be null.
165     * @param classLoader the class loader for the resource package
166     */
167    Resources getTopLevelResources(String resDir, String[] splitResDirs,
168            String[] overlayDirs, String[] libDirs, int displayId,
169            Configuration overrideConfiguration, CompatibilityInfo compatInfo,
170            ClassLoader classLoader) {
171        final float scale = compatInfo.applicationScale;
172        Configuration overrideConfigCopy = (overrideConfiguration != null)
173                ? new Configuration(overrideConfiguration) : null;
174        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
175        Resources r;
176        final boolean findSystemLocales;
177        final boolean hasNonSystemLocales;
178        synchronized (this) {
179            // Resources is app scale dependent.
180            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
181
182            WeakReference<Resources> wr = mActiveResources.get(key);
183            r = wr != null ? wr.get() : null;
184            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
185            if (r != null && r.getAssets().isUpToDate()) {
186                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
187                        + ": appScale=" + r.getCompatibilityInfo().applicationScale
188                        + " key=" + key + " overrideConfig=" + overrideConfiguration);
189                return r;
190            }
191            findSystemLocales = (mSystemLocales.length == 0);
192            hasNonSystemLocales = mHasNonSystemLocales;
193        }
194
195        //if (r != null) {
196        //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
197        //            + r + " " + resDir);
198        //}
199
200        AssetManager assets = new AssetManager();
201        // resDir can be null if the 'android' package is creating a new Resources object.
202        // This is fine, since each AssetManager automatically loads the 'android' package
203        // already.
204        if (resDir != null) {
205            if (assets.addAssetPath(resDir) == 0) {
206                return null;
207            }
208        }
209
210        if (splitResDirs != null) {
211            for (String splitResDir : splitResDirs) {
212                if (assets.addAssetPath(splitResDir) == 0) {
213                    return null;
214                }
215            }
216        }
217
218        if (overlayDirs != null) {
219            for (String idmapPath : overlayDirs) {
220                assets.addOverlayPath(idmapPath);
221            }
222        }
223
224        if (libDirs != null) {
225            for (String libDir : libDirs) {
226                if (libDir.endsWith(".apk")) {
227                    // Avoid opening files we know do not have resources,
228                    // like code-only .jar files.
229                    if (assets.addAssetPath(libDir) == 0) {
230                        Log.w(TAG, "Asset path '" + libDir +
231                                "' does not exist or contains no resources.");
232                    }
233                }
234            }
235        }
236
237        //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
238        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
239        Configuration config;
240        final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
241        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
242        if (!isDefaultDisplay || hasOverrideConfig) {
243            config = new Configuration(getConfiguration());
244            if (!isDefaultDisplay) {
245                applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
246            }
247            if (hasOverrideConfig) {
248                config.updateFrom(key.mOverrideConfiguration);
249                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
250            }
251        } else {
252            config = getConfiguration();
253        }
254        r = new Resources(classLoader);
255        r.setImpl(new ResourcesImpl(assets, dm, config, compatInfo));
256        if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
257                + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
258
259        final String[] systemLocales = (
260                findSystemLocales ?
261                AssetManager.getSystem().getLocales() :
262                null);
263        final String[] nonSystemLocales = assets.getNonSystemLocales();
264        // Avoid checking for non-pseudo-locales if we already know there were some from a previous
265        // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
266        // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
267        // able to affect mHasNonSystemLocales.
268        final boolean isPseudoLocalesOnly = hasNonSystemLocales ||
269                LocaleList.isPseudoLocalesOnly(nonSystemLocales);
270
271        synchronized (this) {
272            WeakReference<Resources> wr = mActiveResources.get(key);
273            Resources existing = wr != null ? wr.get() : null;
274            if (existing != null && existing.getAssets().isUpToDate()) {
275                // Someone else already created the resources while we were
276                // unlocked; go ahead and use theirs.
277                r.getAssets().close();
278                return existing;
279            }
280
281            // XXX need to remove entries when weak references go away
282            mActiveResources.put(key, new WeakReference<>(r));
283            if (mSystemLocales.length == 0) {
284                mSystemLocales = systemLocales;
285            }
286            mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
287            mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
288            if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
289            return r;
290        }
291    }
292
293    /* package */ void setDefaultLocalesLocked(LocaleList locales) {
294        final int bestLocale;
295        if (mHasNonSystemLocales) {
296            bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
297        } else {
298            // We fallback to system locales if there was no locale specifically supported by the
299            // assets. This is to properly support apps that only rely on the shared system assets
300            // and don't need assets of their own.
301            bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales);
302        }
303        // set it for Java, this also affects newly created Resources
304        LocaleList.setDefault(locales, bestLocale);
305    }
306
307    final boolean applyConfigurationToResourcesLocked(Configuration config,
308            CompatibilityInfo compat) {
309        if (mResConfiguration == null) {
310            mResConfiguration = new Configuration();
311        }
312        if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
313            if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
314                    + mResConfiguration.seq + ", newSeq=" + config.seq);
315            return false;
316        }
317        int changes = mResConfiguration.updateFrom(config);
318        // Things might have changed in display manager, so clear the cached displays.
319        mDisplays.clear();
320        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
321
322        if (compat != null && (mResCompatibilityInfo == null ||
323                !mResCompatibilityInfo.equals(compat))) {
324            mResCompatibilityInfo = compat;
325            changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
326                    | ActivityInfo.CONFIG_SCREEN_SIZE
327                    | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
328        }
329
330        Configuration localeAdjustedConfig = config;
331        final LocaleList configLocales = config.getLocales();
332        if (!configLocales.isEmpty()) {
333            setDefaultLocalesLocked(configLocales);
334            final LocaleList adjustedLocales = LocaleList.getAdjustedDefault();
335            if (adjustedLocales != configLocales) { // has the same result as .equals() in this case
336                // The first locale in the list was not chosen. So we create a modified
337                // configuration with the adjusted locales (which moves the chosen locale to the
338                // front).
339                localeAdjustedConfig = new Configuration();
340                localeAdjustedConfig.setTo(config);
341                localeAdjustedConfig.setLocales(adjustedLocales);
342                // Also adjust the locale list in mResConfiguration, so that the Resources created
343                // later would have the same locale list.
344                if (!mResConfiguration.getLocales().equals(adjustedLocales)) {
345                    mResConfiguration.setLocales(adjustedLocales);
346                    changes |= ActivityInfo.CONFIG_LOCALE;
347                }
348            }
349        }
350
351        Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat);
352
353        ApplicationPackageManager.configurationChanged();
354        //Slog.i(TAG, "Configuration changed in " + currentPackageName());
355
356        Configuration tmpConfig = null;
357
358        for (int i = mActiveResources.size() - 1; i >= 0; i--) {
359            ResourcesKey key = mActiveResources.keyAt(i);
360            Resources r = mActiveResources.valueAt(i).get();
361            if (r != null) {
362                if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
363                        + r + " config to: " + localeAdjustedConfig);
364                int displayId = key.mDisplayId;
365                boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
366                DisplayMetrics dm = defaultDisplayMetrics;
367                final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
368                if (!isDefaultDisplay || hasOverrideConfiguration) {
369                    if (tmpConfig == null) {
370                        tmpConfig = new Configuration();
371                    }
372                    tmpConfig.setTo(localeAdjustedConfig);
373                    if (!isDefaultDisplay) {
374                        dm = getDisplayMetricsLocked(displayId);
375                        applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
376                    }
377                    if (hasOverrideConfiguration) {
378                        tmpConfig.updateFrom(key.mOverrideConfiguration);
379                    }
380                    r.updateConfiguration(tmpConfig, dm, compat);
381                } else {
382                    r.updateConfiguration(localeAdjustedConfig, dm, compat);
383                }
384                //Slog.i(TAG, "Updated app resources " + v.getKey()
385                //        + " " + r + ": " + r.getConfiguration());
386            } else {
387                //Slog.i(TAG, "Removing old resources " + v.getKey());
388                mActiveResources.removeAt(i);
389            }
390        }
391
392        return changes != 0;
393    }
394
395}
396