blob: 2a4b278bdca82bb49edb9baba00031b8bf96c8bd [file] [log] [blame]
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.content.res;
17
18import android.animation.Animator;
19import android.animation.StateListAnimator;
20import android.annotation.AnyRes;
21import android.annotation.AttrRes;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.PluralsRes;
25import android.annotation.RawRes;
26import android.annotation.StyleRes;
27import android.annotation.StyleableRes;
28import android.content.pm.ActivityInfo;
Alan Viveretteac85f902016-03-11 15:15:51 -050029import android.content.pm.ActivityInfo.Config;
Alan Viverette9ad386b2017-01-26 14:00:20 -050030import android.content.res.Configuration.NativeConfig;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080031import android.content.res.Resources.NotFoundException;
Makoto Onuki1480b672017-07-14 08:42:50 -070032import android.graphics.Bitmap;
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000033import android.graphics.Typeface;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080034import android.graphics.drawable.ColorDrawable;
35import android.graphics.drawable.Drawable;
ztenghuiee7e8f12017-05-16 15:30:50 -070036import android.graphics.drawable.DrawableContainer;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080037import android.icu.text.PluralRules;
38import android.os.Build;
Yohei Yukawa23cbe852016-05-17 16:42:58 -070039import android.os.LocaleList;
Makoto Onuki1480b672017-07-14 08:42:50 -070040import android.os.SystemClock;
41import android.os.SystemProperties;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080042import android.os.Trace;
43import android.util.AttributeSet;
44import android.util.DisplayMetrics;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080045import android.util.Log;
46import android.util.LongSparseArray;
47import android.util.Slog;
48import android.util.TypedValue;
49import android.util.Xml;
Adam Lesinski4ece3d62016-06-16 18:05:41 -070050import android.view.DisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080051
Sunny Goyal99b25d22017-11-01 11:58:13 -070052import com.android.internal.util.GrowingArrayUtils;
53
Makoto Onuki1480b672017-07-14 08:42:50 -070054import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000057import java.io.IOException;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080058import java.io.InputStream;
59import java.util.Arrays;
60import java.util.Locale;
61
62/**
Adam Lesinski082614c2016-03-04 14:33:47 -080063 * The implementation of Resource access. This class contains the AssetManager and all caches
64 * associated with it.
65 *
66 * {@link Resources} is just a thing wrapper around this class. When a configuration change
67 * occurs, clients can retain the same {@link Resources} reference because the underlying
68 * {@link ResourcesImpl} object will be updated or re-created.
69 *
Adam Lesinskifb302cc2016-02-29 16:50:38 -080070 * @hide
71 */
72public class ResourcesImpl {
73 static final String TAG = "Resources";
74
75 private static final boolean DEBUG_LOAD = false;
76 private static final boolean DEBUG_CONFIG = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080077
Makoto Onuki1480b672017-07-14 08:42:50 -070078 static final String TAG_PRELOAD = TAG + ".preload";
79
80 private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
81 private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
82
83 public static final boolean TRACE_FOR_DETAILED_PRELOAD =
84 SystemProperties.getBoolean("debug.trace_resource_preload", false);
85
86 /** Used only when TRACE_FOR_DETAILED_PRELOAD is true. */
87 private static int sPreloadTracingNumLoadedDrawables;
88 private long mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -070089 private long mPreloadTracingStartBitmapSize;
90 private long mPreloadTracingStartBitmapCount;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080091
92 private static final int ID_OTHER = 0x01000004;
93
94 private static final Object sSync = new Object();
95
96 private static boolean sPreloaded;
97 private boolean mPreloading;
98
99 // Information about preloaded resources. Note that they are not
100 // protected by a lock, because while preloading in zygote we are all
101 // single-threaded, and after that these are immutable.
102 private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
103 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
104 = new LongSparseArray<>();
105 private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
106 sPreloadedComplexColors = new LongSparseArray<>();
107
108 /** Lock object used to protect access to caches and configuration. */
109 private final Object mAccessLock = new Object();
110
111 // These are protected by mAccessLock.
112 private final Configuration mTmpConfig = new Configuration();
113 private final DrawableCache mDrawableCache = new DrawableCache();
114 private final DrawableCache mColorDrawableCache = new DrawableCache();
115 private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
116 new ConfigurationBoundResourceCache<>();
117 private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
118 new ConfigurationBoundResourceCache<>();
119 private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
120 new ConfigurationBoundResourceCache<>();
121
Sunny Goyal99b25d22017-11-01 11:58:13 -0700122 // A stack of all the resourceIds already referenced when parsing a resource. This is used to
123 // detect circular references in the xml.
124 // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
125 // calls to ResourcesImpl
126 private final ThreadLocal<LookupStack> mLookupStack =
127 ThreadLocal.withInitial(() -> new LookupStack());
128
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800129 /** Size of the cyclical cache used to map XML files to blocks. */
130 private static final int XML_BLOCK_CACHE_SIZE = 4;
131
132 // Cyclical cache used for recently-accessed XML files.
133 private int mLastCachedXmlBlockIndex = -1;
134 private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
135 private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
136 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
137
138
139 final AssetManager mAssets;
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700140 private final DisplayMetrics mMetrics = new DisplayMetrics();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700141 private final DisplayAdjustments mDisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800142
143 private PluralRules mPluralRule;
144
145 private final Configuration mConfiguration = new Configuration();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800146
147 static {
148 sPreloadedDrawables = new LongSparseArray[2];
149 sPreloadedDrawables[0] = new LongSparseArray<>();
150 sPreloadedDrawables[1] = new LongSparseArray<>();
151 }
152
153 /**
154 * Creates a new ResourcesImpl object with CompatibilityInfo.
155 *
156 * @param assets Previously created AssetManager.
157 * @param metrics Current display metrics to consider when
158 * selecting/computing resource values.
159 * @param config Desired device configuration to consider when
160 * selecting/computing resource values (optional).
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700161 * @param displayAdjustments this resource's Display override and compatibility info.
162 * Must not be null.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800163 */
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700164 public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700165 @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800166 mAssets = assets;
167 mMetrics.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700168 mDisplayAdjustments = displayAdjustments;
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700169 mConfiguration.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700170 updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800171 }
172
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700173 public DisplayAdjustments getDisplayAdjustments() {
174 return mDisplayAdjustments;
175 }
176
Adam Lesinski082614c2016-03-04 14:33:47 -0800177 public AssetManager getAssets() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800178 return mAssets;
179 }
180
181 DisplayMetrics getDisplayMetrics() {
182 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
183 + "x" + mMetrics.heightPixels + " " + mMetrics.density);
184 return mMetrics;
185 }
186
187 Configuration getConfiguration() {
188 return mConfiguration;
189 }
190
191 Configuration[] getSizeConfigurations() {
192 return mAssets.getSizeConfigurations();
193 }
194
195 CompatibilityInfo getCompatibilityInfo() {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700196 return mDisplayAdjustments.getCompatibilityInfo();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800197 }
198
199 private PluralRules getPluralRule() {
200 synchronized (sSync) {
201 if (mPluralRule == null) {
202 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
203 }
204 return mPluralRule;
205 }
206 }
207
208 void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
209 throws NotFoundException {
210 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
211 if (found) {
212 return;
213 }
214 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
215 }
216
217 void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
Adam Lesinski082614c2016-03-04 14:33:47 -0800218 boolean resolveRefs) throws NotFoundException {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800219 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
220 if (found) {
221 return;
222 }
223 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
224 }
225
226 void getValue(String name, TypedValue outValue, boolean resolveRefs)
227 throws NotFoundException {
228 int id = getIdentifier(name, "string", null);
229 if (id != 0) {
230 getValue(id, outValue, resolveRefs);
231 return;
232 }
233 throw new NotFoundException("String resource name " + name);
234 }
235
236 int getIdentifier(String name, String defType, String defPackage) {
237 if (name == null) {
238 throw new NullPointerException("name is null");
239 }
240 try {
241 return Integer.parseInt(name);
242 } catch (Exception e) {
243 // Ignore
244 }
245 return mAssets.getResourceIdentifier(name, defType, defPackage);
246 }
247
248 @NonNull
249 String getResourceName(@AnyRes int resid) throws NotFoundException {
250 String str = mAssets.getResourceName(resid);
251 if (str != null) return str;
252 throw new NotFoundException("Unable to find resource ID #0x"
253 + Integer.toHexString(resid));
254 }
255
256 @NonNull
257 String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
258 String str = mAssets.getResourcePackageName(resid);
259 if (str != null) return str;
260 throw new NotFoundException("Unable to find resource ID #0x"
261 + Integer.toHexString(resid));
262 }
263
264 @NonNull
265 String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
266 String str = mAssets.getResourceTypeName(resid);
267 if (str != null) return str;
268 throw new NotFoundException("Unable to find resource ID #0x"
269 + Integer.toHexString(resid));
270 }
271
272 @NonNull
273 String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
274 String str = mAssets.getResourceEntryName(resid);
275 if (str != null) return str;
276 throw new NotFoundException("Unable to find resource ID #0x"
277 + Integer.toHexString(resid));
278 }
279
280 @NonNull
281 CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
282 PluralRules rule = getPluralRule();
283 CharSequence res = mAssets.getResourceBagText(id,
284 attrForQuantityCode(rule.select(quantity)));
285 if (res != null) {
286 return res;
287 }
288 res = mAssets.getResourceBagText(id, ID_OTHER);
289 if (res != null) {
290 return res;
291 }
292 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
293 + " quantity=" + quantity
294 + " item=" + rule.select(quantity));
295 }
296
297 private static int attrForQuantityCode(String quantityCode) {
298 switch (quantityCode) {
299 case PluralRules.KEYWORD_ZERO: return 0x01000005;
300 case PluralRules.KEYWORD_ONE: return 0x01000006;
301 case PluralRules.KEYWORD_TWO: return 0x01000007;
302 case PluralRules.KEYWORD_FEW: return 0x01000008;
303 case PluralRules.KEYWORD_MANY: return 0x01000009;
304 default: return ID_OTHER;
305 }
306 }
307
308 @NonNull
309 AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
310 throws NotFoundException {
311 getValue(id, tempValue, true);
312 try {
313 return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
314 } catch (Exception e) {
315 throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
316 + "resource ID #0x" + Integer.toHexString(id), e);
317 }
318 }
319
320 @NonNull
321 InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
322 getValue(id, value, true);
323 try {
324 return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
325 AssetManager.ACCESS_STREAMING);
326 } catch (Exception e) {
Christopher Tatef135b272016-05-27 17:10:30 -0700327 // Note: value.string might be null
328 NotFoundException rnf = new NotFoundException("File "
329 + (value.string == null ? "(null)" : value.string.toString())
330 + " from drawable resource ID #0x" + Integer.toHexString(id));
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800331 rnf.initCause(e);
332 throw rnf;
333 }
334 }
335
336 ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
337 return mAnimatorCache;
338 }
339
340 ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
341 return mStateListAnimatorCache;
342 }
343
Adam Lesinski082614c2016-03-04 14:33:47 -0800344 public void updateConfiguration(Configuration config, DisplayMetrics metrics,
345 CompatibilityInfo compat) {
Adam Lesinski991357f2016-05-10 14:00:03 -0700346 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
347 try {
348 synchronized (mAccessLock) {
349 if (false) {
350 Slog.i(TAG, "**** Updating config of " + this + ": old config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700351 + mConfiguration + " old compat is "
352 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700353 Slog.i(TAG, "**** Updating config of " + this + ": new config is "
354 + config + " new compat is " + compat);
355 }
356 if (compat != null) {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700357 mDisplayAdjustments.setCompatibilityInfo(compat);
Adam Lesinski991357f2016-05-10 14:00:03 -0700358 }
359 if (metrics != null) {
360 mMetrics.setTo(metrics);
361 }
362 // NOTE: We should re-arrange this code to create a Display
363 // with the CompatibilityInfo that is used everywhere we deal
364 // with the display in relation to this app, rather than
365 // doing the conversion here. This impl should be okay because
366 // we make sure to return a compatible display in the places
367 // where there are public APIs to retrieve the display... but
Adam Lesinskib61e4052016-05-19 18:23:05 -0700368 // it would be cleaner and more maintainable to just be
Adam Lesinski991357f2016-05-10 14:00:03 -0700369 // consistently dealing with a compatible display everywhere in
370 // the framework.
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700371 mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800372
Adam Lesinski991357f2016-05-10 14:00:03 -0700373 final @Config int configChanges = calcConfigChanges(config);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800374
Adam Lesinskib61e4052016-05-19 18:23:05 -0700375 // If even after the update there are no Locales set, grab the default locales.
Adam Lesinski991357f2016-05-10 14:00:03 -0700376 LocaleList locales = mConfiguration.getLocales();
377 if (locales.isEmpty()) {
Adam Lesinskib61e4052016-05-19 18:23:05 -0700378 locales = LocaleList.getDefault();
Adam Lesinski991357f2016-05-10 14:00:03 -0700379 mConfiguration.setLocales(locales);
380 }
Adam Lesinskib61e4052016-05-19 18:23:05 -0700381
382 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
383 if (locales.size() > 1) {
384 // The LocaleList has changed. We must query the AssetManager's available
385 // Locales and figure out the best matching Locale in the new LocaleList.
386 String[] availableLocales = mAssets.getNonSystemLocales();
387 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
388 // No app defined locales, so grab the system locales.
389 availableLocales = mAssets.getLocales();
390 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
391 availableLocales = null;
392 }
393 }
394
395 if (availableLocales != null) {
396 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
397 availableLocales);
398 if (bestLocale != null && bestLocale != locales.get(0)) {
399 mConfiguration.setLocales(new LocaleList(bestLocale, locales));
400 }
401 }
402 }
403 }
404
Adam Lesinski991357f2016-05-10 14:00:03 -0700405 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
406 mMetrics.densityDpi = mConfiguration.densityDpi;
407 mMetrics.density =
408 mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
409 }
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700410
411 // Protect against an unset fontScale.
412 mMetrics.scaledDensity = mMetrics.density *
413 (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
Adam Lesinski991357f2016-05-10 14:00:03 -0700414
415 final int width, height;
416 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
417 width = mMetrics.widthPixels;
418 height = mMetrics.heightPixels;
419 } else {
420 //noinspection SuspiciousNameCombination
421 width = mMetrics.heightPixels;
422 //noinspection SuspiciousNameCombination
423 height = mMetrics.widthPixels;
424 }
425
426 final int keyboardHidden;
427 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
428 && mConfiguration.hardKeyboardHidden
429 == Configuration.HARDKEYBOARDHIDDEN_YES) {
430 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
431 } else {
432 keyboardHidden = mConfiguration.keyboardHidden;
433 }
434
435 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
Adam Lesinskib61e4052016-05-19 18:23:05 -0700436 adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
Adam Lesinski991357f2016-05-10 14:00:03 -0700437 mConfiguration.orientation,
438 mConfiguration.touchscreen,
439 mConfiguration.densityDpi, mConfiguration.keyboard,
440 keyboardHidden, mConfiguration.navigation, width, height,
441 mConfiguration.smallestScreenWidthDp,
442 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
443 mConfiguration.screenLayout, mConfiguration.uiMode,
Romain Guy408afbf2017-01-25 10:23:03 -0800444 mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
Adam Lesinski991357f2016-05-10 14:00:03 -0700445
446 if (DEBUG_CONFIG) {
447 Slog.i(TAG, "**** Updating config of " + this + ": final config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700448 + mConfiguration + " final compat is "
449 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700450 }
451
452 mDrawableCache.onConfigurationChange(configChanges);
453 mColorDrawableCache.onConfigurationChange(configChanges);
454 mComplexColorCache.onConfigurationChange(configChanges);
455 mAnimatorCache.onConfigurationChange(configChanges);
456 mStateListAnimatorCache.onConfigurationChange(configChanges);
457
458 flushLayoutCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800459 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700460 synchronized (sSync) {
461 if (mPluralRule != null) {
462 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
463 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800464 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700465 } finally {
466 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800467 }
468 }
469
470 /**
Alan Viveretteac85f902016-03-11 15:15:51 -0500471 * Applies the new configuration, returning a bitmask of the changes
472 * between the old and new configurations.
473 *
474 * @param config the new configuration
475 * @return bitmask of config changes
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800476 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500477 public @Config int calcConfigChanges(@Nullable Configuration config) {
478 if (config == null) {
479 // If there is no configuration, assume all flags have changed.
480 return 0xFFFFFFFF;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800481 }
Alan Viveretteac85f902016-03-11 15:15:51 -0500482
483 mTmpConfig.setTo(config);
484 int density = config.densityDpi;
485 if (density == Configuration.DENSITY_DPI_UNDEFINED) {
486 density = mMetrics.noncompatDensityDpi;
487 }
488
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700489 mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
Alan Viveretteac85f902016-03-11 15:15:51 -0500490
491 if (mTmpConfig.getLocales().isEmpty()) {
492 mTmpConfig.setLocales(LocaleList.getDefault());
493 }
494 return mConfiguration.updateFrom(mTmpConfig);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800495 }
496
497 /**
498 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
499 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
500 *
501 * All released versions of android prior to "L" used the deprecated language
502 * tags, so we will need to support them for backwards compatibility.
503 *
504 * Note that this conversion needs to take place *after* the call to
505 * {@code toLanguageTag} because that will convert all the deprecated codes to
506 * the new ones, even if they're set manually.
507 */
508 private static String adjustLanguageTag(String languageTag) {
509 final int separator = languageTag.indexOf('-');
510 final String language;
511 final String remainder;
512
513 if (separator == -1) {
514 language = languageTag;
515 remainder = "";
516 } else {
517 language = languageTag.substring(0, separator);
518 remainder = languageTag.substring(separator);
519 }
520
521 return Locale.adjustLanguageCode(language) + remainder;
522 }
523
524 /**
525 * Call this to remove all cached loaded layout resources from the
526 * Resources object. Only intended for use with performance testing
527 * tools.
528 */
529 public void flushLayoutCache() {
530 synchronized (mCachedXmlBlocks) {
531 Arrays.fill(mCachedXmlBlockCookies, 0);
532 Arrays.fill(mCachedXmlBlockFiles, null);
533
534 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
535 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
536 final XmlBlock oldBlock = cachedXmlBlocks[i];
537 if (oldBlock != null) {
538 oldBlock.close();
539 }
540 }
541 Arrays.fill(cachedXmlBlocks, null);
542 }
543 }
544
545 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700546 Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
547 int density, @Nullable Resources.Theme theme)
548 throws NotFoundException {
549 // If the drawable's XML lives in our current density qualifier,
550 // it's okay to use a scaled version from the cache. Otherwise, we
551 // need to actually load the drawable from XML.
552 final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
553
554 // Pretend the requested density is actually the display density. If
555 // the drawable returned is not the requested density, then force it
556 // to be scaled later by dividing its density by the ratio of
557 // requested density to actual device density. Drawables that have
558 // undefined density or no density don't need to be handled here.
559 if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
560 if (value.density == density) {
561 value.density = mMetrics.densityDpi;
562 } else {
563 value.density = (value.density * mMetrics.densityDpi) / density;
564 }
565 }
566
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800567 try {
568 if (TRACE_FOR_PRELOAD) {
569 // Log only framework resources
570 if ((id >>> 24) == 0x1) {
571 final String name = getResourceName(id);
572 if (name != null) {
573 Log.d("PreloadDrawable", name);
574 }
575 }
576 }
577
578 final boolean isColorDrawable;
579 final DrawableCache caches;
580 final long key;
581 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
582 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
583 isColorDrawable = true;
584 caches = mColorDrawableCache;
585 key = value.data;
586 } else {
587 isColorDrawable = false;
588 caches = mDrawableCache;
589 key = (((long) value.assetCookie) << 32) | value.data;
590 }
591
592 // First, check whether we have a cached version of this drawable
593 // that was inflated against the specified theme. Skip the cache if
594 // we're currently preloading or we're not using the cache.
595 if (!mPreloading && useCache) {
596 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
597 if (cachedDrawable != null) {
Alan Viverette58857c82016-10-29 00:47:45 +0100598 cachedDrawable.setChangingConfigurations(value.changingConfigurations);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800599 return cachedDrawable;
600 }
601 }
602
603 // Next, check preloaded drawables. Preloaded drawables may contain
604 // unresolved theme attributes.
605 final Drawable.ConstantState cs;
606 if (isColorDrawable) {
607 cs = sPreloadedColorDrawables.get(key);
608 } else {
609 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
610 }
611
612 Drawable dr;
ztenghuiee7e8f12017-05-16 15:30:50 -0700613 boolean needsNewDrawableAfterCache = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800614 if (cs != null) {
Makoto Onuki1480b672017-07-14 08:42:50 -0700615 if (TRACE_FOR_DETAILED_PRELOAD) {
616 // Log only framework resources
617 if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
618 final String name = getResourceName(id);
619 if (name != null) {
620 Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
621 + Integer.toHexString(id) + " " + name);
622 }
623 }
624 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800625 dr = cs.newDrawable(wrapper);
626 } else if (isColorDrawable) {
627 dr = new ColorDrawable(value.data);
628 } else {
Adam Lesinski50954d22017-04-14 18:41:52 -0700629 dr = loadDrawableForCookie(wrapper, value, id, density, null);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800630 }
ztenghuiee7e8f12017-05-16 15:30:50 -0700631 // DrawableContainer' constant state has drawables instances. In order to leave the
632 // constant state intact in the cache, we need to create a new DrawableContainer after
633 // added to cache.
634 if (dr instanceof DrawableContainer) {
635 needsNewDrawableAfterCache = true;
636 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800637
638 // Determine if the drawable has unresolved theme attributes. If it
639 // does, we'll need to apply a theme and store it in a theme-specific
640 // cache.
641 final boolean canApplyTheme = dr != null && dr.canApplyTheme();
642 if (canApplyTheme && theme != null) {
643 dr = dr.mutate();
644 dr.applyTheme(theme);
645 dr.clearMutated();
646 }
647
648 // If we were able to obtain a drawable, store it in the appropriate
649 // cache: preload, not themed, null theme, or theme-specific. Don't
650 // pollute the cache with drawables loaded from a foreign density.
Alan Viverette58857c82016-10-29 00:47:45 +0100651 if (dr != null) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800652 dr.setChangingConfigurations(value.changingConfigurations);
Alan Viverette58857c82016-10-29 00:47:45 +0100653 if (useCache) {
654 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
ztenghuiee7e8f12017-05-16 15:30:50 -0700655 if (needsNewDrawableAfterCache) {
656 Drawable.ConstantState state = dr.getConstantState();
657 if (state != null) {
658 dr = state.newDrawable(wrapper);
659 }
660 }
Alan Viverette58857c82016-10-29 00:47:45 +0100661 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800662 }
663
664 return dr;
665 } catch (Exception e) {
666 String name;
667 try {
668 name = getResourceName(id);
669 } catch (NotFoundException e2) {
670 name = "(missing name)";
671 }
672
673 // The target drawable might fail to load for any number of
674 // reasons, but we always want to include the resource name.
675 // Since the client already expects this method to throw a
676 // NotFoundException, just throw one of those.
677 final NotFoundException nfe = new NotFoundException("Drawable " + name
678 + " with resource ID #0x" + Integer.toHexString(id), e);
679 nfe.setStackTrace(new StackTraceElement[0]);
680 throw nfe;
681 }
682 }
683
684 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Adam Lesinski082614c2016-03-04 14:33:47 -0800685 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800686 final Drawable.ConstantState cs = dr.getConstantState();
687 if (cs == null) {
688 return;
689 }
690
691 if (mPreloading) {
692 final int changingConfigs = cs.getChangingConfigurations();
693 if (isColorDrawable) {
694 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
695 sPreloadedColorDrawables.put(key, cs);
696 }
697 } else {
698 if (verifyPreloadConfig(
Alan Viverettea8a66cc2017-03-20 15:00:51 -0400699 changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
700 if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800701 // If this resource does not vary based on layout direction,
702 // we can put it in all of the preload maps.
703 sPreloadedDrawables[0].put(key, cs);
704 sPreloadedDrawables[1].put(key, cs);
705 } else {
706 // Otherwise, only in the layout dir we loaded it for.
707 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
708 }
709 }
710 }
711 } else {
712 synchronized (mAccessLock) {
713 caches.put(key, theme, cs, usesTheme);
714 }
715 }
716 }
717
Alan Viveretteac85f902016-03-11 15:15:51 -0500718 private boolean verifyPreloadConfig(@Config int changingConfigurations,
719 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800720 // We allow preloading of resources even if they vary by font scale (which
721 // doesn't impact resource selection) or density (which we handle specially by
722 // simply turning off all preloading), as well as any other configs specified
723 // by the caller.
724 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
725 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
726 String resName;
727 try {
728 resName = getResourceName(resourceId);
729 } catch (NotFoundException e) {
730 resName = "?";
731 }
732 // This should never happen in production, so we should log a
733 // warning even if we're not debugging.
734 Log.w(TAG, "Preloaded " + name + " resource #0x"
735 + Integer.toHexString(resourceId)
736 + " (" + resName + ") that varies with configuration!!");
737 return false;
738 }
739 if (TRACE_FOR_PRELOAD) {
740 String resName;
741 try {
742 resName = getResourceName(resourceId);
743 } catch (NotFoundException e) {
744 resName = "?";
745 }
746 Log.w(TAG, "Preloading " + name + " resource #0x"
747 + Integer.toHexString(resourceId)
748 + " (" + resName + ")");
749 }
750 return true;
751 }
752
753 /**
754 * Loads a drawable from XML or resources stream.
755 */
Adam Lesinski50954d22017-04-14 18:41:52 -0700756 private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
757 int id, int density, @Nullable Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800758 if (value.string == null) {
759 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
760 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
761 }
762
763 final String file = value.string.toString();
764
765 if (TRACE_FOR_MISS_PRELOAD) {
766 // Log only framework resources
767 if ((id >>> 24) == 0x1) {
768 final String name = getResourceName(id);
769 if (name != null) {
770 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
771 + ": " + name + " at " + file);
772 }
773 }
774 }
775
Makoto Onuki1480b672017-07-14 08:42:50 -0700776 // For prelaod tracing.
777 long startTime = 0;
778 int startBitmapCount = 0;
779 long startBitmapSize = 0;
780 int startDrwableCount = 0;
781 if (TRACE_FOR_DETAILED_PRELOAD) {
782 startTime = System.nanoTime();
783 startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
784 startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
785 startDrwableCount = sPreloadTracingNumLoadedDrawables;
786 }
787
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800788 if (DEBUG_LOAD) {
789 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
790 }
791
792 final Drawable dr;
793
794 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
Sunny Goyal99b25d22017-11-01 11:58:13 -0700795 LookupStack stack = mLookupStack.get();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800796 try {
Sunny Goyal99b25d22017-11-01 11:58:13 -0700797 // Perform a linear search to check if we have already referenced this resource before.
798 if (stack.contains(id)) {
799 throw new Exception("Recursive reference in drawable");
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800800 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700801 stack.push(id);
802 try {
803 if (file.endsWith(".xml")) {
804 final XmlResourceParser rp = loadXmlResourceParser(
805 file, id, value.assetCookie, "drawable");
806 dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
807 rp.close();
808 } else {
809 final InputStream is = mAssets.openNonAsset(
810 value.assetCookie, file, AssetManager.ACCESS_STREAMING);
811 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
812 is.close();
813 }
814 } finally {
815 stack.pop();
816 }
Hyunyoung Songaff04c32017-10-10 10:35:59 -0700817 } catch (Exception | StackOverflowError e) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800818 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
819 final NotFoundException rnf = new NotFoundException(
820 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
821 rnf.initCause(e);
822 throw rnf;
823 }
824 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
825
Makoto Onuki1480b672017-07-14 08:42:50 -0700826 if (TRACE_FOR_DETAILED_PRELOAD) {
827 if (((id >>> 24) == 0x1)) {
828 final String name = getResourceName(id);
829 if (name != null) {
830 final long time = System.nanoTime() - startTime;
831 final int loadedBitmapCount =
832 Bitmap.sPreloadTracingNumInstantiatedBitmaps - startBitmapCount;
833 final long loadedBitmapSize =
834 Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
835 final int loadedDrawables =
836 sPreloadTracingNumLoadedDrawables - startDrwableCount;
837
838 sPreloadTracingNumLoadedDrawables++;
839
840 final boolean isRoot = (android.os.Process.myUid() == 0);
841
842 Log.d(TAG_PRELOAD,
843 (isRoot ? "Preloaded FW drawable #"
844 : "Loaded non-preloaded FW drawable #")
845 + Integer.toHexString(id)
846 + " " + name
847 + " " + file
848 + " " + dr.getClass().getCanonicalName()
849 + " #nested_drawables= " + loadedDrawables
850 + " #bitmaps= " + loadedBitmapCount
851 + " total_bitmap_size= " + loadedBitmapSize
852 + " in[us] " + (time / 1000));
853 }
854 }
855 }
856
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800857 return dr;
858 }
859
860 /**
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000861 * Loads a font from XML or resources stream.
862 */
863 @Nullable
Clara Bayarried00bfd2017-01-20 14:58:21 +0000864 public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000865 if (value.string == null) {
866 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
867 + Integer.toHexString(id) + ") is not a Font: " + value);
868 }
869
870 final String file = value.string.toString();
Seigo Nonaka2ea169a2017-05-15 16:25:11 -0700871 if (!file.startsWith("res/")) {
872 return null;
873 }
874
Clara Bayarrib12397e2017-01-27 11:02:48 +0000875 Typeface cached = Typeface.findFromCache(mAssets, file);
Clara Bayarried00bfd2017-01-20 14:58:21 +0000876 if (cached != null) {
877 return cached;
878 }
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000879
880 if (DEBUG_LOAD) {
881 Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
882 }
883
884 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
885 try {
Clara Bayarried00bfd2017-01-20 14:58:21 +0000886 if (file.endsWith("xml")) {
887 final XmlResourceParser rp = loadXmlResourceParser(
888 file, id, value.assetCookie, "font");
Seigo Nonakaac873c92017-03-07 15:34:53 -0800889 final FontResourcesParser.FamilyResourceEntry familyEntry =
890 FontResourcesParser.parse(rp, wrapper);
891 if (familyEntry == null) {
Seigo Nonakaac873c92017-03-07 15:34:53 -0800892 return null;
893 }
894 return Typeface.createFromResources(familyEntry, mAssets, file);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000895 }
Clara Bayarried00bfd2017-01-20 14:58:21 +0000896 return Typeface.createFromResources(mAssets, file, value.assetCookie);
897 } catch (XmlPullParserException e) {
898 Log.e(TAG, "Failed to parse xml resource " + file, e);
899 } catch (IOException e) {
900 Log.e(TAG, "Failed to read xml resource " + file, e);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000901 } finally {
902 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
903 }
904 return null;
905 }
906
907 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800908 * Given the value and id, we can get the XML filename as in value.data, based on that, we
909 * first try to load CSL from the cache. If not found, try to get from the constant state.
910 * Last, parse the XML and generate the CSL.
911 */
912 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
Adam Lesinski082614c2016-03-04 14:33:47 -0800913 TypedValue value, int id) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800914 final long key = (((long) value.assetCookie) << 32) | value.data;
915 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
916 ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
917 if (complexColor != null) {
918 return complexColor;
919 }
920
921 final android.content.res.ConstantState<ComplexColor> factory =
922 sPreloadedComplexColors.get(key);
923
924 if (factory != null) {
925 complexColor = factory.newInstance(wrapper, theme);
926 }
927 if (complexColor == null) {
928 complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
929 }
930
931 if (complexColor != null) {
Alan Viverette0b9295d2016-03-10 10:30:08 -0500932 complexColor.setBaseChangingConfigurations(value.changingConfigurations);
933
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800934 if (mPreloading) {
Alan Viverette0b9295d2016-03-10 10:30:08 -0500935 if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
936 0, value.resourceId, "color")) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800937 sPreloadedComplexColors.put(key, complexColor.getConstantState());
938 }
939 } else {
940 cache.put(key, theme, complexColor.getConstantState());
941 }
942 }
943 return complexColor;
944 }
945
946 @Nullable
947 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -0800948 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800949 if (TRACE_FOR_PRELOAD) {
950 // Log only framework resources
951 if ((id >>> 24) == 0x1) {
952 final String name = getResourceName(id);
953 if (name != null) android.util.Log.d("loadComplexColor", name);
954 }
955 }
956
957 final long key = (((long) value.assetCookie) << 32) | value.data;
958
959 // Handle inline color definitions.
960 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
961 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
962 return getColorStateListFromInt(value, key);
963 }
964
965 final String file = value.string.toString();
966
967 ComplexColor complexColor;
968 if (file.endsWith(".xml")) {
969 try {
970 complexColor = loadComplexColorFromName(wrapper, theme, value, id);
971 } catch (Exception e) {
972 final NotFoundException rnf = new NotFoundException(
973 "File " + file + " from complex color resource ID #0x"
974 + Integer.toHexString(id));
975 rnf.initCause(e);
976 throw rnf;
977 }
978 } else {
979 throw new NotFoundException(
980 "File " + file + " from drawable resource ID #0x"
981 + Integer.toHexString(id) + ": .xml extension required");
982 }
983
984 return complexColor;
985 }
986
987 @Nullable
988 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -0800989 Resources.Theme theme)
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800990 throws NotFoundException {
991 if (TRACE_FOR_PRELOAD) {
992 // Log only framework resources
993 if ((id >>> 24) == 0x1) {
994 final String name = getResourceName(id);
995 if (name != null) android.util.Log.d("PreloadColorStateList", name);
996 }
997 }
998
999 final long key = (((long) value.assetCookie) << 32) | value.data;
1000
1001 // Handle inline color definitions.
1002 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
1003 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1004 return getColorStateListFromInt(value, key);
1005 }
1006
1007 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1008 if (complexColor != null && complexColor instanceof ColorStateList) {
1009 return (ColorStateList) complexColor;
1010 }
1011
1012 throw new NotFoundException(
1013 "Can't find ColorStateList from drawable resource ID #0x"
1014 + Integer.toHexString(id));
1015 }
1016
1017 @NonNull
1018 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
1019 ColorStateList csl;
1020 final android.content.res.ConstantState<ComplexColor> factory =
1021 sPreloadedComplexColors.get(key);
1022 if (factory != null) {
1023 return (ColorStateList) factory.newInstance();
1024 }
1025
1026 csl = ColorStateList.valueOf(value.data);
1027
1028 if (mPreloading) {
1029 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
1030 "color")) {
1031 sPreloadedComplexColors.put(key, csl.getConstantState());
1032 }
1033 }
1034
1035 return csl;
1036 }
1037
1038 /**
1039 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
1040 * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
1041 *
1042 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
1043 * and selector tag.
1044 *
1045 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
1046 */
1047 @Nullable
1048 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001049 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001050 if (value.string == null) {
1051 throw new UnsupportedOperationException(
1052 "Can't convert to ComplexColor: type=0x" + value.type);
1053 }
1054
1055 final String file = value.string.toString();
1056
1057 if (TRACE_FOR_MISS_PRELOAD) {
1058 // Log only framework resources
1059 if ((id >>> 24) == 0x1) {
1060 final String name = getResourceName(id);
1061 if (name != null) {
1062 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
1063 + ": " + name + " at " + file);
1064 }
1065 }
1066 }
1067
1068 if (DEBUG_LOAD) {
1069 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
1070 }
1071
1072 ComplexColor complexColor = null;
1073
1074 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
1075 if (file.endsWith(".xml")) {
1076 try {
1077 final XmlResourceParser parser = loadXmlResourceParser(
1078 file, id, value.assetCookie, "ComplexColor");
1079
1080 final AttributeSet attrs = Xml.asAttributeSet(parser);
1081 int type;
1082 while ((type = parser.next()) != XmlPullParser.START_TAG
1083 && type != XmlPullParser.END_DOCUMENT) {
1084 // Seek parser to start tag.
1085 }
1086 if (type != XmlPullParser.START_TAG) {
1087 throw new XmlPullParserException("No start tag found");
1088 }
1089
1090 final String name = parser.getName();
1091 if (name.equals("gradient")) {
1092 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
1093 } else if (name.equals("selector")) {
1094 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
1095 }
1096 parser.close();
1097 } catch (Exception e) {
1098 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1099 final NotFoundException rnf = new NotFoundException(
1100 "File " + file + " from ComplexColor resource ID #0x"
1101 + Integer.toHexString(id));
1102 rnf.initCause(e);
1103 throw rnf;
1104 }
1105 } else {
1106 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1107 throw new NotFoundException(
1108 "File " + file + " from drawable resource ID #0x"
1109 + Integer.toHexString(id) + ": .xml extension required");
1110 }
1111 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1112
1113 return complexColor;
1114 }
1115
1116 /**
1117 * Loads an XML parser for the specified file.
1118 *
1119 * @param file the path for the XML file to parse
1120 * @param id the resource identifier for the file
1121 * @param assetCookie the asset cookie for the file
1122 * @param type the type of resource (used for logging)
1123 * @return a parser for the specified XML file
1124 * @throws NotFoundException if the file could not be loaded
1125 */
1126 @NonNull
Adam Lesinski082614c2016-03-04 14:33:47 -08001127 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
1128 @NonNull String type)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001129 throws NotFoundException {
1130 if (id != 0) {
1131 try {
1132 synchronized (mCachedXmlBlocks) {
1133 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
1134 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
1135 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
1136 // First see if this block is in our cache.
1137 final int num = cachedXmlBlockFiles.length;
1138 for (int i = 0; i < num; i++) {
1139 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
1140 && cachedXmlBlockFiles[i].equals(file)) {
1141 return cachedXmlBlocks[i].newParser();
1142 }
1143 }
1144
1145 // Not in the cache, create a new block and put it at
1146 // the next slot in the cache.
1147 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
1148 if (block != null) {
1149 final int pos = (mLastCachedXmlBlockIndex + 1) % num;
1150 mLastCachedXmlBlockIndex = pos;
1151 final XmlBlock oldBlock = cachedXmlBlocks[pos];
1152 if (oldBlock != null) {
1153 oldBlock.close();
1154 }
1155 cachedXmlBlockCookies[pos] = assetCookie;
1156 cachedXmlBlockFiles[pos] = file;
1157 cachedXmlBlocks[pos] = block;
1158 return block.newParser();
1159 }
1160 }
1161 } catch (Exception e) {
1162 final NotFoundException rnf = new NotFoundException("File " + file
1163 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
1164 rnf.initCause(e);
1165 throw rnf;
1166 }
1167 }
1168
1169 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
1170 + Integer.toHexString(id));
1171 }
1172
1173 /**
1174 * Start preloading of resource data using this Resources object. Only
1175 * for use by the zygote process for loading common system resources.
1176 * {@hide}
1177 */
1178 public final void startPreloading() {
1179 synchronized (sSync) {
1180 if (sPreloaded) {
1181 throw new IllegalStateException("Resources already preloaded");
1182 }
1183 sPreloaded = true;
1184 mPreloading = true;
1185 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1186 updateConfiguration(null, null, null);
Makoto Onuki1480b672017-07-14 08:42:50 -07001187
1188 if (TRACE_FOR_DETAILED_PRELOAD) {
1189 mPreloadTracingPreloadStartTime = SystemClock.uptimeMillis();
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001190 mPreloadTracingStartBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
1191 mPreloadTracingStartBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
Makoto Onuki1480b672017-07-14 08:42:50 -07001192 Log.d(TAG_PRELOAD, "Preload starting");
1193 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001194 }
1195 }
1196
1197 /**
1198 * Called by zygote when it is done preloading resources, to change back
1199 * to normal Resources operation.
1200 */
1201 void finishPreloading() {
1202 if (mPreloading) {
Makoto Onuki1480b672017-07-14 08:42:50 -07001203 if (TRACE_FOR_DETAILED_PRELOAD) {
1204 final long time = SystemClock.uptimeMillis() - mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001205 final long size =
1206 Bitmap.sPreloadTracingTotalBitmapsSize - mPreloadTracingStartBitmapSize;
1207 final long count = Bitmap.sPreloadTracingNumInstantiatedBitmaps
1208 - mPreloadTracingStartBitmapCount;
1209 Log.d(TAG_PRELOAD, "Preload finished, "
1210 + count + " bitmaps of " + size + " bytes in " + time + " ms");
Makoto Onuki1480b672017-07-14 08:42:50 -07001211 }
1212
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001213 mPreloading = false;
1214 flushLayoutCache();
1215 }
1216 }
1217
1218 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1219 return sPreloadedDrawables[0];
1220 }
1221
1222 ThemeImpl newThemeImpl() {
1223 return new ThemeImpl();
1224 }
1225
Adam Lesinski082614c2016-03-04 14:33:47 -08001226 /**
1227 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1228 */
1229 ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1230 ThemeImpl impl = new ThemeImpl();
1231 impl.mKey.setTo(key);
1232 impl.rebase();
1233 return impl;
1234 }
1235
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001236 public class ThemeImpl {
1237 /**
1238 * Unique key for the series of styles applied to this theme.
1239 */
1240 private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1241
1242 @SuppressWarnings("hiding")
1243 private final AssetManager mAssets;
1244 private final long mTheme;
1245
1246 /**
1247 * Resource identifier for the theme.
1248 */
1249 private int mThemeResId = 0;
1250
1251 /*package*/ ThemeImpl() {
1252 mAssets = ResourcesImpl.this.mAssets;
1253 mTheme = mAssets.createTheme();
1254 }
1255
1256 @Override
1257 protected void finalize() throws Throwable {
1258 super.finalize();
1259 mAssets.releaseTheme(mTheme);
1260 }
1261
1262 /*package*/ Resources.ThemeKey getKey() {
1263 return mKey;
1264 }
1265
1266 /*package*/ long getNativeTheme() {
1267 return mTheme;
1268 }
1269
1270 /*package*/ int getAppliedStyleResId() {
1271 return mThemeResId;
1272 }
1273
1274 void applyStyle(int resId, boolean force) {
1275 synchronized (mKey) {
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001276 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001277 mThemeResId = resId;
1278 mKey.append(resId, force);
1279 }
1280 }
1281
1282 void setTo(ThemeImpl other) {
1283 synchronized (mKey) {
1284 synchronized (other.mKey) {
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001285 AssetManager.nativeThemeCopy(mTheme, other.mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001286
1287 mThemeResId = other.mThemeResId;
1288 mKey.setTo(other.getKey());
1289 }
1290 }
1291 }
1292
1293 @NonNull
1294 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001295 AttributeSet set,
1296 @StyleableRes int[] attrs,
1297 @AttrRes int defStyleAttr,
1298 @StyleRes int defStyleRes) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001299 synchronized (mKey) {
1300 final int len = attrs.length;
1301 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1302
1303 // XXX note that for now we only work with compiled XML files.
1304 // To support generic XML files we will need to manually parse
1305 // out the attributes from the XML file (applying type information
1306 // contained in the resources and such).
1307 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001308 mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
1309 array.mDataAddress, array.mIndicesAddress);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001310 array.mTheme = wrapper;
1311 array.mXml = parser;
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001312 return array;
1313 }
1314 }
1315
1316 @NonNull
1317 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001318 @NonNull int[] values,
1319 @NonNull int[] attrs) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001320 synchronized (mKey) {
1321 final int len = attrs.length;
1322 if (values == null || len != values.length) {
1323 throw new IllegalArgumentException(
1324 "Base attribute values must the same length as attrs");
1325 }
1326
1327 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001328 mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001329 array.mTheme = wrapper;
1330 array.mXml = null;
1331 return array;
1332 }
1333 }
1334
1335 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1336 synchronized (mKey) {
1337 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1338 }
1339 }
1340
1341 int[] getAllAttributes() {
1342 return mAssets.getStyleAttributes(getAppliedStyleResId());
1343 }
1344
Alan Viveretteac85f902016-03-11 15:15:51 -05001345 @Config int getChangingConfigurations() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001346 synchronized (mKey) {
Alan Viverette9ad386b2017-01-26 14:00:20 -05001347 final @NativeConfig int nativeChangingConfig =
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001348 AssetManager.nativeThemeGetChangingConfigurations(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001349 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1350 }
1351 }
1352
1353 public void dump(int priority, String tag, String prefix) {
1354 synchronized (mKey) {
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001355 mAssets.dumpTheme(mTheme, priority, tag, prefix);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001356 }
1357 }
1358
1359 String[] getTheme() {
1360 synchronized (mKey) {
1361 final int N = mKey.mCount;
1362 final String[] themes = new String[N * 2];
1363 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1364 final int resId = mKey.mResId[j];
1365 final boolean forced = mKey.mForce[j];
1366 try {
1367 themes[i] = getResourceName(resId);
1368 } catch (NotFoundException e) {
1369 themes[i] = Integer.toHexString(i);
1370 }
1371 themes[i + 1] = forced ? "forced" : "not forced";
1372 }
1373 return themes;
1374 }
1375 }
1376
1377 /**
1378 * Rebases the theme against the parent Resource object's current
1379 * configuration by re-applying the styles passed to
1380 * {@link #applyStyle(int, boolean)}.
1381 */
1382 void rebase() {
1383 synchronized (mKey) {
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001384 AssetManager.nativeThemeClear(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001385
1386 // Reapply the same styles in the same order.
1387 for (int i = 0; i < mKey.mCount; i++) {
1388 final int resId = mKey.mResId[i];
1389 final boolean force = mKey.mForce[i];
Adam Lesinskidcb3c652017-01-23 12:58:11 -08001390 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001391 }
1392 }
1393 }
1394 }
Sunny Goyal99b25d22017-11-01 11:58:13 -07001395
1396 private static class LookupStack {
1397
1398 // Pick a reasonable default size for the array, it is grown as needed.
1399 private int[] mIds = new int[4];
1400 private int mSize = 0;
1401
1402 public void push(int id) {
1403 mIds = GrowingArrayUtils.append(mIds, mSize, id);
1404 mSize++;
1405 }
1406
1407 public boolean contains(int id) {
1408 for (int i = 0; i < mSize; i++) {
1409 if (mIds[i] == id) {
1410 return true;
1411 }
1412 }
1413 return false;
1414 }
1415
1416 public void pop() {
1417 mSize--;
1418 }
1419 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001420}