blob: 00e4841044cdcfb8357b2bf9dfc3c2ab880d411c [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;
Leon Scroggins III40c59fd2018-01-31 20:39:37 -050030import android.content.res.AssetManager.AssetInputStream;
Alan Viverette9ad386b2017-01-26 14:00:20 -050031import android.content.res.Configuration.NativeConfig;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080032import android.content.res.Resources.NotFoundException;
Makoto Onuki1480b672017-07-14 08:42:50 -070033import android.graphics.Bitmap;
Leon Scroggins III40c59fd2018-01-31 20:39:37 -050034import android.graphics.ImageDecoder;
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000035import android.graphics.Typeface;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080036import android.graphics.drawable.ColorDrawable;
37import android.graphics.drawable.Drawable;
ztenghuiee7e8f12017-05-16 15:30:50 -070038import android.graphics.drawable.DrawableContainer;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080039import android.icu.text.PluralRules;
40import android.os.Build;
Yohei Yukawa23cbe852016-05-17 16:42:58 -070041import android.os.LocaleList;
Makoto Onuki1480b672017-07-14 08:42:50 -070042import android.os.SystemClock;
43import android.os.SystemProperties;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080044import android.os.Trace;
45import android.util.AttributeSet;
46import android.util.DisplayMetrics;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080047import android.util.Log;
48import android.util.LongSparseArray;
49import android.util.Slog;
50import android.util.TypedValue;
51import android.util.Xml;
Adam Lesinski4ece3d62016-06-16 18:05:41 -070052import android.view.DisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080053
Sunny Goyal99b25d22017-11-01 11:58:13 -070054import com.android.internal.util.GrowingArrayUtils;
55
Makoto Onuki1480b672017-07-14 08:42:50 -070056import org.xmlpull.v1.XmlPullParser;
57import org.xmlpull.v1.XmlPullParserException;
58
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000059import java.io.IOException;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080060import java.io.InputStream;
61import java.util.Arrays;
62import java.util.Locale;
63
64/**
Adam Lesinski082614c2016-03-04 14:33:47 -080065 * The implementation of Resource access. This class contains the AssetManager and all caches
66 * associated with it.
67 *
68 * {@link Resources} is just a thing wrapper around this class. When a configuration change
69 * occurs, clients can retain the same {@link Resources} reference because the underlying
70 * {@link ResourcesImpl} object will be updated or re-created.
71 *
Adam Lesinskifb302cc2016-02-29 16:50:38 -080072 * @hide
73 */
74public class ResourcesImpl {
75 static final String TAG = "Resources";
76
77 private static final boolean DEBUG_LOAD = false;
78 private static final boolean DEBUG_CONFIG = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080079
Makoto Onuki1480b672017-07-14 08:42:50 -070080 static final String TAG_PRELOAD = TAG + ".preload";
81
82 private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
83 private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
84
85 public static final boolean TRACE_FOR_DETAILED_PRELOAD =
86 SystemProperties.getBoolean("debug.trace_resource_preload", false);
87
88 /** Used only when TRACE_FOR_DETAILED_PRELOAD is true. */
89 private static int sPreloadTracingNumLoadedDrawables;
90 private long mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -070091 private long mPreloadTracingStartBitmapSize;
92 private long mPreloadTracingStartBitmapCount;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080093
94 private static final int ID_OTHER = 0x01000004;
95
96 private static final Object sSync = new Object();
97
98 private static boolean sPreloaded;
99 private boolean mPreloading;
100
101 // Information about preloaded resources. Note that they are not
102 // protected by a lock, because while preloading in zygote we are all
103 // single-threaded, and after that these are immutable.
104 private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
105 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
106 = new LongSparseArray<>();
107 private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
108 sPreloadedComplexColors = new LongSparseArray<>();
109
110 /** Lock object used to protect access to caches and configuration. */
111 private final Object mAccessLock = new Object();
112
113 // These are protected by mAccessLock.
114 private final Configuration mTmpConfig = new Configuration();
115 private final DrawableCache mDrawableCache = new DrawableCache();
116 private final DrawableCache mColorDrawableCache = new DrawableCache();
117 private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
118 new ConfigurationBoundResourceCache<>();
119 private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
120 new ConfigurationBoundResourceCache<>();
121 private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
122 new ConfigurationBoundResourceCache<>();
123
Sunny Goyal99b25d22017-11-01 11:58:13 -0700124 // A stack of all the resourceIds already referenced when parsing a resource. This is used to
125 // detect circular references in the xml.
126 // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
127 // calls to ResourcesImpl
128 private final ThreadLocal<LookupStack> mLookupStack =
129 ThreadLocal.withInitial(() -> new LookupStack());
130
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800131 /** Size of the cyclical cache used to map XML files to blocks. */
132 private static final int XML_BLOCK_CACHE_SIZE = 4;
133
134 // Cyclical cache used for recently-accessed XML files.
135 private int mLastCachedXmlBlockIndex = -1;
136 private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
137 private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
138 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
139
140
141 final AssetManager mAssets;
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700142 private final DisplayMetrics mMetrics = new DisplayMetrics();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700143 private final DisplayAdjustments mDisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800144
145 private PluralRules mPluralRule;
146
147 private final Configuration mConfiguration = new Configuration();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800148
149 static {
150 sPreloadedDrawables = new LongSparseArray[2];
151 sPreloadedDrawables[0] = new LongSparseArray<>();
152 sPreloadedDrawables[1] = new LongSparseArray<>();
153 }
154
155 /**
156 * Creates a new ResourcesImpl object with CompatibilityInfo.
157 *
158 * @param assets Previously created AssetManager.
159 * @param metrics Current display metrics to consider when
160 * selecting/computing resource values.
161 * @param config Desired device configuration to consider when
162 * selecting/computing resource values (optional).
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700163 * @param displayAdjustments this resource's Display override and compatibility info.
164 * Must not be null.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800165 */
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700166 public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700167 @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800168 mAssets = assets;
169 mMetrics.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700170 mDisplayAdjustments = displayAdjustments;
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700171 mConfiguration.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700172 updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
Adam Lesinskif7d01dd2018-01-25 15:38:58 -0800173 mAssets.ensureStringBlocks();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800174 }
175
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700176 public DisplayAdjustments getDisplayAdjustments() {
177 return mDisplayAdjustments;
178 }
179
Adam Lesinski082614c2016-03-04 14:33:47 -0800180 public AssetManager getAssets() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800181 return mAssets;
182 }
183
184 DisplayMetrics getDisplayMetrics() {
185 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
186 + "x" + mMetrics.heightPixels + " " + mMetrics.density);
187 return mMetrics;
188 }
189
190 Configuration getConfiguration() {
191 return mConfiguration;
192 }
193
194 Configuration[] getSizeConfigurations() {
195 return mAssets.getSizeConfigurations();
196 }
197
198 CompatibilityInfo getCompatibilityInfo() {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700199 return mDisplayAdjustments.getCompatibilityInfo();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800200 }
201
202 private PluralRules getPluralRule() {
203 synchronized (sSync) {
204 if (mPluralRule == null) {
205 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
206 }
207 return mPluralRule;
208 }
209 }
210
211 void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
212 throws NotFoundException {
213 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
214 if (found) {
215 return;
216 }
217 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
218 }
219
220 void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
Adam Lesinski082614c2016-03-04 14:33:47 -0800221 boolean resolveRefs) throws NotFoundException {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800222 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
223 if (found) {
224 return;
225 }
226 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
227 }
228
229 void getValue(String name, TypedValue outValue, boolean resolveRefs)
230 throws NotFoundException {
231 int id = getIdentifier(name, "string", null);
232 if (id != 0) {
233 getValue(id, outValue, resolveRefs);
234 return;
235 }
236 throw new NotFoundException("String resource name " + name);
237 }
238
239 int getIdentifier(String name, String defType, String defPackage) {
240 if (name == null) {
241 throw new NullPointerException("name is null");
242 }
243 try {
244 return Integer.parseInt(name);
245 } catch (Exception e) {
246 // Ignore
247 }
248 return mAssets.getResourceIdentifier(name, defType, defPackage);
249 }
250
251 @NonNull
252 String getResourceName(@AnyRes int resid) throws NotFoundException {
253 String str = mAssets.getResourceName(resid);
254 if (str != null) return str;
255 throw new NotFoundException("Unable to find resource ID #0x"
256 + Integer.toHexString(resid));
257 }
258
259 @NonNull
260 String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
261 String str = mAssets.getResourcePackageName(resid);
262 if (str != null) return str;
263 throw new NotFoundException("Unable to find resource ID #0x"
264 + Integer.toHexString(resid));
265 }
266
267 @NonNull
268 String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
269 String str = mAssets.getResourceTypeName(resid);
270 if (str != null) return str;
271 throw new NotFoundException("Unable to find resource ID #0x"
272 + Integer.toHexString(resid));
273 }
274
275 @NonNull
276 String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
277 String str = mAssets.getResourceEntryName(resid);
278 if (str != null) return str;
279 throw new NotFoundException("Unable to find resource ID #0x"
280 + Integer.toHexString(resid));
281 }
282
283 @NonNull
284 CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
285 PluralRules rule = getPluralRule();
286 CharSequence res = mAssets.getResourceBagText(id,
287 attrForQuantityCode(rule.select(quantity)));
288 if (res != null) {
289 return res;
290 }
291 res = mAssets.getResourceBagText(id, ID_OTHER);
292 if (res != null) {
293 return res;
294 }
295 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
296 + " quantity=" + quantity
297 + " item=" + rule.select(quantity));
298 }
299
300 private static int attrForQuantityCode(String quantityCode) {
301 switch (quantityCode) {
302 case PluralRules.KEYWORD_ZERO: return 0x01000005;
303 case PluralRules.KEYWORD_ONE: return 0x01000006;
304 case PluralRules.KEYWORD_TWO: return 0x01000007;
305 case PluralRules.KEYWORD_FEW: return 0x01000008;
306 case PluralRules.KEYWORD_MANY: return 0x01000009;
307 default: return ID_OTHER;
308 }
309 }
310
311 @NonNull
312 AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
313 throws NotFoundException {
314 getValue(id, tempValue, true);
315 try {
316 return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
317 } catch (Exception e) {
318 throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
319 + "resource ID #0x" + Integer.toHexString(id), e);
320 }
321 }
322
323 @NonNull
324 InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
325 getValue(id, value, true);
326 try {
327 return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
328 AssetManager.ACCESS_STREAMING);
329 } catch (Exception e) {
Christopher Tatef135b272016-05-27 17:10:30 -0700330 // Note: value.string might be null
331 NotFoundException rnf = new NotFoundException("File "
332 + (value.string == null ? "(null)" : value.string.toString())
333 + " from drawable resource ID #0x" + Integer.toHexString(id));
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800334 rnf.initCause(e);
335 throw rnf;
336 }
337 }
338
339 ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
340 return mAnimatorCache;
341 }
342
343 ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
344 return mStateListAnimatorCache;
345 }
346
Adam Lesinski082614c2016-03-04 14:33:47 -0800347 public void updateConfiguration(Configuration config, DisplayMetrics metrics,
348 CompatibilityInfo compat) {
Adam Lesinski991357f2016-05-10 14:00:03 -0700349 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
350 try {
351 synchronized (mAccessLock) {
352 if (false) {
353 Slog.i(TAG, "**** Updating config of " + this + ": old config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700354 + mConfiguration + " old compat is "
355 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700356 Slog.i(TAG, "**** Updating config of " + this + ": new config is "
357 + config + " new compat is " + compat);
358 }
359 if (compat != null) {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700360 mDisplayAdjustments.setCompatibilityInfo(compat);
Adam Lesinski991357f2016-05-10 14:00:03 -0700361 }
362 if (metrics != null) {
363 mMetrics.setTo(metrics);
364 }
365 // NOTE: We should re-arrange this code to create a Display
366 // with the CompatibilityInfo that is used everywhere we deal
367 // with the display in relation to this app, rather than
368 // doing the conversion here. This impl should be okay because
369 // we make sure to return a compatible display in the places
370 // where there are public APIs to retrieve the display... but
Adam Lesinskib61e4052016-05-19 18:23:05 -0700371 // it would be cleaner and more maintainable to just be
Adam Lesinski991357f2016-05-10 14:00:03 -0700372 // consistently dealing with a compatible display everywhere in
373 // the framework.
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700374 mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800375
Adam Lesinski991357f2016-05-10 14:00:03 -0700376 final @Config int configChanges = calcConfigChanges(config);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800377
Adam Lesinskib61e4052016-05-19 18:23:05 -0700378 // If even after the update there are no Locales set, grab the default locales.
Adam Lesinski991357f2016-05-10 14:00:03 -0700379 LocaleList locales = mConfiguration.getLocales();
380 if (locales.isEmpty()) {
Adam Lesinskib61e4052016-05-19 18:23:05 -0700381 locales = LocaleList.getDefault();
Adam Lesinski991357f2016-05-10 14:00:03 -0700382 mConfiguration.setLocales(locales);
383 }
Adam Lesinskib61e4052016-05-19 18:23:05 -0700384
385 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
386 if (locales.size() > 1) {
387 // The LocaleList has changed. We must query the AssetManager's available
388 // Locales and figure out the best matching Locale in the new LocaleList.
389 String[] availableLocales = mAssets.getNonSystemLocales();
390 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
391 // No app defined locales, so grab the system locales.
392 availableLocales = mAssets.getLocales();
393 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
394 availableLocales = null;
395 }
396 }
397
398 if (availableLocales != null) {
399 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
400 availableLocales);
401 if (bestLocale != null && bestLocale != locales.get(0)) {
402 mConfiguration.setLocales(new LocaleList(bestLocale, locales));
403 }
404 }
405 }
406 }
407
Adam Lesinski991357f2016-05-10 14:00:03 -0700408 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
409 mMetrics.densityDpi = mConfiguration.densityDpi;
410 mMetrics.density =
411 mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
412 }
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700413
414 // Protect against an unset fontScale.
415 mMetrics.scaledDensity = mMetrics.density *
416 (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
Adam Lesinski991357f2016-05-10 14:00:03 -0700417
418 final int width, height;
419 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
420 width = mMetrics.widthPixels;
421 height = mMetrics.heightPixels;
422 } else {
423 //noinspection SuspiciousNameCombination
424 width = mMetrics.heightPixels;
425 //noinspection SuspiciousNameCombination
426 height = mMetrics.widthPixels;
427 }
428
429 final int keyboardHidden;
430 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
431 && mConfiguration.hardKeyboardHidden
432 == Configuration.HARDKEYBOARDHIDDEN_YES) {
433 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
434 } else {
435 keyboardHidden = mConfiguration.keyboardHidden;
436 }
437
438 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
Adam Lesinskib61e4052016-05-19 18:23:05 -0700439 adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
Adam Lesinski991357f2016-05-10 14:00:03 -0700440 mConfiguration.orientation,
441 mConfiguration.touchscreen,
442 mConfiguration.densityDpi, mConfiguration.keyboard,
443 keyboardHidden, mConfiguration.navigation, width, height,
444 mConfiguration.smallestScreenWidthDp,
445 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
446 mConfiguration.screenLayout, mConfiguration.uiMode,
Romain Guy408afbf2017-01-25 10:23:03 -0800447 mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
Adam Lesinski991357f2016-05-10 14:00:03 -0700448
449 if (DEBUG_CONFIG) {
450 Slog.i(TAG, "**** Updating config of " + this + ": final config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700451 + mConfiguration + " final compat is "
452 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700453 }
454
455 mDrawableCache.onConfigurationChange(configChanges);
456 mColorDrawableCache.onConfigurationChange(configChanges);
457 mComplexColorCache.onConfigurationChange(configChanges);
458 mAnimatorCache.onConfigurationChange(configChanges);
459 mStateListAnimatorCache.onConfigurationChange(configChanges);
460
461 flushLayoutCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800462 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700463 synchronized (sSync) {
464 if (mPluralRule != null) {
465 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
466 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800467 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700468 } finally {
469 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800470 }
471 }
472
473 /**
Alan Viveretteac85f902016-03-11 15:15:51 -0500474 * Applies the new configuration, returning a bitmask of the changes
475 * between the old and new configurations.
476 *
477 * @param config the new configuration
478 * @return bitmask of config changes
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800479 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500480 public @Config int calcConfigChanges(@Nullable Configuration config) {
481 if (config == null) {
482 // If there is no configuration, assume all flags have changed.
483 return 0xFFFFFFFF;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800484 }
Alan Viveretteac85f902016-03-11 15:15:51 -0500485
486 mTmpConfig.setTo(config);
487 int density = config.densityDpi;
488 if (density == Configuration.DENSITY_DPI_UNDEFINED) {
489 density = mMetrics.noncompatDensityDpi;
490 }
491
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700492 mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
Alan Viveretteac85f902016-03-11 15:15:51 -0500493
494 if (mTmpConfig.getLocales().isEmpty()) {
495 mTmpConfig.setLocales(LocaleList.getDefault());
496 }
497 return mConfiguration.updateFrom(mTmpConfig);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800498 }
499
500 /**
501 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
502 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
503 *
504 * All released versions of android prior to "L" used the deprecated language
505 * tags, so we will need to support them for backwards compatibility.
506 *
507 * Note that this conversion needs to take place *after* the call to
508 * {@code toLanguageTag} because that will convert all the deprecated codes to
509 * the new ones, even if they're set manually.
510 */
511 private static String adjustLanguageTag(String languageTag) {
512 final int separator = languageTag.indexOf('-');
513 final String language;
514 final String remainder;
515
516 if (separator == -1) {
517 language = languageTag;
518 remainder = "";
519 } else {
520 language = languageTag.substring(0, separator);
521 remainder = languageTag.substring(separator);
522 }
523
524 return Locale.adjustLanguageCode(language) + remainder;
525 }
526
527 /**
528 * Call this to remove all cached loaded layout resources from the
529 * Resources object. Only intended for use with performance testing
530 * tools.
531 */
532 public void flushLayoutCache() {
533 synchronized (mCachedXmlBlocks) {
534 Arrays.fill(mCachedXmlBlockCookies, 0);
535 Arrays.fill(mCachedXmlBlockFiles, null);
536
537 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
538 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
539 final XmlBlock oldBlock = cachedXmlBlocks[i];
540 if (oldBlock != null) {
541 oldBlock.close();
542 }
543 }
544 Arrays.fill(cachedXmlBlocks, null);
545 }
546 }
547
548 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700549 Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
550 int density, @Nullable Resources.Theme theme)
551 throws NotFoundException {
552 // If the drawable's XML lives in our current density qualifier,
553 // it's okay to use a scaled version from the cache. Otherwise, we
554 // need to actually load the drawable from XML.
555 final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
556
557 // Pretend the requested density is actually the display density. If
558 // the drawable returned is not the requested density, then force it
559 // to be scaled later by dividing its density by the ratio of
560 // requested density to actual device density. Drawables that have
561 // undefined density or no density don't need to be handled here.
562 if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
563 if (value.density == density) {
564 value.density = mMetrics.densityDpi;
565 } else {
566 value.density = (value.density * mMetrics.densityDpi) / density;
567 }
568 }
569
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800570 try {
571 if (TRACE_FOR_PRELOAD) {
572 // Log only framework resources
573 if ((id >>> 24) == 0x1) {
574 final String name = getResourceName(id);
575 if (name != null) {
576 Log.d("PreloadDrawable", name);
577 }
578 }
579 }
580
581 final boolean isColorDrawable;
582 final DrawableCache caches;
583 final long key;
584 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
585 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
586 isColorDrawable = true;
587 caches = mColorDrawableCache;
588 key = value.data;
589 } else {
590 isColorDrawable = false;
591 caches = mDrawableCache;
592 key = (((long) value.assetCookie) << 32) | value.data;
593 }
594
595 // First, check whether we have a cached version of this drawable
596 // that was inflated against the specified theme. Skip the cache if
597 // we're currently preloading or we're not using the cache.
598 if (!mPreloading && useCache) {
599 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
600 if (cachedDrawable != null) {
Alan Viverette58857c82016-10-29 00:47:45 +0100601 cachedDrawable.setChangingConfigurations(value.changingConfigurations);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800602 return cachedDrawable;
603 }
604 }
605
606 // Next, check preloaded drawables. Preloaded drawables may contain
607 // unresolved theme attributes.
608 final Drawable.ConstantState cs;
609 if (isColorDrawable) {
610 cs = sPreloadedColorDrawables.get(key);
611 } else {
612 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
613 }
614
615 Drawable dr;
ztenghuiee7e8f12017-05-16 15:30:50 -0700616 boolean needsNewDrawableAfterCache = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800617 if (cs != null) {
Makoto Onuki1480b672017-07-14 08:42:50 -0700618 if (TRACE_FOR_DETAILED_PRELOAD) {
619 // Log only framework resources
620 if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
621 final String name = getResourceName(id);
622 if (name != null) {
623 Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
624 + Integer.toHexString(id) + " " + name);
625 }
626 }
627 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800628 dr = cs.newDrawable(wrapper);
629 } else if (isColorDrawable) {
630 dr = new ColorDrawable(value.data);
631 } else {
Adam Lesinski50954d22017-04-14 18:41:52 -0700632 dr = loadDrawableForCookie(wrapper, value, id, density, null);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800633 }
ztenghuiee7e8f12017-05-16 15:30:50 -0700634 // DrawableContainer' constant state has drawables instances. In order to leave the
635 // constant state intact in the cache, we need to create a new DrawableContainer after
636 // added to cache.
637 if (dr instanceof DrawableContainer) {
638 needsNewDrawableAfterCache = true;
639 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800640
641 // Determine if the drawable has unresolved theme attributes. If it
642 // does, we'll need to apply a theme and store it in a theme-specific
643 // cache.
644 final boolean canApplyTheme = dr != null && dr.canApplyTheme();
645 if (canApplyTheme && theme != null) {
646 dr = dr.mutate();
647 dr.applyTheme(theme);
648 dr.clearMutated();
649 }
650
651 // If we were able to obtain a drawable, store it in the appropriate
652 // cache: preload, not themed, null theme, or theme-specific. Don't
653 // pollute the cache with drawables loaded from a foreign density.
Alan Viverette58857c82016-10-29 00:47:45 +0100654 if (dr != null) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800655 dr.setChangingConfigurations(value.changingConfigurations);
Alan Viverette58857c82016-10-29 00:47:45 +0100656 if (useCache) {
657 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
ztenghuiee7e8f12017-05-16 15:30:50 -0700658 if (needsNewDrawableAfterCache) {
659 Drawable.ConstantState state = dr.getConstantState();
660 if (state != null) {
661 dr = state.newDrawable(wrapper);
662 }
663 }
Alan Viverette58857c82016-10-29 00:47:45 +0100664 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800665 }
666
667 return dr;
668 } catch (Exception e) {
669 String name;
670 try {
671 name = getResourceName(id);
672 } catch (NotFoundException e2) {
673 name = "(missing name)";
674 }
675
676 // The target drawable might fail to load for any number of
677 // reasons, but we always want to include the resource name.
678 // Since the client already expects this method to throw a
679 // NotFoundException, just throw one of those.
680 final NotFoundException nfe = new NotFoundException("Drawable " + name
681 + " with resource ID #0x" + Integer.toHexString(id), e);
682 nfe.setStackTrace(new StackTraceElement[0]);
683 throw nfe;
684 }
685 }
686
687 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Adam Lesinski082614c2016-03-04 14:33:47 -0800688 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800689 final Drawable.ConstantState cs = dr.getConstantState();
690 if (cs == null) {
691 return;
692 }
693
694 if (mPreloading) {
695 final int changingConfigs = cs.getChangingConfigurations();
696 if (isColorDrawable) {
697 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
698 sPreloadedColorDrawables.put(key, cs);
699 }
700 } else {
701 if (verifyPreloadConfig(
Alan Viverettea8a66cc2017-03-20 15:00:51 -0400702 changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
703 if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800704 // If this resource does not vary based on layout direction,
705 // we can put it in all of the preload maps.
706 sPreloadedDrawables[0].put(key, cs);
707 sPreloadedDrawables[1].put(key, cs);
708 } else {
709 // Otherwise, only in the layout dir we loaded it for.
710 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
711 }
712 }
713 }
714 } else {
715 synchronized (mAccessLock) {
716 caches.put(key, theme, cs, usesTheme);
717 }
718 }
719 }
720
Alan Viveretteac85f902016-03-11 15:15:51 -0500721 private boolean verifyPreloadConfig(@Config int changingConfigurations,
722 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800723 // We allow preloading of resources even if they vary by font scale (which
724 // doesn't impact resource selection) or density (which we handle specially by
725 // simply turning off all preloading), as well as any other configs specified
726 // by the caller.
727 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
728 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
729 String resName;
730 try {
731 resName = getResourceName(resourceId);
732 } catch (NotFoundException e) {
733 resName = "?";
734 }
735 // This should never happen in production, so we should log a
736 // warning even if we're not debugging.
737 Log.w(TAG, "Preloaded " + name + " resource #0x"
738 + Integer.toHexString(resourceId)
739 + " (" + resName + ") that varies with configuration!!");
740 return false;
741 }
742 if (TRACE_FOR_PRELOAD) {
743 String resName;
744 try {
745 resName = getResourceName(resourceId);
746 } catch (NotFoundException e) {
747 resName = "?";
748 }
749 Log.w(TAG, "Preloading " + name + " resource #0x"
750 + Integer.toHexString(resourceId)
751 + " (" + resName + ")");
752 }
753 return true;
754 }
755
756 /**
757 * Loads a drawable from XML or resources stream.
758 */
Adam Lesinski50954d22017-04-14 18:41:52 -0700759 private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
760 int id, int density, @Nullable Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800761 if (value.string == null) {
762 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
763 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
764 }
765
766 final String file = value.string.toString();
767
768 if (TRACE_FOR_MISS_PRELOAD) {
769 // Log only framework resources
770 if ((id >>> 24) == 0x1) {
771 final String name = getResourceName(id);
772 if (name != null) {
773 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
774 + ": " + name + " at " + file);
775 }
776 }
777 }
778
Makoto Onuki1480b672017-07-14 08:42:50 -0700779 // For prelaod tracing.
780 long startTime = 0;
781 int startBitmapCount = 0;
782 long startBitmapSize = 0;
783 int startDrwableCount = 0;
784 if (TRACE_FOR_DETAILED_PRELOAD) {
785 startTime = System.nanoTime();
786 startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
787 startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
788 startDrwableCount = sPreloadTracingNumLoadedDrawables;
789 }
790
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800791 if (DEBUG_LOAD) {
792 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
793 }
794
795 final Drawable dr;
796
797 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
Sunny Goyal99b25d22017-11-01 11:58:13 -0700798 LookupStack stack = mLookupStack.get();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800799 try {
Sunny Goyal99b25d22017-11-01 11:58:13 -0700800 // Perform a linear search to check if we have already referenced this resource before.
801 if (stack.contains(id)) {
802 throw new Exception("Recursive reference in drawable");
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800803 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700804 stack.push(id);
805 try {
806 if (file.endsWith(".xml")) {
807 final XmlResourceParser rp = loadXmlResourceParser(
808 file, id, value.assetCookie, "drawable");
809 dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
810 rp.close();
811 } else {
812 final InputStream is = mAssets.openNonAsset(
813 value.assetCookie, file, AssetManager.ACCESS_STREAMING);
Leon Scroggins III40c59fd2018-01-31 20:39:37 -0500814 AssetInputStream ais = (AssetInputStream) is;
815 // ImageDecoder will close the input stream.
816 ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
817 wrapper, value);
818 dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
819 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
820 });
Sunny Goyal99b25d22017-11-01 11:58:13 -0700821 }
822 } finally {
823 stack.pop();
824 }
Hyunyoung Songaff04c32017-10-10 10:35:59 -0700825 } catch (Exception | StackOverflowError e) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800826 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
827 final NotFoundException rnf = new NotFoundException(
828 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
829 rnf.initCause(e);
830 throw rnf;
831 }
832 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
833
Makoto Onuki1480b672017-07-14 08:42:50 -0700834 if (TRACE_FOR_DETAILED_PRELOAD) {
835 if (((id >>> 24) == 0x1)) {
836 final String name = getResourceName(id);
837 if (name != null) {
838 final long time = System.nanoTime() - startTime;
839 final int loadedBitmapCount =
840 Bitmap.sPreloadTracingNumInstantiatedBitmaps - startBitmapCount;
841 final long loadedBitmapSize =
842 Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
843 final int loadedDrawables =
844 sPreloadTracingNumLoadedDrawables - startDrwableCount;
845
846 sPreloadTracingNumLoadedDrawables++;
847
848 final boolean isRoot = (android.os.Process.myUid() == 0);
849
850 Log.d(TAG_PRELOAD,
851 (isRoot ? "Preloaded FW drawable #"
852 : "Loaded non-preloaded FW drawable #")
853 + Integer.toHexString(id)
854 + " " + name
855 + " " + file
856 + " " + dr.getClass().getCanonicalName()
857 + " #nested_drawables= " + loadedDrawables
858 + " #bitmaps= " + loadedBitmapCount
859 + " total_bitmap_size= " + loadedBitmapSize
860 + " in[us] " + (time / 1000));
861 }
862 }
863 }
864
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800865 return dr;
866 }
867
868 /**
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000869 * Loads a font from XML or resources stream.
870 */
871 @Nullable
Clara Bayarried00bfd2017-01-20 14:58:21 +0000872 public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000873 if (value.string == null) {
874 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
875 + Integer.toHexString(id) + ") is not a Font: " + value);
876 }
877
878 final String file = value.string.toString();
Seigo Nonaka2ea169a2017-05-15 16:25:11 -0700879 if (!file.startsWith("res/")) {
880 return null;
881 }
882
Clara Bayarrib12397e2017-01-27 11:02:48 +0000883 Typeface cached = Typeface.findFromCache(mAssets, file);
Clara Bayarried00bfd2017-01-20 14:58:21 +0000884 if (cached != null) {
885 return cached;
886 }
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000887
888 if (DEBUG_LOAD) {
889 Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
890 }
891
892 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
893 try {
Clara Bayarried00bfd2017-01-20 14:58:21 +0000894 if (file.endsWith("xml")) {
895 final XmlResourceParser rp = loadXmlResourceParser(
896 file, id, value.assetCookie, "font");
Seigo Nonakaac873c92017-03-07 15:34:53 -0800897 final FontResourcesParser.FamilyResourceEntry familyEntry =
898 FontResourcesParser.parse(rp, wrapper);
899 if (familyEntry == null) {
Seigo Nonakaac873c92017-03-07 15:34:53 -0800900 return null;
901 }
902 return Typeface.createFromResources(familyEntry, mAssets, file);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000903 }
Clara Bayarried00bfd2017-01-20 14:58:21 +0000904 return Typeface.createFromResources(mAssets, file, value.assetCookie);
905 } catch (XmlPullParserException e) {
906 Log.e(TAG, "Failed to parse xml resource " + file, e);
907 } catch (IOException e) {
908 Log.e(TAG, "Failed to read xml resource " + file, e);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000909 } finally {
910 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
911 }
912 return null;
913 }
914
915 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800916 * Given the value and id, we can get the XML filename as in value.data, based on that, we
917 * first try to load CSL from the cache. If not found, try to get from the constant state.
918 * Last, parse the XML and generate the CSL.
919 */
920 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
Adam Lesinski082614c2016-03-04 14:33:47 -0800921 TypedValue value, int id) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800922 final long key = (((long) value.assetCookie) << 32) | value.data;
923 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
924 ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
925 if (complexColor != null) {
926 return complexColor;
927 }
928
929 final android.content.res.ConstantState<ComplexColor> factory =
930 sPreloadedComplexColors.get(key);
931
932 if (factory != null) {
933 complexColor = factory.newInstance(wrapper, theme);
934 }
935 if (complexColor == null) {
936 complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
937 }
938
939 if (complexColor != null) {
Alan Viverette0b9295d2016-03-10 10:30:08 -0500940 complexColor.setBaseChangingConfigurations(value.changingConfigurations);
941
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800942 if (mPreloading) {
Alan Viverette0b9295d2016-03-10 10:30:08 -0500943 if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
944 0, value.resourceId, "color")) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800945 sPreloadedComplexColors.put(key, complexColor.getConstantState());
946 }
947 } else {
948 cache.put(key, theme, complexColor.getConstantState());
949 }
950 }
951 return complexColor;
952 }
953
954 @Nullable
955 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -0800956 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800957 if (TRACE_FOR_PRELOAD) {
958 // Log only framework resources
959 if ((id >>> 24) == 0x1) {
960 final String name = getResourceName(id);
961 if (name != null) android.util.Log.d("loadComplexColor", name);
962 }
963 }
964
965 final long key = (((long) value.assetCookie) << 32) | value.data;
966
967 // Handle inline color definitions.
968 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
969 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
970 return getColorStateListFromInt(value, key);
971 }
972
973 final String file = value.string.toString();
974
975 ComplexColor complexColor;
976 if (file.endsWith(".xml")) {
977 try {
978 complexColor = loadComplexColorFromName(wrapper, theme, value, id);
979 } catch (Exception e) {
980 final NotFoundException rnf = new NotFoundException(
981 "File " + file + " from complex color resource ID #0x"
982 + Integer.toHexString(id));
983 rnf.initCause(e);
984 throw rnf;
985 }
986 } else {
987 throw new NotFoundException(
988 "File " + file + " from drawable resource ID #0x"
989 + Integer.toHexString(id) + ": .xml extension required");
990 }
991
992 return complexColor;
993 }
994
995 @Nullable
996 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -0800997 Resources.Theme theme)
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800998 throws NotFoundException {
999 if (TRACE_FOR_PRELOAD) {
1000 // Log only framework resources
1001 if ((id >>> 24) == 0x1) {
1002 final String name = getResourceName(id);
1003 if (name != null) android.util.Log.d("PreloadColorStateList", name);
1004 }
1005 }
1006
1007 final long key = (((long) value.assetCookie) << 32) | value.data;
1008
1009 // Handle inline color definitions.
1010 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
1011 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1012 return getColorStateListFromInt(value, key);
1013 }
1014
1015 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1016 if (complexColor != null && complexColor instanceof ColorStateList) {
1017 return (ColorStateList) complexColor;
1018 }
1019
1020 throw new NotFoundException(
1021 "Can't find ColorStateList from drawable resource ID #0x"
1022 + Integer.toHexString(id));
1023 }
1024
1025 @NonNull
1026 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
1027 ColorStateList csl;
1028 final android.content.res.ConstantState<ComplexColor> factory =
1029 sPreloadedComplexColors.get(key);
1030 if (factory != null) {
1031 return (ColorStateList) factory.newInstance();
1032 }
1033
1034 csl = ColorStateList.valueOf(value.data);
1035
1036 if (mPreloading) {
1037 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
1038 "color")) {
1039 sPreloadedComplexColors.put(key, csl.getConstantState());
1040 }
1041 }
1042
1043 return csl;
1044 }
1045
1046 /**
1047 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
1048 * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
1049 *
1050 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
1051 * and selector tag.
1052 *
1053 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
1054 */
1055 @Nullable
1056 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001057 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001058 if (value.string == null) {
1059 throw new UnsupportedOperationException(
1060 "Can't convert to ComplexColor: type=0x" + value.type);
1061 }
1062
1063 final String file = value.string.toString();
1064
1065 if (TRACE_FOR_MISS_PRELOAD) {
1066 // Log only framework resources
1067 if ((id >>> 24) == 0x1) {
1068 final String name = getResourceName(id);
1069 if (name != null) {
1070 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
1071 + ": " + name + " at " + file);
1072 }
1073 }
1074 }
1075
1076 if (DEBUG_LOAD) {
1077 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
1078 }
1079
1080 ComplexColor complexColor = null;
1081
1082 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
1083 if (file.endsWith(".xml")) {
1084 try {
1085 final XmlResourceParser parser = loadXmlResourceParser(
1086 file, id, value.assetCookie, "ComplexColor");
1087
1088 final AttributeSet attrs = Xml.asAttributeSet(parser);
1089 int type;
1090 while ((type = parser.next()) != XmlPullParser.START_TAG
1091 && type != XmlPullParser.END_DOCUMENT) {
1092 // Seek parser to start tag.
1093 }
1094 if (type != XmlPullParser.START_TAG) {
1095 throw new XmlPullParserException("No start tag found");
1096 }
1097
1098 final String name = parser.getName();
1099 if (name.equals("gradient")) {
1100 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
1101 } else if (name.equals("selector")) {
1102 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
1103 }
1104 parser.close();
1105 } catch (Exception e) {
1106 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1107 final NotFoundException rnf = new NotFoundException(
1108 "File " + file + " from ComplexColor resource ID #0x"
1109 + Integer.toHexString(id));
1110 rnf.initCause(e);
1111 throw rnf;
1112 }
1113 } else {
1114 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1115 throw new NotFoundException(
1116 "File " + file + " from drawable resource ID #0x"
1117 + Integer.toHexString(id) + ": .xml extension required");
1118 }
1119 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1120
1121 return complexColor;
1122 }
1123
1124 /**
1125 * Loads an XML parser for the specified file.
1126 *
1127 * @param file the path for the XML file to parse
1128 * @param id the resource identifier for the file
1129 * @param assetCookie the asset cookie for the file
1130 * @param type the type of resource (used for logging)
1131 * @return a parser for the specified XML file
1132 * @throws NotFoundException if the file could not be loaded
1133 */
1134 @NonNull
Adam Lesinski082614c2016-03-04 14:33:47 -08001135 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
1136 @NonNull String type)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001137 throws NotFoundException {
1138 if (id != 0) {
1139 try {
1140 synchronized (mCachedXmlBlocks) {
1141 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
1142 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
1143 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
1144 // First see if this block is in our cache.
1145 final int num = cachedXmlBlockFiles.length;
1146 for (int i = 0; i < num; i++) {
1147 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
1148 && cachedXmlBlockFiles[i].equals(file)) {
1149 return cachedXmlBlocks[i].newParser();
1150 }
1151 }
1152
1153 // Not in the cache, create a new block and put it at
1154 // the next slot in the cache.
1155 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
1156 if (block != null) {
1157 final int pos = (mLastCachedXmlBlockIndex + 1) % num;
1158 mLastCachedXmlBlockIndex = pos;
1159 final XmlBlock oldBlock = cachedXmlBlocks[pos];
1160 if (oldBlock != null) {
1161 oldBlock.close();
1162 }
1163 cachedXmlBlockCookies[pos] = assetCookie;
1164 cachedXmlBlockFiles[pos] = file;
1165 cachedXmlBlocks[pos] = block;
1166 return block.newParser();
1167 }
1168 }
1169 } catch (Exception e) {
1170 final NotFoundException rnf = new NotFoundException("File " + file
1171 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
1172 rnf.initCause(e);
1173 throw rnf;
1174 }
1175 }
1176
1177 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
1178 + Integer.toHexString(id));
1179 }
1180
1181 /**
1182 * Start preloading of resource data using this Resources object. Only
1183 * for use by the zygote process for loading common system resources.
1184 * {@hide}
1185 */
1186 public final void startPreloading() {
1187 synchronized (sSync) {
1188 if (sPreloaded) {
1189 throw new IllegalStateException("Resources already preloaded");
1190 }
1191 sPreloaded = true;
1192 mPreloading = true;
1193 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1194 updateConfiguration(null, null, null);
Makoto Onuki1480b672017-07-14 08:42:50 -07001195
1196 if (TRACE_FOR_DETAILED_PRELOAD) {
1197 mPreloadTracingPreloadStartTime = SystemClock.uptimeMillis();
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001198 mPreloadTracingStartBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
1199 mPreloadTracingStartBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
Makoto Onuki1480b672017-07-14 08:42:50 -07001200 Log.d(TAG_PRELOAD, "Preload starting");
1201 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001202 }
1203 }
1204
1205 /**
1206 * Called by zygote when it is done preloading resources, to change back
1207 * to normal Resources operation.
1208 */
1209 void finishPreloading() {
1210 if (mPreloading) {
Makoto Onuki1480b672017-07-14 08:42:50 -07001211 if (TRACE_FOR_DETAILED_PRELOAD) {
1212 final long time = SystemClock.uptimeMillis() - mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001213 final long size =
1214 Bitmap.sPreloadTracingTotalBitmapsSize - mPreloadTracingStartBitmapSize;
1215 final long count = Bitmap.sPreloadTracingNumInstantiatedBitmaps
1216 - mPreloadTracingStartBitmapCount;
1217 Log.d(TAG_PRELOAD, "Preload finished, "
1218 + count + " bitmaps of " + size + " bytes in " + time + " ms");
Makoto Onuki1480b672017-07-14 08:42:50 -07001219 }
1220
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001221 mPreloading = false;
1222 flushLayoutCache();
1223 }
1224 }
1225
1226 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1227 return sPreloadedDrawables[0];
1228 }
1229
1230 ThemeImpl newThemeImpl() {
1231 return new ThemeImpl();
1232 }
1233
Adam Lesinski082614c2016-03-04 14:33:47 -08001234 /**
1235 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1236 */
1237 ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1238 ThemeImpl impl = new ThemeImpl();
1239 impl.mKey.setTo(key);
1240 impl.rebase();
1241 return impl;
1242 }
1243
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001244 public class ThemeImpl {
1245 /**
1246 * Unique key for the series of styles applied to this theme.
1247 */
1248 private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1249
1250 @SuppressWarnings("hiding")
1251 private final AssetManager mAssets;
1252 private final long mTheme;
1253
1254 /**
1255 * Resource identifier for the theme.
1256 */
1257 private int mThemeResId = 0;
1258
1259 /*package*/ ThemeImpl() {
1260 mAssets = ResourcesImpl.this.mAssets;
1261 mTheme = mAssets.createTheme();
1262 }
1263
1264 @Override
1265 protected void finalize() throws Throwable {
1266 super.finalize();
1267 mAssets.releaseTheme(mTheme);
1268 }
1269
1270 /*package*/ Resources.ThemeKey getKey() {
1271 return mKey;
1272 }
1273
1274 /*package*/ long getNativeTheme() {
1275 return mTheme;
1276 }
1277
1278 /*package*/ int getAppliedStyleResId() {
1279 return mThemeResId;
1280 }
1281
1282 void applyStyle(int resId, boolean force) {
1283 synchronized (mKey) {
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001284 AssetManager.applyThemeStyle(mTheme, resId, force);
1285
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001286 mThemeResId = resId;
1287 mKey.append(resId, force);
1288 }
1289 }
1290
1291 void setTo(ThemeImpl other) {
1292 synchronized (mKey) {
1293 synchronized (other.mKey) {
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001294 AssetManager.copyTheme(mTheme, other.mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001295
1296 mThemeResId = other.mThemeResId;
1297 mKey.setTo(other.getKey());
1298 }
1299 }
1300 }
1301
1302 @NonNull
1303 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001304 AttributeSet set,
1305 @StyleableRes int[] attrs,
1306 @AttrRes int defStyleAttr,
1307 @StyleRes int defStyleRes) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001308 synchronized (mKey) {
1309 final int len = attrs.length;
1310 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1311
1312 // XXX note that for now we only work with compiled XML files.
1313 // To support generic XML files we will need to manually parse
1314 // out the attributes from the XML file (applying type information
1315 // contained in the resources and such).
1316 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001317 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1318 parser != null ? parser.mParseState : 0,
1319 attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001320 array.mTheme = wrapper;
1321 array.mXml = parser;
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001322
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001323 return array;
1324 }
1325 }
1326
1327 @NonNull
1328 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001329 @NonNull int[] values,
1330 @NonNull int[] attrs) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001331 synchronized (mKey) {
1332 final int len = attrs.length;
1333 if (values == null || len != values.length) {
1334 throw new IllegalArgumentException(
1335 "Base attribute values must the same length as attrs");
1336 }
1337
1338 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001339 AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001340 array.mTheme = wrapper;
1341 array.mXml = null;
1342 return array;
1343 }
1344 }
1345
1346 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1347 synchronized (mKey) {
1348 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1349 }
1350 }
1351
1352 int[] getAllAttributes() {
1353 return mAssets.getStyleAttributes(getAppliedStyleResId());
1354 }
1355
Alan Viveretteac85f902016-03-11 15:15:51 -05001356 @Config int getChangingConfigurations() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001357 synchronized (mKey) {
Alan Viverette9ad386b2017-01-26 14:00:20 -05001358 final @NativeConfig int nativeChangingConfig =
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001359 AssetManager.getThemeChangingConfigurations(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001360 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1361 }
1362 }
1363
1364 public void dump(int priority, String tag, String prefix) {
1365 synchronized (mKey) {
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001366 AssetManager.dumpTheme(mTheme, priority, tag, prefix);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001367 }
1368 }
1369
1370 String[] getTheme() {
1371 synchronized (mKey) {
1372 final int N = mKey.mCount;
1373 final String[] themes = new String[N * 2];
1374 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1375 final int resId = mKey.mResId[j];
1376 final boolean forced = mKey.mForce[j];
1377 try {
1378 themes[i] = getResourceName(resId);
1379 } catch (NotFoundException e) {
1380 themes[i] = Integer.toHexString(i);
1381 }
1382 themes[i + 1] = forced ? "forced" : "not forced";
1383 }
1384 return themes;
1385 }
1386 }
1387
1388 /**
1389 * Rebases the theme against the parent Resource object's current
1390 * configuration by re-applying the styles passed to
1391 * {@link #applyStyle(int, boolean)}.
1392 */
1393 void rebase() {
1394 synchronized (mKey) {
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001395 AssetManager.clearTheme(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001396
1397 // Reapply the same styles in the same order.
1398 for (int i = 0; i < mKey.mCount; i++) {
1399 final int resId = mKey.mResId[i];
1400 final boolean force = mKey.mForce[i];
Adam Lesinskif7d01dd2018-01-25 15:38:58 -08001401 AssetManager.applyThemeStyle(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001402 }
1403 }
1404 }
1405 }
Sunny Goyal99b25d22017-11-01 11:58:13 -07001406
1407 private static class LookupStack {
1408
1409 // Pick a reasonable default size for the array, it is grown as needed.
1410 private int[] mIds = new int[4];
1411 private int mSize = 0;
1412
1413 public void push(int id) {
1414 mIds = GrowingArrayUtils.append(mIds, mSize, id);
1415 mSize++;
1416 }
1417
1418 public boolean contains(int id) {
1419 for (int i = 0; i < mSize; i++) {
1420 if (mIds[i] == id) {
1421 return true;
1422 }
1423 }
1424 return false;
1425 }
1426
1427 public void pop() {
1428 mSize--;
1429 }
1430 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001431}