blob: 84489cfb768c2076d18c549582a7863965318b2c [file] [log] [blame]
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001/*
Ashley Rosee3b13572018-08-21 16:57:30 -04002 * Copyright 2018 The Android Open Source Project
Adam Lesinskifb302cc2016-02-29 16:50:38 -08003 *
Ashley Rosee3b13572018-08-21 16:57:30 -04004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Adam Lesinskifb302cc2016-02-29 16:50:38 -08007 *
Ashley Rosee3b13572018-08-21 16:57:30 -04008 * http://www.apache.org/licenses/LICENSE-2.0
Adam Lesinskifb302cc2016-02-29 16:50:38 -08009 *
10 * Unless required by applicable law or agreed to in writing, software
Ashley Rosee3b13572018-08-21 16:57:30 -040011 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Adam Lesinskifb302cc2016-02-29 16:50:38 -080015 */
16package android.content.res;
17
Aurimas Liutikasaae06632019-01-30 13:16:27 -080018import static android.content.res.Resources.ID_NULL;
19
Adam Lesinskifb302cc2016-02-29 16:50:38 -080020import android.animation.Animator;
21import android.animation.StateListAnimator;
22import android.annotation.AnyRes;
23import android.annotation.AttrRes;
24import android.annotation.NonNull;
25import android.annotation.Nullable;
26import android.annotation.PluralsRes;
27import android.annotation.RawRes;
28import android.annotation.StyleRes;
29import android.annotation.StyleableRes;
Mathew Inwood5c0d3542018-08-14 13:54:31 +010030import android.annotation.UnsupportedAppUsage;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080031import android.content.pm.ActivityInfo;
Alan Viveretteac85f902016-03-11 15:15:51 -050032import android.content.pm.ActivityInfo.Config;
Leon Scroggins III513031d2018-02-15 15:31:45 -050033import android.content.res.AssetManager.AssetInputStream;
Alan Viverette9ad386b2017-01-26 14:00:20 -050034import android.content.res.Configuration.NativeConfig;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080035import android.content.res.Resources.NotFoundException;
Makoto Onuki1480b672017-07-14 08:42:50 -070036import android.graphics.Bitmap;
Leon Scroggins III513031d2018-02-15 15:31:45 -050037import android.graphics.ImageDecoder;
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000038import android.graphics.Typeface;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080039import android.graphics.drawable.ColorDrawable;
Ashley Rosee3b13572018-08-21 16:57:30 -040040import android.graphics.drawable.ColorStateListDrawable;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080041import android.graphics.drawable.Drawable;
ztenghuiee7e8f12017-05-16 15:30:50 -070042import android.graphics.drawable.DrawableContainer;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080043import android.icu.text.PluralRules;
44import android.os.Build;
Yohei Yukawa23cbe852016-05-17 16:42:58 -070045import android.os.LocaleList;
Makoto Onuki1480b672017-07-14 08:42:50 -070046import android.os.SystemClock;
47import android.os.SystemProperties;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080048import android.os.Trace;
49import android.util.AttributeSet;
50import android.util.DisplayMetrics;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080051import android.util.Log;
52import android.util.LongSparseArray;
53import android.util.Slog;
54import android.util.TypedValue;
55import android.util.Xml;
Adam Lesinski4ece3d62016-06-16 18:05:41 -070056import android.view.DisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080057
Sunny Goyal99b25d22017-11-01 11:58:13 -070058import com.android.internal.util.GrowingArrayUtils;
59
Winson9947f1e2019-08-16 10:20:39 -070060import libcore.io.IoUtils;
61
Makoto Onuki1480b672017-07-14 08:42:50 -070062import org.xmlpull.v1.XmlPullParser;
63import org.xmlpull.v1.XmlPullParserException;
64
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000065import java.io.IOException;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080066import java.io.InputStream;
67import java.util.Arrays;
68import java.util.Locale;
69
70/**
Adam Lesinski082614c2016-03-04 14:33:47 -080071 * The implementation of Resource access. This class contains the AssetManager and all caches
72 * associated with it.
73 *
74 * {@link Resources} is just a thing wrapper around this class. When a configuration change
75 * occurs, clients can retain the same {@link Resources} reference because the underlying
76 * {@link ResourcesImpl} object will be updated or re-created.
77 *
Adam Lesinskifb302cc2016-02-29 16:50:38 -080078 * @hide
79 */
80public class ResourcesImpl {
81 static final String TAG = "Resources";
82
83 private static final boolean DEBUG_LOAD = false;
84 private static final boolean DEBUG_CONFIG = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080085
Makoto Onuki1480b672017-07-14 08:42:50 -070086 static final String TAG_PRELOAD = TAG + ".preload";
87
Mathew Inwood5c0d3542018-08-14 13:54:31 +010088 @UnsupportedAppUsage
Makoto Onuki1480b672017-07-14 08:42:50 -070089 private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
Mathew Inwood5c0d3542018-08-14 13:54:31 +010090 @UnsupportedAppUsage
Makoto Onuki1480b672017-07-14 08:42:50 -070091 private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
92
93 public static final boolean TRACE_FOR_DETAILED_PRELOAD =
94 SystemProperties.getBoolean("debug.trace_resource_preload", false);
95
96 /** Used only when TRACE_FOR_DETAILED_PRELOAD is true. */
97 private static int sPreloadTracingNumLoadedDrawables;
98 private long mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -070099 private long mPreloadTracingStartBitmapSize;
100 private long mPreloadTracingStartBitmapCount;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800101
102 private static final int ID_OTHER = 0x01000004;
103
104 private static final Object sSync = new Object();
105
106 private static boolean sPreloaded;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100107 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800108 private boolean mPreloading;
109
110 // Information about preloaded resources. Note that they are not
111 // protected by a lock, because while preloading in zygote we are all
112 // single-threaded, and after that these are immutable.
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100113 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800114 private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100115 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800116 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
117 = new LongSparseArray<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100118 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800119 private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
120 sPreloadedComplexColors = new LongSparseArray<>();
121
122 /** Lock object used to protect access to caches and configuration. */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100123 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800124 private final Object mAccessLock = new Object();
125
126 // These are protected by mAccessLock.
127 private final Configuration mTmpConfig = new Configuration();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100128 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800129 private final DrawableCache mDrawableCache = new DrawableCache();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100130 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800131 private final DrawableCache mColorDrawableCache = new DrawableCache();
132 private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
133 new ConfigurationBoundResourceCache<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100134 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800135 private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
136 new ConfigurationBoundResourceCache<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100137 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800138 private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
139 new ConfigurationBoundResourceCache<>();
140
Sunny Goyal99b25d22017-11-01 11:58:13 -0700141 // A stack of all the resourceIds already referenced when parsing a resource. This is used to
142 // detect circular references in the xml.
143 // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
144 // calls to ResourcesImpl
145 private final ThreadLocal<LookupStack> mLookupStack =
146 ThreadLocal.withInitial(() -> new LookupStack());
147
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800148 /** Size of the cyclical cache used to map XML files to blocks. */
149 private static final int XML_BLOCK_CACHE_SIZE = 4;
150
151 // Cyclical cache used for recently-accessed XML files.
152 private int mLastCachedXmlBlockIndex = -1;
153 private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
154 private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
155 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
156
157
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100158 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800159 final AssetManager mAssets;
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700160 private final DisplayMetrics mMetrics = new DisplayMetrics();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700161 private final DisplayAdjustments mDisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800162
163 private PluralRules mPluralRule;
164
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100165 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800166 private final Configuration mConfiguration = new Configuration();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800167
168 static {
169 sPreloadedDrawables = new LongSparseArray[2];
170 sPreloadedDrawables[0] = new LongSparseArray<>();
171 sPreloadedDrawables[1] = new LongSparseArray<>();
172 }
173
174 /**
175 * Creates a new ResourcesImpl object with CompatibilityInfo.
176 *
177 * @param assets Previously created AssetManager.
178 * @param metrics Current display metrics to consider when
179 * selecting/computing resource values.
180 * @param config Desired device configuration to consider when
181 * selecting/computing resource values (optional).
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700182 * @param displayAdjustments this resource's Display override and compatibility info.
183 * Must not be null.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800184 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100185 @UnsupportedAppUsage
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700186 public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700187 @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800188 mAssets = assets;
189 mMetrics.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700190 mDisplayAdjustments = displayAdjustments;
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700191 mConfiguration.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700192 updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800193 }
194
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700195 public DisplayAdjustments getDisplayAdjustments() {
196 return mDisplayAdjustments;
197 }
198
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100199 @UnsupportedAppUsage
Adam Lesinski082614c2016-03-04 14:33:47 -0800200 public AssetManager getAssets() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800201 return mAssets;
202 }
203
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100204 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800205 DisplayMetrics getDisplayMetrics() {
206 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
207 + "x" + mMetrics.heightPixels + " " + mMetrics.density);
208 return mMetrics;
209 }
210
211 Configuration getConfiguration() {
212 return mConfiguration;
213 }
214
215 Configuration[] getSizeConfigurations() {
216 return mAssets.getSizeConfigurations();
217 }
218
219 CompatibilityInfo getCompatibilityInfo() {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700220 return mDisplayAdjustments.getCompatibilityInfo();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800221 }
222
223 private PluralRules getPluralRule() {
224 synchronized (sSync) {
225 if (mPluralRule == null) {
226 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
227 }
228 return mPluralRule;
229 }
230 }
231
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100232 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800233 void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
234 throws NotFoundException {
235 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
236 if (found) {
237 return;
238 }
239 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
240 }
241
242 void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
Adam Lesinski082614c2016-03-04 14:33:47 -0800243 boolean resolveRefs) throws NotFoundException {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800244 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
245 if (found) {
246 return;
247 }
248 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
249 }
250
251 void getValue(String name, TypedValue outValue, boolean resolveRefs)
252 throws NotFoundException {
253 int id = getIdentifier(name, "string", null);
254 if (id != 0) {
255 getValue(id, outValue, resolveRefs);
256 return;
257 }
258 throw new NotFoundException("String resource name " + name);
259 }
260
261 int getIdentifier(String name, String defType, String defPackage) {
262 if (name == null) {
263 throw new NullPointerException("name is null");
264 }
265 try {
266 return Integer.parseInt(name);
267 } catch (Exception e) {
268 // Ignore
269 }
270 return mAssets.getResourceIdentifier(name, defType, defPackage);
271 }
272
273 @NonNull
274 String getResourceName(@AnyRes int resid) throws NotFoundException {
275 String str = mAssets.getResourceName(resid);
276 if (str != null) return str;
277 throw new NotFoundException("Unable to find resource ID #0x"
278 + Integer.toHexString(resid));
279 }
280
281 @NonNull
282 String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
283 String str = mAssets.getResourcePackageName(resid);
284 if (str != null) return str;
285 throw new NotFoundException("Unable to find resource ID #0x"
286 + Integer.toHexString(resid));
287 }
288
289 @NonNull
290 String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
291 String str = mAssets.getResourceTypeName(resid);
292 if (str != null) return str;
293 throw new NotFoundException("Unable to find resource ID #0x"
294 + Integer.toHexString(resid));
295 }
296
297 @NonNull
298 String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
299 String str = mAssets.getResourceEntryName(resid);
300 if (str != null) return str;
301 throw new NotFoundException("Unable to find resource ID #0x"
302 + Integer.toHexString(resid));
303 }
304
305 @NonNull
Winson2f3669b2019-01-11 11:28:34 -0800306 String getLastResourceResolution() throws NotFoundException {
307 String str = mAssets.getLastResourceResolution();
308 if (str != null) return str;
309 throw new NotFoundException("Associated AssetManager hasn't resolved a resource");
310 }
311
312 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800313 CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
314 PluralRules rule = getPluralRule();
315 CharSequence res = mAssets.getResourceBagText(id,
316 attrForQuantityCode(rule.select(quantity)));
317 if (res != null) {
318 return res;
319 }
320 res = mAssets.getResourceBagText(id, ID_OTHER);
321 if (res != null) {
322 return res;
323 }
324 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
325 + " quantity=" + quantity
326 + " item=" + rule.select(quantity));
327 }
328
329 private static int attrForQuantityCode(String quantityCode) {
330 switch (quantityCode) {
331 case PluralRules.KEYWORD_ZERO: return 0x01000005;
332 case PluralRules.KEYWORD_ONE: return 0x01000006;
333 case PluralRules.KEYWORD_TWO: return 0x01000007;
334 case PluralRules.KEYWORD_FEW: return 0x01000008;
335 case PluralRules.KEYWORD_MANY: return 0x01000009;
336 default: return ID_OTHER;
337 }
338 }
339
340 @NonNull
341 AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
342 throws NotFoundException {
343 getValue(id, tempValue, true);
344 try {
345 return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
346 } catch (Exception e) {
347 throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
348 + "resource ID #0x" + Integer.toHexString(id), e);
349 }
350 }
351
352 @NonNull
353 InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
354 getValue(id, value, true);
355 try {
356 return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
357 AssetManager.ACCESS_STREAMING);
358 } catch (Exception e) {
Christopher Tatef135b272016-05-27 17:10:30 -0700359 // Note: value.string might be null
360 NotFoundException rnf = new NotFoundException("File "
361 + (value.string == null ? "(null)" : value.string.toString())
362 + " from drawable resource ID #0x" + Integer.toHexString(id));
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800363 rnf.initCause(e);
364 throw rnf;
365 }
366 }
367
368 ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
369 return mAnimatorCache;
370 }
371
372 ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
373 return mStateListAnimatorCache;
374 }
375
Adam Lesinski082614c2016-03-04 14:33:47 -0800376 public void updateConfiguration(Configuration config, DisplayMetrics metrics,
377 CompatibilityInfo compat) {
Adam Lesinski991357f2016-05-10 14:00:03 -0700378 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
379 try {
380 synchronized (mAccessLock) {
Winson9947f1e2019-08-16 10:20:39 -0700381 if (DEBUG_CONFIG) {
Adam Lesinski991357f2016-05-10 14:00:03 -0700382 Slog.i(TAG, "**** Updating config of " + this + ": old config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700383 + mConfiguration + " old compat is "
384 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700385 Slog.i(TAG, "**** Updating config of " + this + ": new config is "
386 + config + " new compat is " + compat);
387 }
388 if (compat != null) {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700389 mDisplayAdjustments.setCompatibilityInfo(compat);
Adam Lesinski991357f2016-05-10 14:00:03 -0700390 }
391 if (metrics != null) {
392 mMetrics.setTo(metrics);
393 }
394 // NOTE: We should re-arrange this code to create a Display
395 // with the CompatibilityInfo that is used everywhere we deal
396 // with the display in relation to this app, rather than
397 // doing the conversion here. This impl should be okay because
398 // we make sure to return a compatible display in the places
399 // where there are public APIs to retrieve the display... but
Adam Lesinskib61e4052016-05-19 18:23:05 -0700400 // it would be cleaner and more maintainable to just be
Adam Lesinski991357f2016-05-10 14:00:03 -0700401 // consistently dealing with a compatible display everywhere in
402 // the framework.
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700403 mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800404
Adam Lesinski991357f2016-05-10 14:00:03 -0700405 final @Config int configChanges = calcConfigChanges(config);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800406
Adam Lesinskib61e4052016-05-19 18:23:05 -0700407 // If even after the update there are no Locales set, grab the default locales.
Adam Lesinski991357f2016-05-10 14:00:03 -0700408 LocaleList locales = mConfiguration.getLocales();
409 if (locales.isEmpty()) {
Adam Lesinskib61e4052016-05-19 18:23:05 -0700410 locales = LocaleList.getDefault();
Adam Lesinski991357f2016-05-10 14:00:03 -0700411 mConfiguration.setLocales(locales);
412 }
Adam Lesinskib61e4052016-05-19 18:23:05 -0700413
414 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
415 if (locales.size() > 1) {
416 // The LocaleList has changed. We must query the AssetManager's available
417 // Locales and figure out the best matching Locale in the new LocaleList.
418 String[] availableLocales = mAssets.getNonSystemLocales();
419 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
420 // No app defined locales, so grab the system locales.
421 availableLocales = mAssets.getLocales();
422 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
423 availableLocales = null;
424 }
425 }
426
427 if (availableLocales != null) {
428 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
429 availableLocales);
430 if (bestLocale != null && bestLocale != locales.get(0)) {
431 mConfiguration.setLocales(new LocaleList(bestLocale, locales));
432 }
433 }
434 }
435 }
436
Adam Lesinski991357f2016-05-10 14:00:03 -0700437 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
438 mMetrics.densityDpi = mConfiguration.densityDpi;
439 mMetrics.density =
440 mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
441 }
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700442
443 // Protect against an unset fontScale.
444 mMetrics.scaledDensity = mMetrics.density *
445 (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
Adam Lesinski991357f2016-05-10 14:00:03 -0700446
447 final int width, height;
448 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
449 width = mMetrics.widthPixels;
450 height = mMetrics.heightPixels;
451 } else {
452 //noinspection SuspiciousNameCombination
453 width = mMetrics.heightPixels;
454 //noinspection SuspiciousNameCombination
455 height = mMetrics.widthPixels;
456 }
457
458 final int keyboardHidden;
459 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
460 && mConfiguration.hardKeyboardHidden
461 == Configuration.HARDKEYBOARDHIDDEN_YES) {
462 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
463 } else {
464 keyboardHidden = mConfiguration.keyboardHidden;
465 }
466
467 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
Adam Lesinskib61e4052016-05-19 18:23:05 -0700468 adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
Adam Lesinski991357f2016-05-10 14:00:03 -0700469 mConfiguration.orientation,
470 mConfiguration.touchscreen,
471 mConfiguration.densityDpi, mConfiguration.keyboard,
472 keyboardHidden, mConfiguration.navigation, width, height,
473 mConfiguration.smallestScreenWidthDp,
474 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
475 mConfiguration.screenLayout, mConfiguration.uiMode,
Romain Guy408afbf2017-01-25 10:23:03 -0800476 mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
Adam Lesinski991357f2016-05-10 14:00:03 -0700477
478 if (DEBUG_CONFIG) {
479 Slog.i(TAG, "**** Updating config of " + this + ": final config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700480 + mConfiguration + " final compat is "
481 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700482 }
483
484 mDrawableCache.onConfigurationChange(configChanges);
485 mColorDrawableCache.onConfigurationChange(configChanges);
486 mComplexColorCache.onConfigurationChange(configChanges);
487 mAnimatorCache.onConfigurationChange(configChanges);
488 mStateListAnimatorCache.onConfigurationChange(configChanges);
489
490 flushLayoutCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800491 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700492 synchronized (sSync) {
493 if (mPluralRule != null) {
494 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
495 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800496 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700497 } finally {
498 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800499 }
500 }
501
502 /**
Alan Viveretteac85f902016-03-11 15:15:51 -0500503 * Applies the new configuration, returning a bitmask of the changes
504 * between the old and new configurations.
505 *
506 * @param config the new configuration
507 * @return bitmask of config changes
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800508 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500509 public @Config int calcConfigChanges(@Nullable Configuration config) {
510 if (config == null) {
511 // If there is no configuration, assume all flags have changed.
512 return 0xFFFFFFFF;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800513 }
Alan Viveretteac85f902016-03-11 15:15:51 -0500514
515 mTmpConfig.setTo(config);
516 int density = config.densityDpi;
517 if (density == Configuration.DENSITY_DPI_UNDEFINED) {
518 density = mMetrics.noncompatDensityDpi;
519 }
520
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700521 mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
Alan Viveretteac85f902016-03-11 15:15:51 -0500522
523 if (mTmpConfig.getLocales().isEmpty()) {
524 mTmpConfig.setLocales(LocaleList.getDefault());
525 }
526 return mConfiguration.updateFrom(mTmpConfig);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800527 }
528
529 /**
530 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
531 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
532 *
533 * All released versions of android prior to "L" used the deprecated language
534 * tags, so we will need to support them for backwards compatibility.
535 *
536 * Note that this conversion needs to take place *after* the call to
537 * {@code toLanguageTag} because that will convert all the deprecated codes to
538 * the new ones, even if they're set manually.
539 */
540 private static String adjustLanguageTag(String languageTag) {
541 final int separator = languageTag.indexOf('-');
542 final String language;
543 final String remainder;
544
545 if (separator == -1) {
546 language = languageTag;
547 remainder = "";
548 } else {
549 language = languageTag.substring(0, separator);
550 remainder = languageTag.substring(separator);
551 }
552
553 return Locale.adjustLanguageCode(language) + remainder;
554 }
555
556 /**
557 * Call this to remove all cached loaded layout resources from the
558 * Resources object. Only intended for use with performance testing
559 * tools.
560 */
561 public void flushLayoutCache() {
562 synchronized (mCachedXmlBlocks) {
563 Arrays.fill(mCachedXmlBlockCookies, 0);
564 Arrays.fill(mCachedXmlBlockFiles, null);
565
566 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
567 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
568 final XmlBlock oldBlock = cachedXmlBlocks[i];
569 if (oldBlock != null) {
570 oldBlock.close();
571 }
572 }
573 Arrays.fill(cachedXmlBlocks, null);
574 }
575 }
576
Winson9947f1e2019-08-16 10:20:39 -0700577 /**
578 * Wipe all caches that might be read and return an outdated object when resolving a resource.
579 */
580 public void clearAllCaches() {
581 synchronized (mAccessLock) {
582 mDrawableCache.clear();
583 mColorDrawableCache.clear();
584 mComplexColorCache.clear();
585 mAnimatorCache.clear();
586 mStateListAnimatorCache.clear();
587 flushLayoutCache();
588 }
589 }
590
Chris Craik1194b0b2018-03-23 13:36:24 -0700591 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700592 Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
593 int density, @Nullable Resources.Theme theme)
594 throws NotFoundException {
595 // If the drawable's XML lives in our current density qualifier,
596 // it's okay to use a scaled version from the cache. Otherwise, we
597 // need to actually load the drawable from XML.
598 final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
599
600 // Pretend the requested density is actually the display density. If
601 // the drawable returned is not the requested density, then force it
602 // to be scaled later by dividing its density by the ratio of
603 // requested density to actual device density. Drawables that have
604 // undefined density or no density don't need to be handled here.
605 if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
606 if (value.density == density) {
607 value.density = mMetrics.densityDpi;
608 } else {
609 value.density = (value.density * mMetrics.densityDpi) / density;
610 }
611 }
612
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800613 try {
614 if (TRACE_FOR_PRELOAD) {
615 // Log only framework resources
616 if ((id >>> 24) == 0x1) {
617 final String name = getResourceName(id);
618 if (name != null) {
619 Log.d("PreloadDrawable", name);
620 }
621 }
622 }
623
624 final boolean isColorDrawable;
625 final DrawableCache caches;
626 final long key;
627 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
628 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
629 isColorDrawable = true;
630 caches = mColorDrawableCache;
631 key = value.data;
632 } else {
633 isColorDrawable = false;
634 caches = mDrawableCache;
635 key = (((long) value.assetCookie) << 32) | value.data;
636 }
637
638 // First, check whether we have a cached version of this drawable
639 // that was inflated against the specified theme. Skip the cache if
640 // we're currently preloading or we're not using the cache.
641 if (!mPreloading && useCache) {
642 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
643 if (cachedDrawable != null) {
Alan Viverette58857c82016-10-29 00:47:45 +0100644 cachedDrawable.setChangingConfigurations(value.changingConfigurations);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800645 return cachedDrawable;
646 }
647 }
648
649 // Next, check preloaded drawables. Preloaded drawables may contain
650 // unresolved theme attributes.
651 final Drawable.ConstantState cs;
652 if (isColorDrawable) {
653 cs = sPreloadedColorDrawables.get(key);
654 } else {
655 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
656 }
657
658 Drawable dr;
ztenghuiee7e8f12017-05-16 15:30:50 -0700659 boolean needsNewDrawableAfterCache = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800660 if (cs != null) {
Makoto Onuki1480b672017-07-14 08:42:50 -0700661 if (TRACE_FOR_DETAILED_PRELOAD) {
662 // Log only framework resources
663 if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
664 final String name = getResourceName(id);
665 if (name != null) {
666 Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
667 + Integer.toHexString(id) + " " + name);
668 }
669 }
670 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800671 dr = cs.newDrawable(wrapper);
672 } else if (isColorDrawable) {
673 dr = new ColorDrawable(value.data);
674 } else {
Chris Craikceb26932018-02-01 15:51:34 -0800675 dr = loadDrawableForCookie(wrapper, value, id, density);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800676 }
ztenghuiee7e8f12017-05-16 15:30:50 -0700677 // DrawableContainer' constant state has drawables instances. In order to leave the
678 // constant state intact in the cache, we need to create a new DrawableContainer after
679 // added to cache.
680 if (dr instanceof DrawableContainer) {
681 needsNewDrawableAfterCache = true;
682 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800683
684 // Determine if the drawable has unresolved theme attributes. If it
685 // does, we'll need to apply a theme and store it in a theme-specific
686 // cache.
687 final boolean canApplyTheme = dr != null && dr.canApplyTheme();
688 if (canApplyTheme && theme != null) {
689 dr = dr.mutate();
690 dr.applyTheme(theme);
691 dr.clearMutated();
692 }
693
694 // If we were able to obtain a drawable, store it in the appropriate
695 // cache: preload, not themed, null theme, or theme-specific. Don't
696 // pollute the cache with drawables loaded from a foreign density.
Alan Viverette58857c82016-10-29 00:47:45 +0100697 if (dr != null) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800698 dr.setChangingConfigurations(value.changingConfigurations);
Alan Viverette58857c82016-10-29 00:47:45 +0100699 if (useCache) {
700 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
ztenghuiee7e8f12017-05-16 15:30:50 -0700701 if (needsNewDrawableAfterCache) {
702 Drawable.ConstantState state = dr.getConstantState();
703 if (state != null) {
704 dr = state.newDrawable(wrapper);
705 }
706 }
Alan Viverette58857c82016-10-29 00:47:45 +0100707 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800708 }
709
710 return dr;
711 } catch (Exception e) {
712 String name;
713 try {
714 name = getResourceName(id);
715 } catch (NotFoundException e2) {
716 name = "(missing name)";
717 }
718
719 // The target drawable might fail to load for any number of
720 // reasons, but we always want to include the resource name.
721 // Since the client already expects this method to throw a
722 // NotFoundException, just throw one of those.
723 final NotFoundException nfe = new NotFoundException("Drawable " + name
724 + " with resource ID #0x" + Integer.toHexString(id), e);
725 nfe.setStackTrace(new StackTraceElement[0]);
726 throw nfe;
727 }
728 }
729
730 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Adam Lesinski082614c2016-03-04 14:33:47 -0800731 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800732 final Drawable.ConstantState cs = dr.getConstantState();
733 if (cs == null) {
734 return;
735 }
736
737 if (mPreloading) {
738 final int changingConfigs = cs.getChangingConfigurations();
739 if (isColorDrawable) {
740 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
741 sPreloadedColorDrawables.put(key, cs);
742 }
743 } else {
744 if (verifyPreloadConfig(
Alan Viverettea8a66cc2017-03-20 15:00:51 -0400745 changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
746 if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800747 // If this resource does not vary based on layout direction,
748 // we can put it in all of the preload maps.
749 sPreloadedDrawables[0].put(key, cs);
750 sPreloadedDrawables[1].put(key, cs);
751 } else {
752 // Otherwise, only in the layout dir we loaded it for.
753 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
754 }
755 }
756 }
757 } else {
758 synchronized (mAccessLock) {
759 caches.put(key, theme, cs, usesTheme);
760 }
761 }
762 }
763
Alan Viveretteac85f902016-03-11 15:15:51 -0500764 private boolean verifyPreloadConfig(@Config int changingConfigurations,
765 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800766 // We allow preloading of resources even if they vary by font scale (which
767 // doesn't impact resource selection) or density (which we handle specially by
768 // simply turning off all preloading), as well as any other configs specified
769 // by the caller.
770 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
771 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
772 String resName;
773 try {
774 resName = getResourceName(resourceId);
775 } catch (NotFoundException e) {
776 resName = "?";
777 }
778 // This should never happen in production, so we should log a
779 // warning even if we're not debugging.
780 Log.w(TAG, "Preloaded " + name + " resource #0x"
781 + Integer.toHexString(resourceId)
782 + " (" + resName + ") that varies with configuration!!");
783 return false;
784 }
785 if (TRACE_FOR_PRELOAD) {
786 String resName;
787 try {
788 resName = getResourceName(resourceId);
789 } catch (NotFoundException e) {
790 resName = "?";
791 }
792 Log.w(TAG, "Preloading " + name + " resource #0x"
793 + Integer.toHexString(resourceId)
794 + " (" + resName + ")");
795 }
796 return true;
797 }
798
799 /**
Leon Scroggins III513031d2018-02-15 15:31:45 -0500800 * Loads a Drawable from an encoded image stream, or null.
801 *
802 * This call will handle closing ais.
803 */
Chris Craik1194b0b2018-03-23 13:36:24 -0700804 @Nullable
Leon Scroggins III513031d2018-02-15 15:31:45 -0500805 private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
806 @NonNull Resources wrapper, @NonNull TypedValue value) {
807 ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
808 wrapper, value);
809 try {
810 return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
811 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
812 });
813 } catch (IOException ioe) {
814 // This is okay. This may be something that ImageDecoder does not
815 // support, like SVG.
816 return null;
817 }
818 }
819
820 /**
Winson9947f1e2019-08-16 10:20:39 -0700821 * Loads a Drawable from an encoded image stream, or null.
822 *
823 * This call will handle closing the {@link InputStream}.
824 */
825 @Nullable
826 private Drawable decodeImageDrawable(@NonNull InputStream inputStream,
827 @NonNull Resources wrapper, @NonNull TypedValue value) {
828 ImageDecoder.Source src = ImageDecoder.createSource(wrapper, inputStream, value.density);
829 try {
830 return ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
831 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
832 } catch (IOException ignored) {
833 // This is okay. This may be something that ImageDecoder does not
834 // support, like SVG.
835 return null;
836 } finally {
837 IoUtils.closeQuietly(inputStream);
838 }
839 }
840
841 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800842 * Loads a drawable from XML or resources stream.
Chris Craik1194b0b2018-03-23 13:36:24 -0700843 *
844 * @return Drawable, or null if Drawable cannot be decoded.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800845 */
Chris Craik1194b0b2018-03-23 13:36:24 -0700846 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700847 private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
Chris Craikceb26932018-02-01 15:51:34 -0800848 int id, int density) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800849 if (value.string == null) {
850 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
851 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
852 }
853
854 final String file = value.string.toString();
855
856 if (TRACE_FOR_MISS_PRELOAD) {
857 // Log only framework resources
858 if ((id >>> 24) == 0x1) {
859 final String name = getResourceName(id);
860 if (name != null) {
861 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
862 + ": " + name + " at " + file);
863 }
864 }
865 }
866
Chris Craikceb26932018-02-01 15:51:34 -0800867 // For preload tracing.
Makoto Onuki1480b672017-07-14 08:42:50 -0700868 long startTime = 0;
869 int startBitmapCount = 0;
870 long startBitmapSize = 0;
Chris Craikceb26932018-02-01 15:51:34 -0800871 int startDrawableCount = 0;
Makoto Onuki1480b672017-07-14 08:42:50 -0700872 if (TRACE_FOR_DETAILED_PRELOAD) {
873 startTime = System.nanoTime();
874 startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
875 startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
Chris Craikceb26932018-02-01 15:51:34 -0800876 startDrawableCount = sPreloadTracingNumLoadedDrawables;
Makoto Onuki1480b672017-07-14 08:42:50 -0700877 }
878
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800879 if (DEBUG_LOAD) {
880 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
881 }
882
Chris Craikceb26932018-02-01 15:51:34 -0800883
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800884 final Drawable dr;
885
886 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
Sunny Goyal99b25d22017-11-01 11:58:13 -0700887 LookupStack stack = mLookupStack.get();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800888 try {
Sunny Goyal99b25d22017-11-01 11:58:13 -0700889 // Perform a linear search to check if we have already referenced this resource before.
890 if (stack.contains(id)) {
891 throw new Exception("Recursive reference in drawable");
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800892 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700893 stack.push(id);
894 try {
895 if (file.endsWith(".xml")) {
Ashley Rose43f7c8e2019-07-09 20:14:17 -0400896 final String typeName = getResourceTypeName(id);
897 if (typeName != null && typeName.equals("color")) {
Winson9653d662019-01-17 16:47:36 -0800898 dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
Ashley Rosee3b13572018-08-21 16:57:30 -0400899 } else {
Winson9653d662019-01-17 16:47:36 -0800900 dr = loadXmlDrawable(wrapper, value, id, density, file);
Ashley Rosee3b13572018-08-21 16:57:30 -0400901 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700902 } else {
903 final InputStream is = mAssets.openNonAsset(
904 value.assetCookie, file, AssetManager.ACCESS_STREAMING);
Winson9947f1e2019-08-16 10:20:39 -0700905 if (is instanceof AssetInputStream) {
906 AssetInputStream ais = (AssetInputStream) is;
907 dr = decodeImageDrawable(ais, wrapper, value);
908 } else {
909 dr = decodeImageDrawable(is, wrapper, value);
910 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700911 }
912 } finally {
913 stack.pop();
914 }
Hyunyoung Songaff04c32017-10-10 10:35:59 -0700915 } catch (Exception | StackOverflowError e) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800916 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
917 final NotFoundException rnf = new NotFoundException(
918 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
919 rnf.initCause(e);
920 throw rnf;
921 }
922 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
923
Makoto Onuki1480b672017-07-14 08:42:50 -0700924 if (TRACE_FOR_DETAILED_PRELOAD) {
925 if (((id >>> 24) == 0x1)) {
926 final String name = getResourceName(id);
927 if (name != null) {
928 final long time = System.nanoTime() - startTime;
929 final int loadedBitmapCount =
930 Bitmap.sPreloadTracingNumInstantiatedBitmaps - startBitmapCount;
931 final long loadedBitmapSize =
932 Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
933 final int loadedDrawables =
Chris Craikceb26932018-02-01 15:51:34 -0800934 sPreloadTracingNumLoadedDrawables - startDrawableCount;
Makoto Onuki1480b672017-07-14 08:42:50 -0700935
936 sPreloadTracingNumLoadedDrawables++;
937
938 final boolean isRoot = (android.os.Process.myUid() == 0);
939
940 Log.d(TAG_PRELOAD,
941 (isRoot ? "Preloaded FW drawable #"
942 : "Loaded non-preloaded FW drawable #")
943 + Integer.toHexString(id)
944 + " " + name
945 + " " + file
946 + " " + dr.getClass().getCanonicalName()
947 + " #nested_drawables= " + loadedDrawables
948 + " #bitmaps= " + loadedBitmapCount
949 + " total_bitmap_size= " + loadedBitmapSize
950 + " in[us] " + (time / 1000));
951 }
952 }
953 }
954
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800955 return dr;
956 }
957
Winson9653d662019-01-17 16:47:36 -0800958 private Drawable loadColorOrXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
959 int id, int density, String file) {
960 try {
961 ColorStateList csl = loadColorStateList(wrapper, value, id, null);
962 return new ColorStateListDrawable(csl);
963 } catch (NotFoundException originalException) {
964 // If we fail to load as color, try as normal XML drawable
965 try {
966 return loadXmlDrawable(wrapper, value, id, density, file);
967 } catch (Exception ignored) {
968 // If fallback also fails, throw the original exception
969 throw originalException;
970 }
971 }
972 }
973
974 private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
975 int id, int density, String file)
976 throws IOException, XmlPullParserException {
977 try (
978 XmlResourceParser rp =
979 loadXmlResourceParser(file, id, value.assetCookie, "drawable")
980 ) {
981 return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
982 }
983 }
984
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800985 /**
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000986 * Loads a font from XML or resources stream.
987 */
988 @Nullable
Clara Bayarried00bfd2017-01-20 14:58:21 +0000989 public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000990 if (value.string == null) {
991 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
992 + Integer.toHexString(id) + ") is not a Font: " + value);
993 }
994
995 final String file = value.string.toString();
Seigo Nonaka2ea169a2017-05-15 16:25:11 -0700996 if (!file.startsWith("res/")) {
997 return null;
998 }
999
Clara Bayarrib12397e2017-01-27 11:02:48 +00001000 Typeface cached = Typeface.findFromCache(mAssets, file);
Clara Bayarried00bfd2017-01-20 14:58:21 +00001001 if (cached != null) {
1002 return cached;
1003 }
Clara Bayarri18e9f9f2016-12-19 16:20:29 +00001004
1005 if (DEBUG_LOAD) {
1006 Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
1007 }
1008
1009 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
1010 try {
Clara Bayarried00bfd2017-01-20 14:58:21 +00001011 if (file.endsWith("xml")) {
1012 final XmlResourceParser rp = loadXmlResourceParser(
1013 file, id, value.assetCookie, "font");
Seigo Nonakaac873c92017-03-07 15:34:53 -08001014 final FontResourcesParser.FamilyResourceEntry familyEntry =
1015 FontResourcesParser.parse(rp, wrapper);
1016 if (familyEntry == null) {
Seigo Nonakaac873c92017-03-07 15:34:53 -08001017 return null;
1018 }
1019 return Typeface.createFromResources(familyEntry, mAssets, file);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +00001020 }
Seigo Nonakaee4b6d82018-10-25 13:12:03 -07001021 return new Typeface.Builder(mAssets, file, false /* isAsset */, value.assetCookie)
1022 .build();
Clara Bayarried00bfd2017-01-20 14:58:21 +00001023 } catch (XmlPullParserException e) {
1024 Log.e(TAG, "Failed to parse xml resource " + file, e);
1025 } catch (IOException e) {
1026 Log.e(TAG, "Failed to read xml resource " + file, e);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +00001027 } finally {
1028 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1029 }
1030 return null;
1031 }
1032
1033 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001034 * Given the value and id, we can get the XML filename as in value.data, based on that, we
1035 * first try to load CSL from the cache. If not found, try to get from the constant state.
1036 * Last, parse the XML and generate the CSL.
1037 */
Chris Craik2e41d492018-02-09 14:21:37 -08001038 @Nullable
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001039 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
Adam Lesinski082614c2016-03-04 14:33:47 -08001040 TypedValue value, int id) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001041 final long key = (((long) value.assetCookie) << 32) | value.data;
1042 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
1043 ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
1044 if (complexColor != null) {
1045 return complexColor;
1046 }
1047
1048 final android.content.res.ConstantState<ComplexColor> factory =
1049 sPreloadedComplexColors.get(key);
1050
1051 if (factory != null) {
1052 complexColor = factory.newInstance(wrapper, theme);
1053 }
1054 if (complexColor == null) {
1055 complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
1056 }
1057
Chris Craik2e41d492018-02-09 14:21:37 -08001058 if (complexColor != null) {
1059 complexColor.setBaseChangingConfigurations(value.changingConfigurations);
Alan Viverette0b9295d2016-03-10 10:30:08 -05001060
Chris Craik2e41d492018-02-09 14:21:37 -08001061 if (mPreloading) {
1062 if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
1063 0, value.resourceId, "color")) {
1064 sPreloadedComplexColors.put(key, complexColor.getConstantState());
1065 }
1066 } else {
1067 cache.put(key, theme, complexColor.getConstantState());
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001068 }
1069 }
1070 return complexColor;
1071 }
1072
1073 @Nullable
1074 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001075 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001076 if (TRACE_FOR_PRELOAD) {
1077 // Log only framework resources
1078 if ((id >>> 24) == 0x1) {
1079 final String name = getResourceName(id);
1080 if (name != null) android.util.Log.d("loadComplexColor", name);
1081 }
1082 }
1083
1084 final long key = (((long) value.assetCookie) << 32) | value.data;
1085
1086 // Handle inline color definitions.
1087 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
1088 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1089 return getColorStateListFromInt(value, key);
1090 }
1091
1092 final String file = value.string.toString();
1093
1094 ComplexColor complexColor;
1095 if (file.endsWith(".xml")) {
1096 try {
1097 complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1098 } catch (Exception e) {
1099 final NotFoundException rnf = new NotFoundException(
1100 "File " + file + " from complex color resource ID #0x"
1101 + Integer.toHexString(id));
1102 rnf.initCause(e);
1103 throw rnf;
1104 }
1105 } else {
1106 throw new NotFoundException(
1107 "File " + file + " from drawable resource ID #0x"
1108 + Integer.toHexString(id) + ": .xml extension required");
1109 }
1110
1111 return complexColor;
1112 }
1113
Chris Craikceb26932018-02-01 15:51:34 -08001114 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001115 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001116 Resources.Theme theme)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001117 throws NotFoundException {
1118 if (TRACE_FOR_PRELOAD) {
1119 // Log only framework resources
1120 if ((id >>> 24) == 0x1) {
1121 final String name = getResourceName(id);
1122 if (name != null) android.util.Log.d("PreloadColorStateList", name);
1123 }
1124 }
1125
1126 final long key = (((long) value.assetCookie) << 32) | value.data;
1127
1128 // Handle inline color definitions.
1129 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
1130 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1131 return getColorStateListFromInt(value, key);
1132 }
1133
1134 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1135 if (complexColor != null && complexColor instanceof ColorStateList) {
1136 return (ColorStateList) complexColor;
1137 }
1138
1139 throw new NotFoundException(
1140 "Can't find ColorStateList from drawable resource ID #0x"
1141 + Integer.toHexString(id));
1142 }
1143
1144 @NonNull
1145 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
1146 ColorStateList csl;
1147 final android.content.res.ConstantState<ComplexColor> factory =
1148 sPreloadedComplexColors.get(key);
1149 if (factory != null) {
1150 return (ColorStateList) factory.newInstance();
1151 }
1152
1153 csl = ColorStateList.valueOf(value.data);
1154
1155 if (mPreloading) {
1156 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
1157 "color")) {
1158 sPreloadedComplexColors.put(key, csl.getConstantState());
1159 }
1160 }
1161
1162 return csl;
1163 }
1164
1165 /**
1166 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
1167 * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
1168 *
1169 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
1170 * and selector tag.
1171 *
Chris Craik2e41d492018-02-09 14:21:37 -08001172 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content, or
1173 * {@code null} if the XML file is neither.
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001174 */
Chris Craikceb26932018-02-01 15:51:34 -08001175 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001176 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001177 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001178 if (value.string == null) {
1179 throw new UnsupportedOperationException(
1180 "Can't convert to ComplexColor: type=0x" + value.type);
1181 }
1182
1183 final String file = value.string.toString();
1184
1185 if (TRACE_FOR_MISS_PRELOAD) {
1186 // Log only framework resources
1187 if ((id >>> 24) == 0x1) {
1188 final String name = getResourceName(id);
1189 if (name != null) {
1190 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
1191 + ": " + name + " at " + file);
1192 }
1193 }
1194 }
1195
1196 if (DEBUG_LOAD) {
1197 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
1198 }
1199
1200 ComplexColor complexColor = null;
1201
1202 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
1203 if (file.endsWith(".xml")) {
1204 try {
1205 final XmlResourceParser parser = loadXmlResourceParser(
1206 file, id, value.assetCookie, "ComplexColor");
1207
1208 final AttributeSet attrs = Xml.asAttributeSet(parser);
1209 int type;
1210 while ((type = parser.next()) != XmlPullParser.START_TAG
1211 && type != XmlPullParser.END_DOCUMENT) {
1212 // Seek parser to start tag.
1213 }
1214 if (type != XmlPullParser.START_TAG) {
1215 throw new XmlPullParserException("No start tag found");
1216 }
1217
1218 final String name = parser.getName();
1219 if (name.equals("gradient")) {
1220 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
1221 } else if (name.equals("selector")) {
1222 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
1223 }
1224 parser.close();
1225 } catch (Exception e) {
1226 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1227 final NotFoundException rnf = new NotFoundException(
1228 "File " + file + " from ComplexColor resource ID #0x"
1229 + Integer.toHexString(id));
1230 rnf.initCause(e);
1231 throw rnf;
1232 }
1233 } else {
1234 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1235 throw new NotFoundException(
1236 "File " + file + " from drawable resource ID #0x"
1237 + Integer.toHexString(id) + ": .xml extension required");
1238 }
1239 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1240
1241 return complexColor;
1242 }
1243
1244 /**
1245 * Loads an XML parser for the specified file.
1246 *
1247 * @param file the path for the XML file to parse
1248 * @param id the resource identifier for the file
1249 * @param assetCookie the asset cookie for the file
1250 * @param type the type of resource (used for logging)
1251 * @return a parser for the specified XML file
1252 * @throws NotFoundException if the file could not be loaded
1253 */
1254 @NonNull
Adam Lesinski082614c2016-03-04 14:33:47 -08001255 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
1256 @NonNull String type)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001257 throws NotFoundException {
1258 if (id != 0) {
1259 try {
1260 synchronized (mCachedXmlBlocks) {
1261 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
1262 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
1263 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
1264 // First see if this block is in our cache.
1265 final int num = cachedXmlBlockFiles.length;
1266 for (int i = 0; i < num; i++) {
1267 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
1268 && cachedXmlBlockFiles[i].equals(file)) {
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001269 return cachedXmlBlocks[i].newParser(id);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001270 }
1271 }
1272
1273 // Not in the cache, create a new block and put it at
1274 // the next slot in the cache.
1275 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
1276 if (block != null) {
1277 final int pos = (mLastCachedXmlBlockIndex + 1) % num;
1278 mLastCachedXmlBlockIndex = pos;
1279 final XmlBlock oldBlock = cachedXmlBlocks[pos];
1280 if (oldBlock != null) {
1281 oldBlock.close();
1282 }
1283 cachedXmlBlockCookies[pos] = assetCookie;
1284 cachedXmlBlockFiles[pos] = file;
1285 cachedXmlBlocks[pos] = block;
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001286 return block.newParser(id);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001287 }
1288 }
1289 } catch (Exception e) {
1290 final NotFoundException rnf = new NotFoundException("File " + file
1291 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
1292 rnf.initCause(e);
1293 throw rnf;
1294 }
1295 }
1296
1297 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
1298 + Integer.toHexString(id));
1299 }
1300
1301 /**
1302 * Start preloading of resource data using this Resources object. Only
1303 * for use by the zygote process for loading common system resources.
1304 * {@hide}
1305 */
1306 public final void startPreloading() {
1307 synchronized (sSync) {
1308 if (sPreloaded) {
1309 throw new IllegalStateException("Resources already preloaded");
1310 }
1311 sPreloaded = true;
1312 mPreloading = true;
1313 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1314 updateConfiguration(null, null, null);
Makoto Onuki1480b672017-07-14 08:42:50 -07001315
1316 if (TRACE_FOR_DETAILED_PRELOAD) {
1317 mPreloadTracingPreloadStartTime = SystemClock.uptimeMillis();
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001318 mPreloadTracingStartBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
1319 mPreloadTracingStartBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
Makoto Onuki1480b672017-07-14 08:42:50 -07001320 Log.d(TAG_PRELOAD, "Preload starting");
1321 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001322 }
1323 }
1324
1325 /**
1326 * Called by zygote when it is done preloading resources, to change back
1327 * to normal Resources operation.
1328 */
1329 void finishPreloading() {
1330 if (mPreloading) {
Makoto Onuki1480b672017-07-14 08:42:50 -07001331 if (TRACE_FOR_DETAILED_PRELOAD) {
1332 final long time = SystemClock.uptimeMillis() - mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001333 final long size =
1334 Bitmap.sPreloadTracingTotalBitmapsSize - mPreloadTracingStartBitmapSize;
1335 final long count = Bitmap.sPreloadTracingNumInstantiatedBitmaps
1336 - mPreloadTracingStartBitmapCount;
1337 Log.d(TAG_PRELOAD, "Preload finished, "
1338 + count + " bitmaps of " + size + " bytes in " + time + " ms");
Makoto Onuki1480b672017-07-14 08:42:50 -07001339 }
1340
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001341 mPreloading = false;
1342 flushLayoutCache();
1343 }
1344 }
1345
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001346 @AnyRes
1347 static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
Aurimas Liutikas02d7f452019-03-08 15:29:09 -08001348 if (set == null || !(set instanceof XmlBlock.Parser)) {
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001349 return ID_NULL;
1350 }
1351 return ((XmlBlock.Parser) set).getSourceResId();
1352 }
1353
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001354 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1355 return sPreloadedDrawables[0];
1356 }
1357
1358 ThemeImpl newThemeImpl() {
1359 return new ThemeImpl();
1360 }
1361
Adam Lesinski082614c2016-03-04 14:33:47 -08001362 /**
1363 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1364 */
1365 ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1366 ThemeImpl impl = new ThemeImpl();
1367 impl.mKey.setTo(key);
1368 impl.rebase();
1369 return impl;
1370 }
1371
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001372 public class ThemeImpl {
1373 /**
1374 * Unique key for the series of styles applied to this theme.
1375 */
1376 private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1377
1378 @SuppressWarnings("hiding")
1379 private final AssetManager mAssets;
1380 private final long mTheme;
1381
1382 /**
1383 * Resource identifier for the theme.
1384 */
1385 private int mThemeResId = 0;
1386
1387 /*package*/ ThemeImpl() {
1388 mAssets = ResourcesImpl.this.mAssets;
1389 mTheme = mAssets.createTheme();
1390 }
1391
1392 @Override
1393 protected void finalize() throws Throwable {
1394 super.finalize();
1395 mAssets.releaseTheme(mTheme);
1396 }
1397
1398 /*package*/ Resources.ThemeKey getKey() {
1399 return mKey;
1400 }
1401
1402 /*package*/ long getNativeTheme() {
1403 return mTheme;
1404 }
1405
1406 /*package*/ int getAppliedStyleResId() {
1407 return mThemeResId;
1408 }
1409
1410 void applyStyle(int resId, boolean force) {
1411 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001412 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001413 mThemeResId = resId;
1414 mKey.append(resId, force);
1415 }
1416 }
1417
1418 void setTo(ThemeImpl other) {
1419 synchronized (mKey) {
1420 synchronized (other.mKey) {
Ryan Mitchellb3ae42e2018-10-16 12:48:38 -07001421 mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001422
1423 mThemeResId = other.mThemeResId;
1424 mKey.setTo(other.getKey());
1425 }
1426 }
1427 }
1428
1429 @NonNull
1430 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001431 AttributeSet set,
1432 @StyleableRes int[] attrs,
1433 @AttrRes int defStyleAttr,
1434 @StyleRes int defStyleRes) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001435 synchronized (mKey) {
1436 final int len = attrs.length;
Eric Holk3509b622019-05-20 15:22:22 -07001437 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001438
1439 // XXX note that for now we only work with compiled XML files.
1440 // To support generic XML files we will need to manually parse
1441 // out the attributes from the XML file (applying type information
1442 // contained in the resources and such).
1443 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001444 mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
1445 array.mDataAddress, array.mIndicesAddress);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001446 array.mTheme = wrapper;
1447 array.mXml = parser;
Eric Holk3509b622019-05-20 15:22:22 -07001448 return array;
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001449 }
1450 }
1451
1452 @NonNull
1453 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001454 @NonNull int[] values,
1455 @NonNull int[] attrs) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001456 synchronized (mKey) {
1457 final int len = attrs.length;
1458 if (values == null || len != values.length) {
1459 throw new IllegalArgumentException(
1460 "Base attribute values must the same length as attrs");
1461 }
1462
1463 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001464 mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001465 array.mTheme = wrapper;
1466 array.mXml = null;
1467 return array;
1468 }
1469 }
1470
1471 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1472 synchronized (mKey) {
1473 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1474 }
1475 }
1476
1477 int[] getAllAttributes() {
1478 return mAssets.getStyleAttributes(getAppliedStyleResId());
1479 }
1480
Alan Viveretteac85f902016-03-11 15:15:51 -05001481 @Config int getChangingConfigurations() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001482 synchronized (mKey) {
Alan Viverette9ad386b2017-01-26 14:00:20 -05001483 final @NativeConfig int nativeChangingConfig =
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001484 AssetManager.nativeThemeGetChangingConfigurations(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001485 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1486 }
1487 }
1488
1489 public void dump(int priority, String tag, String prefix) {
1490 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001491 mAssets.dumpTheme(mTheme, priority, tag, prefix);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001492 }
1493 }
1494
1495 String[] getTheme() {
1496 synchronized (mKey) {
1497 final int N = mKey.mCount;
1498 final String[] themes = new String[N * 2];
1499 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1500 final int resId = mKey.mResId[j];
1501 final boolean forced = mKey.mForce[j];
1502 try {
1503 themes[i] = getResourceName(resId);
1504 } catch (NotFoundException e) {
1505 themes[i] = Integer.toHexString(i);
1506 }
1507 themes[i + 1] = forced ? "forced" : "not forced";
1508 }
1509 return themes;
1510 }
1511 }
1512
1513 /**
1514 * Rebases the theme against the parent Resource object's current
1515 * configuration by re-applying the styles passed to
1516 * {@link #applyStyle(int, boolean)}.
1517 */
1518 void rebase() {
1519 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001520 AssetManager.nativeThemeClear(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001521
1522 // Reapply the same styles in the same order.
1523 for (int i = 0; i < mKey.mCount; i++) {
1524 final int resId = mKey.mResId[i];
1525 final boolean force = mKey.mForce[i];
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001526 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001527 }
1528 }
1529 }
Aurimas Liutikas8f004c82019-01-17 17:20:10 -08001530
1531 /**
1532 * Returns the ordered list of resource ID that are considered when resolving attribute
1533 * values when making an equivalent call to
1534 * {@link #obtainStyledAttributes(Resources.Theme, AttributeSet, int[], int, int)}. The list
1535 * will include a set of explicit styles ({@code explicitStyleRes} and it will include the
1536 * default styles ({@code defStyleAttr} and {@code defStyleRes}).
1537 *
1538 * @param defStyleAttr An attribute in the current theme that contains a
1539 * reference to a style resource that supplies
1540 * defaults values for the TypedArray. Can be
1541 * 0 to not look for defaults.
1542 * @param defStyleRes A resource identifier of a style resource that
1543 * supplies default values for the TypedArray,
1544 * used only if defStyleAttr is 0 or can not be found
1545 * in the theme. Can be 0 to not look for defaults.
1546 * @param explicitStyleRes A resource identifier of an explicit style resource.
1547 * @return ordered list of resource ID that are considered when resolving attribute values.
1548 */
Aurimas Liutikas6c15bc02019-02-28 11:16:06 -08001549 @Nullable
Aurimas Liutikas8f004c82019-01-17 17:20:10 -08001550 public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
1551 @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
1552 synchronized (mKey) {
1553 return mAssets.getAttributeResolutionStack(
1554 mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
1555 }
1556 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001557 }
Sunny Goyal99b25d22017-11-01 11:58:13 -07001558
1559 private static class LookupStack {
1560
1561 // Pick a reasonable default size for the array, it is grown as needed.
1562 private int[] mIds = new int[4];
1563 private int mSize = 0;
1564
1565 public void push(int id) {
1566 mIds = GrowingArrayUtils.append(mIds, mSize, id);
1567 mSize++;
1568 }
1569
1570 public boolean contains(int id) {
1571 for (int i = 0; i < mSize; i++) {
1572 if (mIds[i] == id) {
1573 return true;
1574 }
1575 }
1576 return false;
1577 }
1578
1579 public void pop() {
1580 mSize--;
1581 }
1582 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001583}