blob: 54d813dee6d71c6062c9e642c09d5b65ede4443e [file] [log] [blame]
Craig Mautner88c05892013-06-28 09:47:45 -07001/*
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
Adam Lesinski082614c2016-03-04 14:33:47 -080021import android.annotation.NonNull;
22import android.annotation.Nullable;
Craig Mautner88c05892013-06-28 09:47:45 -070023import android.content.pm.ActivityInfo;
24import android.content.res.AssetManager;
25import android.content.res.CompatibilityInfo;
26import android.content.res.Configuration;
27import android.content.res.Resources;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080028import android.content.res.ResourcesImpl;
Craig Mautner88c05892013-06-28 09:47:45 -070029import android.content.res.ResourcesKey;
30import android.hardware.display.DisplayManagerGlobal;
Adam Lesinski082614c2016-03-04 14:33:47 -080031import android.os.IBinder;
Craig Mautner88c05892013-06-28 09:47:45 -070032import android.util.ArrayMap;
33import android.util.DisplayMetrics;
Seigo Nonakacada57a2016-01-22 17:22:11 +090034import android.util.LocaleList;
Adam Lesinski1dd50c52015-03-16 15:10:56 -070035import android.util.Log;
Wale Ogunwale7c726682015-02-06 17:34:28 -080036import android.util.Pair;
Craig Mautner88c05892013-06-28 09:47:45 -070037import android.util.Slog;
38import android.view.Display;
Wale Ogunwale26698512015-06-05 16:55:33 -070039import android.view.DisplayAdjustments;
Adam Lesinski082614c2016-03-04 14:33:47 -080040import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.util.ArrayUtils;
Wale Ogunwale26698512015-06-05 16:55:33 -070042
Craig Mautner88c05892013-06-28 09:47:45 -070043import java.lang.ref.WeakReference;
Adam Lesinski082614c2016-03-04 14:33:47 -080044import java.util.ArrayList;
Roozbeh Pournader834641b2016-01-23 22:34:57 -080045import java.util.Arrays;
46import java.util.HashSet;
Adam Lesinski082614c2016-03-04 14:33:47 -080047import java.util.Objects;
48import java.util.WeakHashMap;
49import java.util.function.Predicate;
Craig Mautner88c05892013-06-28 09:47:45 -070050
51/** @hide */
52public class ResourcesManager {
53 static final String TAG = "ResourcesManager";
Wale Ogunwale60454db2015-01-23 16:05:07 -080054 private static final boolean DEBUG = false;
Craig Mautner88c05892013-06-28 09:47:45 -070055
56 private static ResourcesManager sResourcesManager;
Adam Lesinski082614c2016-03-04 14:33:47 -080057
58 /**
59 * Predicate that returns true if a WeakReference is gc'ed.
60 */
61 private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
62 new Predicate<WeakReference<Resources>>() {
63 @Override
64 public boolean test(WeakReference<Resources> weakRef) {
65 return weakRef == null || weakRef.get() == null;
66 }
67 };
Craig Mautner88c05892013-06-28 09:47:45 -070068
Roozbeh Pournader834641b2016-01-23 22:34:57 -080069 private String[] mSystemLocales = {};
Adam Lesinski082614c2016-03-04 14:33:47 -080070 private final HashSet<String> mNonSystemLocales = new HashSet<>();
Roozbeh Pournader834641b2016-01-23 22:34:57 -080071 private boolean mHasNonSystemLocales = false;
72
Adam Lesinski082614c2016-03-04 14:33:47 -080073 /**
74 * The global compatibility settings.
75 */
76 private CompatibilityInfo mResCompatibilityInfo;
Craig Mautner88c05892013-06-28 09:47:45 -070077
Adam Lesinski082614c2016-03-04 14:33:47 -080078 /**
79 * The global configuration upon which all Resources are based. Multi-window Resources
80 * apply their overrides to this configuration.
81 */
82 private final Configuration mResConfiguration = new Configuration();
83
84 /**
85 * A mapping of ResourceImpls and their configurations. These are heavy weight objects
86 * which should be reused as much as possible.
87 */
88 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
89 new ArrayMap<>();
90
91 /**
92 * A list of Resource references that can be reused.
93 */
94 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
95
96 /**
97 * Each Activity may have only one Resources object.
98 */
99 private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
100 new WeakHashMap<>();
101
102 /**
103 * A cache of DisplayId to DisplayAdjustments.
104 */
105 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
106 new ArrayMap<>();
Craig Mautner88c05892013-06-28 09:47:45 -0700107
108 public static ResourcesManager getInstance() {
109 synchronized (ResourcesManager.class) {
110 if (sResourcesManager == null) {
111 sResourcesManager = new ResourcesManager();
112 }
113 return sResourcesManager;
114 }
115 }
116
117 public Configuration getConfiguration() {
118 return mResConfiguration;
119 }
120
Wale Ogunwale7c726682015-02-06 17:34:28 -0800121 DisplayMetrics getDisplayMetricsLocked() {
122 return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
Craig Mautner88c05892013-06-28 09:47:45 -0700123 }
124
Adam Lesinski082614c2016-03-04 14:33:47 -0800125 /**
126 * Protected so that tests can override and returns something a fixed value.
127 */
128 @VisibleForTesting
129 protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
Wale Ogunwale7c726682015-02-06 17:34:28 -0800130 DisplayMetrics dm = new DisplayMetrics();
Wale Ogunwale26698512015-06-05 16:55:33 -0700131 final Display display =
132 getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800133 if (display != null) {
134 display.getMetrics(dm);
Craig Mautner88c05892013-06-28 09:47:45 -0700135 } else {
Craig Mautner88c05892013-06-28 09:47:45 -0700136 dm.setToDefaults();
137 }
Craig Mautner88c05892013-06-28 09:47:45 -0700138 return dm;
139 }
140
Adam Lesinski082614c2016-03-04 14:33:47 -0800141 private static void applyNonDefaultDisplayMetricsToConfiguration(
142 @NonNull DisplayMetrics dm, @NonNull Configuration config) {
Craig Mautner88c05892013-06-28 09:47:45 -0700143 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
144 config.densityDpi = dm.densityDpi;
Adam Lesinski082614c2016-03-04 14:33:47 -0800145 config.screenWidthDp = (int) (dm.widthPixels / dm.density);
146 config.screenHeightDp = (int) (dm.heightPixels / dm.density);
Craig Mautner88c05892013-06-28 09:47:45 -0700147 int sl = Configuration.resetScreenLayout(config.screenLayout);
148 if (dm.widthPixels > dm.heightPixels) {
149 config.orientation = Configuration.ORIENTATION_LANDSCAPE;
150 config.screenLayout = Configuration.reduceScreenLayout(sl,
151 config.screenWidthDp, config.screenHeightDp);
152 } else {
153 config.orientation = Configuration.ORIENTATION_PORTRAIT;
154 config.screenLayout = Configuration.reduceScreenLayout(sl,
155 config.screenHeightDp, config.screenWidthDp);
156 }
157 config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
158 config.compatScreenWidthDp = config.screenWidthDp;
159 config.compatScreenHeightDp = config.screenHeightDp;
160 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
161 }
162
Adam Lesinski082614c2016-03-04 14:33:47 -0800163 public boolean applyCompatConfigurationLocked(int displayDensity,
164 @NonNull Configuration compatConfiguration) {
Craig Mautner88c05892013-06-28 09:47:45 -0700165 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
166 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
167 return true;
168 }
169 return false;
170 }
171
172 /**
Wale Ogunwale7c726682015-02-06 17:34:28 -0800173 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
174 * available.
175 *
176 * @param displayId display Id.
Wale Ogunwale26698512015-06-05 16:55:33 -0700177 * @param displayAdjustments display adjustments.
Wale Ogunwale7c726682015-02-06 17:34:28 -0800178 */
Adam Lesinski082614c2016-03-04 14:33:47 -0800179 public Display getAdjustedDisplay(final int displayId,
180 @Nullable DisplayAdjustments displayAdjustments) {
Wale Ogunwale26698512015-06-05 16:55:33 -0700181 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
182 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
183 final Pair<Integer, DisplayAdjustments> key =
184 Pair.create(displayId, displayAdjustmentsCopy);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800185 synchronized (this) {
186 WeakReference<Display> wd = mDisplays.get(key);
187 if (wd != null) {
188 final Display display = wd.get();
189 if (display != null) {
190 return display;
191 }
192 }
193 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
194 if (dm == null) {
195 // may be null early in system startup
196 return null;
197 }
Wale Ogunwale26698512015-06-05 16:55:33 -0700198 final Display display = dm.getCompatibleDisplay(displayId, key.second);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800199 if (display != null) {
200 mDisplays.put(key, new WeakReference<>(display));
201 }
202 return display;
203 }
204 }
205
206 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800207 * Creates an AssetManager from the paths within the ResourcesKey.
Craig Mautner88c05892013-06-28 09:47:45 -0700208 *
Adam Lesinski082614c2016-03-04 14:33:47 -0800209 * This can be overridden in tests so as to avoid creating a real AssetManager with
210 * real APK paths.
211 * @param key The key containing the resource paths to add to the AssetManager.
212 * @return a new AssetManager.
213 */
214 @VisibleForTesting
215 protected AssetManager createAssetManager(@NonNull final ResourcesKey key) {
Craig Mautner88c05892013-06-28 09:47:45 -0700216 AssetManager assets = new AssetManager();
Adam Lesinski082614c2016-03-04 14:33:47 -0800217
Adam Lesinski54130de2014-08-20 10:49:13 -0700218 // resDir can be null if the 'android' package is creating a new Resources object.
219 // This is fine, since each AssetManager automatically loads the 'android' package
220 // already.
Adam Lesinski082614c2016-03-04 14:33:47 -0800221 if (key.mResDir != null) {
222 if (assets.addAssetPath(key.mResDir) == 0) {
Adam Lesinski54130de2014-08-20 10:49:13 -0700223 return null;
224 }
Craig Mautner88c05892013-06-28 09:47:45 -0700225 }
226
Adam Lesinski082614c2016-03-04 14:33:47 -0800227 if (key.mSplitResDirs != null) {
228 for (final String splitResDir : key.mSplitResDirs) {
Jeff Sharkey8a4c9722014-06-16 13:48:42 -0700229 if (assets.addAssetPath(splitResDir) == 0) {
230 return null;
231 }
232 }
233 }
234
Adam Lesinski082614c2016-03-04 14:33:47 -0800235 if (key.mOverlayDirs != null) {
236 for (final String idmapPath : key.mOverlayDirs) {
MÃ¥rten Kongstad48d22322014-01-31 14:43:27 +0100237 assets.addOverlayPath(idmapPath);
238 }
239 }
240
Adam Lesinski082614c2016-03-04 14:33:47 -0800241 if (key.mLibDirs != null) {
242 for (final String libDir : key.mLibDirs) {
Adam Lesinski1dd50c52015-03-16 15:10:56 -0700243 if (libDir.endsWith(".apk")) {
244 // Avoid opening files we know do not have resources,
245 // like code-only .jar files.
246 if (assets.addAssetPath(libDir) == 0) {
247 Log.w(TAG, "Asset path '" + libDir +
248 "' does not exist or contains no resources.");
249 }
Adam Lesinskide898ff2014-01-29 18:20:45 -0800250 }
251 }
252 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800253 return assets;
254 }
Adam Lesinskide898ff2014-01-29 18:20:45 -0800255
Adam Lesinski082614c2016-03-04 14:33:47 -0800256 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
Craig Mautner88c05892013-06-28 09:47:45 -0700257 Configuration config;
Adam Lesinski082614c2016-03-04 14:33:47 -0800258 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
Craig Mautner88c05892013-06-28 09:47:45 -0700259 final boolean hasOverrideConfig = key.hasOverrideConfiguration();
260 if (!isDefaultDisplay || hasOverrideConfig) {
261 config = new Configuration(getConfiguration());
262 if (!isDefaultDisplay) {
Adam Lesinski082614c2016-03-04 14:33:47 -0800263 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
Craig Mautner88c05892013-06-28 09:47:45 -0700264 }
265 if (hasOverrideConfig) {
266 config.updateFrom(key.mOverrideConfiguration);
Wale Ogunwale60454db2015-01-23 16:05:07 -0800267 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
Craig Mautner88c05892013-06-28 09:47:45 -0700268 }
269 } else {
270 config = getConfiguration();
271 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800272 return config;
273 }
Craig Mautner88c05892013-06-28 09:47:45 -0700274
Adam Lesinski082614c2016-03-04 14:33:47 -0800275
276 private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
277 AssetManager assets = createAssetManager(key);
278 DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
279 Configuration config = generateConfig(key, dm);
280 ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
281 if (DEBUG) {
282 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
283 }
284 return impl;
285 }
286
287 /**
288 * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
289 *
290 * @param key The key to match.
291 * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
292 */
293 private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
294 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
295 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
296 if (impl != null && impl.getAssets().isUpToDate()) {
297 return impl;
298 }
299 return null;
300 }
301
302 /**
303 * Find the ResourcesKey that this ResourcesImpl object is associated with.
304 * @return the ResourcesKey or null if none was found.
305 */
306 private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) {
307 final int refCount = mResourceImpls.size();
308 for (int i = 0; i < refCount; i++) {
309 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
310 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
311 if (impl != null && resourceImpl == impl) {
312 return mResourceImpls.keyAt(i);
313 }
314 }
315 return null;
316 }
317
318 /**
319 * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
320 * or the class loader is different.
321 */
322 private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
323 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
324 // This is a request tied to an Activity, meaning we will need to update all
325 // Activity related Resources to match this configuration.
326 WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken);
327 Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
328 if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) {
329 resources = new Resources(classLoader);
330 mActivityResourceReferences.put(activityToken, new WeakReference<>(resources));
331 if (DEBUG) {
332 Slog.d(TAG, "- creating new ref=" + resources);
333 }
334 } else {
335 if (DEBUG) {
336 Slog.d(TAG, "- using existing ref=" + resources);
337 }
338 }
339
340 if (resources.getImpl() != impl) {
341 if (DEBUG) {
342 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
343 }
344
345 // Setting an impl is expensive because we update all ThemeImpl references.
346 // too.
347 resources.setImpl(impl);
348 }
349 return resources;
350 }
351
352 /**
353 * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
354 * otherwise creates a new Resources object.
355 */
356 private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
357 @NonNull ResourcesImpl impl) {
358 // Find an existing Resources that has this ResourcesImpl set.
359 final int refCount = mResourceReferences.size();
360 for (int i = 0; i < refCount; i++) {
361 WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
362 Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
363 if (resources != null &&
364 Objects.equals(resources.getClassLoader(), classLoader) &&
365 resources.getImpl() == impl) {
366 if (DEBUG) {
367 Slog.d(TAG, "- using existing ref=" + resources);
368 }
369 return resources;
370 }
371 }
372
373 // Create a new Resources reference and use the existing ResourcesImpl object.
374 Resources resources = new Resources(classLoader);
375 resources.setImpl(impl);
376 mResourceReferences.add(new WeakReference<>(resources));
377 if (DEBUG) {
378 Slog.d(TAG, "- creating new ref=" + resources);
379 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
380 }
381 return resources;
382 }
383
384 /**
385 * Gets or creates a new Resources object associated with the IBinder token. References returned
386 * by this method live as long as the Activity, meaning they can be cached and used by the
387 * Activity even after a configuration change. If any other parameter is changed
388 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
389 * is updated and handed back to the caller. However, changing the class loader will result in a
390 * new Resources object.
391 * <p/>
392 * If activityToken is null, a cached Resources object will be returned if it matches the
393 * input parameters. Otherwise a new Resources object that satisfies these parameters is
394 * returned.
395 *
396 * @param activityToken Represents an Activity. If null, global resources are assumed.
397 * @param resDir The base resource path. Can be null (only framework resources will be loaded).
398 * @param splitResDirs An array of split resource paths. Can be null.
399 * @param overlayDirs An array of overlay paths. Can be null.
400 * @param libDirs An array of resource library paths. Can be null.
401 * @param displayId The ID of the display for which to create the resources.
402 * @param overrideConfig The configuration to apply on top of the base configuration. Can be
403 * null. Mostly used with Activities that are in multi-window which may override width and
404 * height properties from the base config.
405 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
406 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
407 * @param classLoader The class loader to use when inflating Resources. If null, the
408 * {@link ClassLoader#getSystemClassLoader()} is used.
409 * @return a Resources object from which to access resources.
410 */
411 public Resources getResources(@Nullable IBinder activityToken,
412 @Nullable String resDir,
413 @Nullable String[] splitResDirs,
414 @Nullable String[] overlayDirs,
415 @Nullable String[] libDirs,
416 int displayId,
417 @Nullable Configuration overrideConfig,
418 @NonNull CompatibilityInfo compatInfo,
419 @Nullable ClassLoader classLoader) {
420 final ResourcesKey key = new ResourcesKey(
421 resDir,
422 splitResDirs,
423 overlayDirs,
424 libDirs,
425 displayId,
426 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
427 compatInfo);
428
429 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
430
431 final boolean findSystemLocales;
432 final boolean hasNonSystemLocales;
433 synchronized (this) {
434 findSystemLocales = (mSystemLocales.length == 0);
435 hasNonSystemLocales = mHasNonSystemLocales;
436
437 if (DEBUG) {
438 Throwable here = new Throwable();
439 here.fillInStackTrace();
440 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
441 }
442
443 if (activityToken != null) {
444 ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
445 if (resourcesImpl != null) {
446 if (DEBUG) {
447 Slog.d(TAG, "- using existing impl=" + resourcesImpl);
448 }
449 return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
450 resourcesImpl);
451 }
452
453 // We will create the ResourcesImpl object outside of holding this lock.
454
455 } else {
456 // Clean up any dead references so they don't pile up.
457 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
458
459 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
460 ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
461 if (resourcesImpl != null) {
462 if (DEBUG) {
463 Slog.d(TAG, "- using existing impl=" + resourcesImpl);
464 }
465 return getOrCreateResourcesLocked(classLoader, resourcesImpl);
466 }
467
468 // We will create the ResourcesImpl object outside of holding this lock.
469 }
470 }
471
472 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
473 ResourcesImpl resourcesImpl = createResourcesImpl(key);
474
475 final String[] systemLocales = findSystemLocales
476 ? AssetManager.getSystem().getLocales() : null;
477 final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800478 // Avoid checking for non-pseudo-locales if we already know there were some from a previous
479 // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
480 // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
481 // able to affect mHasNonSystemLocales.
482 final boolean isPseudoLocalesOnly = hasNonSystemLocales ||
483 LocaleList.isPseudoLocalesOnly(nonSystemLocales);
484
Craig Mautner88c05892013-06-28 09:47:45 -0700485 synchronized (this) {
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800486 if (mSystemLocales.length == 0) {
487 mSystemLocales = systemLocales;
488 }
489 mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
490 mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
Adam Lesinski082614c2016-03-04 14:33:47 -0800491
492 ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
493 if (existingResourcesImpl != null) {
494 if (DEBUG) {
495 Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
496 + " new impl=" + resourcesImpl);
497 }
498 resourcesImpl.getAssets().close();
499 resourcesImpl = existingResourcesImpl;
500 } else {
501 // Add this ResourcesImpl to the cache.
502 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
503 }
504
505 final Resources resources;
506 if (activityToken != null) {
507 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
508 resourcesImpl);
509 } else {
510 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
511 }
512 return resources;
Craig Mautner88c05892013-06-28 09:47:45 -0700513 }
514 }
515
Todd Kennedy39bfee52016-02-24 10:28:21 -0800516 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800517 * Updates an Activity's Resources object with overrideConfig. The Resources object
518 * that was previously returned by
519 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
520 * CompatibilityInfo, ClassLoader)} is
521 * still valid and will have the updated configuration.
522 * @param activityToken The Activity token.
523 * @param overrideConfig The configuration override to update.
Todd Kennedy39bfee52016-02-24 10:28:21 -0800524 */
Adam Lesinski082614c2016-03-04 14:33:47 -0800525 public void updateResourcesForActivity(@NonNull IBinder activityToken,
526 @Nullable Configuration overrideConfig) {
527 final ClassLoader classLoader;
528 final ResourcesKey oldKey;
529 synchronized (this) {
530 // Extract the ResourcesKey that was last used to create the Resources for this
531 // activity.
532 WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken);
533 final Resources resources = weakResRef != null ? weakResRef.get() : null;
534 if (resources == null) {
535 Slog.e(TAG, "can't update resources for uncached activity " + activityToken);
536 return;
537 }
538
539 classLoader = resources.getClassLoader();
540 oldKey = findKeyForResourceImpl(resources.getImpl());
541 if (oldKey == null) {
542 Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
543 return;
544 }
545 }
546
547 // Update the Resources object with the new override config and all of the existing
548 // settings.
549 getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs,
550 oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo,
551 classLoader);
Todd Kennedy39bfee52016-02-24 10:28:21 -0800552 }
553
Adam Lesinski082614c2016-03-04 14:33:47 -0800554 /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800555 final int bestLocale;
556 if (mHasNonSystemLocales) {
557 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
558 } else {
559 // We fallback to system locales if there was no locale specifically supported by the
560 // assets. This is to properly support apps that only rely on the shared system assets
561 // and don't need assets of their own.
562 bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales);
563 }
564 // set it for Java, this also affects newly created Resources
565 LocaleList.setDefault(locales, bestLocale);
566 }
567
Adam Lesinski082614c2016-03-04 14:33:47 -0800568 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
569 @Nullable CompatibilityInfo compat) {
Craig Mautner88c05892013-06-28 09:47:45 -0700570 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
Wale Ogunwale60454db2015-01-23 16:05:07 -0800571 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
Craig Mautner88c05892013-06-28 09:47:45 -0700572 + mResConfiguration.seq + ", newSeq=" + config.seq);
573 return false;
574 }
575 int changes = mResConfiguration.updateFrom(config);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800576 // Things might have changed in display manager, so clear the cached displays.
577 mDisplays.clear();
578 DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
Craig Mautner88c05892013-06-28 09:47:45 -0700579
580 if (compat != null && (mResCompatibilityInfo == null ||
581 !mResCompatibilityInfo.equals(compat))) {
582 mResCompatibilityInfo = compat;
583 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
584 | ActivityInfo.CONFIG_SCREEN_SIZE
585 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
586 }
587
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800588 Configuration localeAdjustedConfig = config;
589 final LocaleList configLocales = config.getLocales();
590 if (!configLocales.isEmpty()) {
591 setDefaultLocalesLocked(configLocales);
592 final LocaleList adjustedLocales = LocaleList.getAdjustedDefault();
593 if (adjustedLocales != configLocales) { // has the same result as .equals() in this case
594 // The first locale in the list was not chosen. So we create a modified
595 // configuration with the adjusted locales (which moves the chosen locale to the
596 // front).
597 localeAdjustedConfig = new Configuration();
598 localeAdjustedConfig.setTo(config);
599 localeAdjustedConfig.setLocales(adjustedLocales);
600 // Also adjust the locale list in mResConfiguration, so that the Resources created
601 // later would have the same locale list.
602 if (!mResConfiguration.getLocales().equals(adjustedLocales)) {
603 mResConfiguration.setLocales(adjustedLocales);
604 changes |= ActivityInfo.CONFIG_LOCALE;
605 }
606 }
Craig Mautner88c05892013-06-28 09:47:45 -0700607 }
608
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800609 Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat);
Craig Mautner88c05892013-06-28 09:47:45 -0700610
611 ApplicationPackageManager.configurationChanged();
612 //Slog.i(TAG, "Configuration changed in " + currentPackageName());
613
614 Configuration tmpConfig = null;
615
Adam Lesinski082614c2016-03-04 14:33:47 -0800616 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
617 ResourcesKey key = mResourceImpls.keyAt(i);
618 ResourcesImpl r = mResourceImpls.valueAt(i).get();
Craig Mautner88c05892013-06-28 09:47:45 -0700619 if (r != null) {
Wale Ogunwale60454db2015-01-23 16:05:07 -0800620 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800621 + r + " config to: " + localeAdjustedConfig);
Dianne Hackbornadd005c2013-07-17 18:43:12 -0700622 int displayId = key.mDisplayId;
Craig Mautner88c05892013-06-28 09:47:45 -0700623 boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
624 DisplayMetrics dm = defaultDisplayMetrics;
Dianne Hackbornadd005c2013-07-17 18:43:12 -0700625 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
Craig Mautner88c05892013-06-28 09:47:45 -0700626 if (!isDefaultDisplay || hasOverrideConfiguration) {
627 if (tmpConfig == null) {
628 tmpConfig = new Configuration();
629 }
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800630 tmpConfig.setTo(localeAdjustedConfig);
Craig Mautner88c05892013-06-28 09:47:45 -0700631 if (!isDefaultDisplay) {
632 dm = getDisplayMetricsLocked(displayId);
Adam Lesinski082614c2016-03-04 14:33:47 -0800633 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
Craig Mautner88c05892013-06-28 09:47:45 -0700634 }
635 if (hasOverrideConfiguration) {
Dianne Hackbornadd005c2013-07-17 18:43:12 -0700636 tmpConfig.updateFrom(key.mOverrideConfiguration);
Craig Mautner88c05892013-06-28 09:47:45 -0700637 }
638 r.updateConfiguration(tmpConfig, dm, compat);
639 } else {
Roozbeh Pournader834641b2016-01-23 22:34:57 -0800640 r.updateConfiguration(localeAdjustedConfig, dm, compat);
Craig Mautner88c05892013-06-28 09:47:45 -0700641 }
642 //Slog.i(TAG, "Updated app resources " + v.getKey()
643 // + " " + r + ": " + r.getConfiguration());
644 } else {
645 //Slog.i(TAG, "Removing old resources " + v.getKey());
Adam Lesinski082614c2016-03-04 14:33:47 -0800646 mResourceImpls.removeAt(i);
Craig Mautner88c05892013-06-28 09:47:45 -0700647 }
648 }
649
650 return changes != 0;
651 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800652}