ResourcesManager.java revision 1dd50c54b5ca05531676e4542971b91ed023a792
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.ResourcesKey;
27import android.hardware.display.DisplayManagerGlobal;
28import android.util.ArrayMap;
29import android.util.DisplayMetrics;
30import android.util.Log;
31import android.util.Pair;
32import android.util.Slog;
33import android.view.Display;
34import java.lang.ref.WeakReference;
35import java.util.Locale;
36
37/** @hide */
38public class ResourcesManager {
39    static final String TAG = "ResourcesManager";
40    private static final boolean DEBUG = false;
41
42    private static ResourcesManager sResourcesManager;
43    private final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources =
44            new ArrayMap<>();
45    private final ArrayMap<Pair<Integer, Configuration>, WeakReference<Display>> mDisplays =
46            new ArrayMap<>();
47
48    CompatibilityInfo mResCompatibilityInfo;
49
50    Configuration mResConfiguration;
51
52    public static ResourcesManager getInstance() {
53        synchronized (ResourcesManager.class) {
54            if (sResourcesManager == null) {
55                sResourcesManager = new ResourcesManager();
56            }
57            return sResourcesManager;
58        }
59    }
60
61    public Configuration getConfiguration() {
62        return mResConfiguration;
63    }
64
65    DisplayMetrics getDisplayMetricsLocked() {
66        return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
67    }
68
69    DisplayMetrics getDisplayMetricsLocked(int displayId) {
70        DisplayMetrics dm = new DisplayMetrics();
71        final Display display = getAdjustedDisplay(displayId, Configuration.EMPTY);
72        if (display != null) {
73            display.getMetrics(dm);
74        } else {
75            dm.setToDefaults();
76        }
77        return dm;
78    }
79
80    final void applyNonDefaultDisplayMetricsToConfigurationLocked(
81            DisplayMetrics dm, Configuration config) {
82        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
83        config.densityDpi = dm.densityDpi;
84        config.screenWidthDp = (int)(dm.widthPixels / dm.density);
85        config.screenHeightDp = (int)(dm.heightPixels / dm.density);
86        int sl = Configuration.resetScreenLayout(config.screenLayout);
87        if (dm.widthPixels > dm.heightPixels) {
88            config.orientation = Configuration.ORIENTATION_LANDSCAPE;
89            config.screenLayout = Configuration.reduceScreenLayout(sl,
90                    config.screenWidthDp, config.screenHeightDp);
91        } else {
92            config.orientation = Configuration.ORIENTATION_PORTRAIT;
93            config.screenLayout = Configuration.reduceScreenLayout(sl,
94                    config.screenHeightDp, config.screenWidthDp);
95        }
96        config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
97        config.compatScreenWidthDp = config.screenWidthDp;
98        config.compatScreenHeightDp = config.screenHeightDp;
99        config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
100    }
101
102    public boolean applyCompatConfiguration(int displayDensity,
103            Configuration compatConfiguration) {
104        if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
105            mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
106            return true;
107        }
108        return false;
109    }
110
111    /**
112     * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
113     * available.
114     *
115     * @param displayId display Id.
116     * @param overrideConfiguration override configurations.
117     */
118    public Display getAdjustedDisplay(final int displayId, Configuration overrideConfiguration) {
119        final Configuration configCopy = (overrideConfiguration != null)
120                ? new Configuration(overrideConfiguration) : new Configuration();
121        final Pair<Integer, Configuration> key = Pair.create(displayId, configCopy);
122        synchronized (this) {
123            WeakReference<Display> wd = mDisplays.get(key);
124            if (wd != null) {
125                final Display display = wd.get();
126                if (display != null) {
127                    return display;
128                }
129            }
130            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
131            if (dm == null) {
132                // may be null early in system startup
133                return null;
134            }
135            final Display display = dm.getRealDisplay(displayId, key.second);
136            if (display != null) {
137                mDisplays.put(key, new WeakReference<>(display));
138            }
139            return display;
140        }
141    }
142
143    /**
144     * Creates the top level Resources for applications with the given compatibility info.
145     *
146     * @param resDir the resource directory.
147     * @param splitResDirs split resource directories.
148     * @param overlayDirs the resource overlay directories.
149     * @param libDirs the shared library resource dirs this app references.
150     * @param displayId display Id.
151     * @param overrideConfiguration override configurations.
152     * @param compatInfo the compatibility info. Must not be null.
153     */
154    Resources getTopLevelResources(String resDir, String[] splitResDirs,
155            String[] overlayDirs, String[] libDirs, int displayId,
156            Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
157        final float scale = compatInfo.applicationScale;
158        Configuration overrideConfigCopy = (overrideConfiguration != null)
159                ? new Configuration(overrideConfiguration) : null;
160        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
161        Resources r;
162        synchronized (this) {
163            // Resources is app scale dependent.
164            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
165
166            WeakReference<Resources> wr = mActiveResources.get(key);
167            r = wr != null ? wr.get() : null;
168            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
169            if (r != null && r.getAssets().isUpToDate()) {
170                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
171                        + ": appScale=" + r.getCompatibilityInfo().applicationScale
172                        + " key=" + key + " overrideConfig=" + overrideConfiguration);
173                return r;
174            }
175        }
176
177        //if (r != null) {
178        //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
179        //            + r + " " + resDir);
180        //}
181
182        AssetManager assets = new AssetManager();
183        // resDir can be null if the 'android' package is creating a new Resources object.
184        // This is fine, since each AssetManager automatically loads the 'android' package
185        // already.
186        if (resDir != null) {
187            if (assets.addAssetPath(resDir) == 0) {
188                return null;
189            }
190        }
191
192        if (splitResDirs != null) {
193            for (String splitResDir : splitResDirs) {
194                if (assets.addAssetPath(splitResDir) == 0) {
195                    return null;
196                }
197            }
198        }
199
200        if (overlayDirs != null) {
201            for (String idmapPath : overlayDirs) {
202                assets.addOverlayPath(idmapPath);
203            }
204        }
205
206        if (libDirs != null) {
207            for (String libDir : libDirs) {
208                if (libDir.endsWith(".apk")) {
209                    // Avoid opening files we know do not have resources,
210                    // like code-only .jar files.
211                    if (assets.addAssetPath(libDir) == 0) {
212                        Log.w(TAG, "Asset path '" + libDir +
213                                "' does not exist or contains no resources.");
214                    }
215                }
216            }
217        }
218
219        //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
220        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
221        Configuration config;
222        final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
223        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
224        if (!isDefaultDisplay || hasOverrideConfig) {
225            config = new Configuration(getConfiguration());
226            if (!isDefaultDisplay) {
227                applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
228            }
229            if (hasOverrideConfig) {
230                config.updateFrom(key.mOverrideConfiguration);
231                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
232            }
233        } else {
234            config = getConfiguration();
235        }
236        r = new Resources(assets, dm, config, compatInfo);
237        if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
238                + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
239
240        synchronized (this) {
241            WeakReference<Resources> wr = mActiveResources.get(key);
242            Resources existing = wr != null ? wr.get() : null;
243            if (existing != null && existing.getAssets().isUpToDate()) {
244                // Someone else already created the resources while we were
245                // unlocked; go ahead and use theirs.
246                r.getAssets().close();
247                return existing;
248            }
249
250            // XXX need to remove entries when weak references go away
251            mActiveResources.put(key, new WeakReference<>(r));
252            if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
253            return r;
254        }
255    }
256
257    final boolean applyConfigurationToResourcesLocked(Configuration config,
258            CompatibilityInfo compat) {
259        if (mResConfiguration == null) {
260            mResConfiguration = new Configuration();
261        }
262        if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
263            if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
264                    + mResConfiguration.seq + ", newSeq=" + config.seq);
265            return false;
266        }
267        int changes = mResConfiguration.updateFrom(config);
268        // Things might have changed in display manager, so clear the cached displays.
269        mDisplays.clear();
270        DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
271
272        if (compat != null && (mResCompatibilityInfo == null ||
273                !mResCompatibilityInfo.equals(compat))) {
274            mResCompatibilityInfo = compat;
275            changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
276                    | ActivityInfo.CONFIG_SCREEN_SIZE
277                    | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
278        }
279
280        // set it for java, this also affects newly created Resources
281        if (config.locale != null) {
282            Locale.setDefault(config.locale);
283        }
284
285        Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
286
287        ApplicationPackageManager.configurationChanged();
288        //Slog.i(TAG, "Configuration changed in " + currentPackageName());
289
290        Configuration tmpConfig = null;
291
292        for (int i = mActiveResources.size() - 1; i >= 0; i--) {
293            ResourcesKey key = mActiveResources.keyAt(i);
294            Resources r = mActiveResources.valueAt(i).get();
295            if (r != null) {
296                if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
297                        + r + " config to: " + config);
298                int displayId = key.mDisplayId;
299                boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
300                DisplayMetrics dm = defaultDisplayMetrics;
301                final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
302                if (!isDefaultDisplay || hasOverrideConfiguration) {
303                    if (tmpConfig == null) {
304                        tmpConfig = new Configuration();
305                    }
306                    tmpConfig.setTo(config);
307                    if (!isDefaultDisplay) {
308                        dm = getDisplayMetricsLocked(displayId);
309                        applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
310                    }
311                    if (hasOverrideConfiguration) {
312                        tmpConfig.updateFrom(key.mOverrideConfiguration);
313                    }
314                    r.updateConfiguration(tmpConfig, dm, compat);
315                } else {
316                    r.updateConfiguration(config, dm, compat);
317                }
318                //Slog.i(TAG, "Updated app resources " + v.getKey()
319                //        + " " + r + ": " + r.getConfiguration());
320            } else {
321                //Slog.i(TAG, "Removing old resources " + v.getKey());
322                mActiveResources.removeAt(i);
323            }
324        }
325
326        return changes != 0;
327    }
328
329}
330