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 
17 package android.app;
18 
19 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
20 
21 import android.content.pm.ActivityInfo;
22 import android.content.res.AssetManager;
23 import android.content.res.CompatibilityInfo;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.content.res.ResourcesKey;
27 import android.hardware.display.DisplayManagerGlobal;
28 import android.util.ArrayMap;
29 import android.util.DisplayMetrics;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.util.Slog;
33 import android.view.Display;
34 import android.view.DisplayAdjustments;
35 
36 import java.lang.ref.WeakReference;
37 import java.util.Locale;
38 
39 /** @hide */
40 public 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 
getInstance()54     public static ResourcesManager getInstance() {
55         synchronized (ResourcesManager.class) {
56             if (sResourcesManager == null) {
57                 sResourcesManager = new ResourcesManager();
58             }
59             return sResourcesManager;
60         }
61     }
62 
getConfiguration()63     public Configuration getConfiguration() {
64         return mResConfiguration;
65     }
66 
getDisplayMetricsLocked()67     DisplayMetrics getDisplayMetricsLocked() {
68         return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
69     }
70 
getDisplayMetricsLocked(int displayId)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 
applyNonDefaultDisplayMetricsToConfigurationLocked( DisplayMetrics dm, Configuration config)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 
applyCompatConfiguration(int displayDensity, Configuration compatConfiguration)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      */
getAdjustedDisplay(final int displayId, DisplayAdjustments displayAdjustments)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      */
getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo)158     Resources getTopLevelResources(String resDir, String[] splitResDirs,
159             String[] overlayDirs, String[] libDirs, int displayId,
160             Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
161         final float scale = compatInfo.applicationScale;
162         Configuration overrideConfigCopy = (overrideConfiguration != null)
163                 ? new Configuration(overrideConfiguration) : null;
164         ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
165         Resources r;
166         synchronized (this) {
167             // Resources is app scale dependent.
168             if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
169 
170             WeakReference<Resources> wr = mActiveResources.get(key);
171             r = wr != null ? wr.get() : null;
172             //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
173             if (r != null && r.getAssets().isUpToDate()) {
174                 if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
175                         + ": appScale=" + r.getCompatibilityInfo().applicationScale
176                         + " key=" + key + " overrideConfig=" + overrideConfiguration);
177                 return r;
178             }
179         }
180 
181         //if (r != null) {
182         //    Log.w(TAG, "Throwing away out-of-date resources!!!! "
183         //            + r + " " + resDir);
184         //}
185 
186         AssetManager assets = new AssetManager();
187         // resDir can be null if the 'android' package is creating a new Resources object.
188         // This is fine, since each AssetManager automatically loads the 'android' package
189         // already.
190         if (resDir != null) {
191             if (assets.addAssetPath(resDir) == 0) {
192                 return null;
193             }
194         }
195 
196         if (splitResDirs != null) {
197             for (String splitResDir : splitResDirs) {
198                 if (assets.addAssetPath(splitResDir) == 0) {
199                     return null;
200                 }
201             }
202         }
203 
204         if (overlayDirs != null) {
205             for (String idmapPath : overlayDirs) {
206                 assets.addOverlayPath(idmapPath);
207             }
208         }
209 
210         if (libDirs != null) {
211             for (String libDir : libDirs) {
212                 if (libDir.endsWith(".apk")) {
213                     // Avoid opening files we know do not have resources,
214                     // like code-only .jar files.
215                     if (assets.addAssetPath(libDir) == 0) {
216                         Log.w(TAG, "Asset path '" + libDir +
217                                 "' does not exist or contains no resources.");
218                     }
219                 }
220             }
221         }
222 
223         //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
224         DisplayMetrics dm = getDisplayMetricsLocked(displayId);
225         Configuration config;
226         final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
227         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
228         if (!isDefaultDisplay || hasOverrideConfig) {
229             config = new Configuration(getConfiguration());
230             if (!isDefaultDisplay) {
231                 applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
232             }
233             if (hasOverrideConfig) {
234                 config.updateFrom(key.mOverrideConfiguration);
235                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
236             }
237         } else {
238             config = getConfiguration();
239         }
240         r = new Resources(assets, dm, config, compatInfo);
241         if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
242                 + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
243 
244         synchronized (this) {
245             WeakReference<Resources> wr = mActiveResources.get(key);
246             Resources existing = wr != null ? wr.get() : null;
247             if (existing != null && existing.getAssets().isUpToDate()) {
248                 // Someone else already created the resources while we were
249                 // unlocked; go ahead and use theirs.
250                 r.getAssets().close();
251                 return existing;
252             }
253 
254             // XXX need to remove entries when weak references go away
255             mActiveResources.put(key, new WeakReference<>(r));
256             if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
257             return r;
258         }
259     }
260 
applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat)261     final boolean applyConfigurationToResourcesLocked(Configuration config,
262             CompatibilityInfo compat) {
263         if (mResConfiguration == null) {
264             mResConfiguration = new Configuration();
265         }
266         if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
267             if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
268                     + mResConfiguration.seq + ", newSeq=" + config.seq);
269             return false;
270         }
271         int changes = mResConfiguration.updateFrom(config);
272         // Things might have changed in display manager, so clear the cached displays.
273         mDisplays.clear();
274         DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
275 
276         if (compat != null && (mResCompatibilityInfo == null ||
277                 !mResCompatibilityInfo.equals(compat))) {
278             mResCompatibilityInfo = compat;
279             changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
280                     | ActivityInfo.CONFIG_SCREEN_SIZE
281                     | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
282         }
283 
284         // set it for java, this also affects newly created Resources
285         if (config.locale != null) {
286             Locale.setDefault(config.locale);
287         }
288 
289         Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
290 
291         ApplicationPackageManager.configurationChanged();
292         //Slog.i(TAG, "Configuration changed in " + currentPackageName());
293 
294         Configuration tmpConfig = null;
295 
296         for (int i = mActiveResources.size() - 1; i >= 0; i--) {
297             ResourcesKey key = mActiveResources.keyAt(i);
298             Resources r = mActiveResources.valueAt(i).get();
299             if (r != null) {
300                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
301                         + r + " config to: " + config);
302                 int displayId = key.mDisplayId;
303                 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
304                 DisplayMetrics dm = defaultDisplayMetrics;
305                 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
306                 if (!isDefaultDisplay || hasOverrideConfiguration) {
307                     if (tmpConfig == null) {
308                         tmpConfig = new Configuration();
309                     }
310                     tmpConfig.setTo(config);
311                     if (!isDefaultDisplay) {
312                         dm = getDisplayMetricsLocked(displayId);
313                         applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig);
314                     }
315                     if (hasOverrideConfiguration) {
316                         tmpConfig.updateFrom(key.mOverrideConfiguration);
317                     }
318                     r.updateConfiguration(tmpConfig, dm, compat);
319                 } else {
320                     r.updateConfiguration(config, dm, compat);
321                 }
322                 //Slog.i(TAG, "Updated app resources " + v.getKey()
323                 //        + " " + r + ": " + r.getConfiguration());
324             } else {
325                 //Slog.i(TAG, "Removing old resources " + v.getKey());
326                 mActiveResources.removeAt(i);
327             }
328         }
329 
330         return changes != 0;
331     }
332 
333 }
334