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