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