blob: 633966c387869fe7eeac7c0b2dc71835cf5dcc6d [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
Makoto Onuki1480b672017-07-14 08:42:50 -070060import org.xmlpull.v1.XmlPullParser;
61import org.xmlpull.v1.XmlPullParserException;
62
Clara Bayarri18e9f9f2016-12-19 16:20:29 +000063import java.io.IOException;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080064import java.io.InputStream;
65import java.util.Arrays;
66import java.util.Locale;
67
68/**
Adam Lesinski082614c2016-03-04 14:33:47 -080069 * The implementation of Resource access. This class contains the AssetManager and all caches
70 * associated with it.
71 *
72 * {@link Resources} is just a thing wrapper around this class. When a configuration change
73 * occurs, clients can retain the same {@link Resources} reference because the underlying
74 * {@link ResourcesImpl} object will be updated or re-created.
75 *
Adam Lesinskifb302cc2016-02-29 16:50:38 -080076 * @hide
77 */
78public class ResourcesImpl {
79 static final String TAG = "Resources";
80
81 private static final boolean DEBUG_LOAD = false;
82 private static final boolean DEBUG_CONFIG = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080083
Makoto Onuki1480b672017-07-14 08:42:50 -070084 static final String TAG_PRELOAD = TAG + ".preload";
85
Mathew Inwood5c0d3542018-08-14 13:54:31 +010086 @UnsupportedAppUsage
Makoto Onuki1480b672017-07-14 08:42:50 -070087 private static final boolean TRACE_FOR_PRELOAD = false; // Do we still need it?
Mathew Inwood5c0d3542018-08-14 13:54:31 +010088 @UnsupportedAppUsage
Makoto Onuki1480b672017-07-14 08:42:50 -070089 private static final boolean TRACE_FOR_MISS_PRELOAD = false; // Do we still need it?
90
91 public static final boolean TRACE_FOR_DETAILED_PRELOAD =
92 SystemProperties.getBoolean("debug.trace_resource_preload", false);
93
94 /** Used only when TRACE_FOR_DETAILED_PRELOAD is true. */
95 private static int sPreloadTracingNumLoadedDrawables;
96 private long mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -070097 private long mPreloadTracingStartBitmapSize;
98 private long mPreloadTracingStartBitmapCount;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080099
100 private static final int ID_OTHER = 0x01000004;
101
102 private static final Object sSync = new Object();
103
104 private static boolean sPreloaded;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100105 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800106 private boolean mPreloading;
107
108 // Information about preloaded resources. Note that they are not
109 // protected by a lock, because while preloading in zygote we are all
110 // single-threaded, and after that these are immutable.
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100111 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800112 private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100113 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800114 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
115 = new LongSparseArray<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100116 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800117 private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
118 sPreloadedComplexColors = new LongSparseArray<>();
119
120 /** Lock object used to protect access to caches and configuration. */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100121 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800122 private final Object mAccessLock = new Object();
123
124 // These are protected by mAccessLock.
125 private final Configuration mTmpConfig = new Configuration();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100126 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800127 private final DrawableCache mDrawableCache = new DrawableCache();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100128 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800129 private final DrawableCache mColorDrawableCache = new DrawableCache();
130 private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
131 new ConfigurationBoundResourceCache<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100132 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800133 private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
134 new ConfigurationBoundResourceCache<>();
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100135 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800136 private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
137 new ConfigurationBoundResourceCache<>();
138
Sunny Goyal99b25d22017-11-01 11:58:13 -0700139 // A stack of all the resourceIds already referenced when parsing a resource. This is used to
140 // detect circular references in the xml.
141 // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
142 // calls to ResourcesImpl
143 private final ThreadLocal<LookupStack> mLookupStack =
144 ThreadLocal.withInitial(() -> new LookupStack());
145
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800146 /** Size of the cyclical cache used to map XML files to blocks. */
147 private static final int XML_BLOCK_CACHE_SIZE = 4;
148
149 // Cyclical cache used for recently-accessed XML files.
150 private int mLastCachedXmlBlockIndex = -1;
151 private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
152 private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
153 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
154
155
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100156 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800157 final AssetManager mAssets;
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700158 private final DisplayMetrics mMetrics = new DisplayMetrics();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700159 private final DisplayAdjustments mDisplayAdjustments;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800160
161 private PluralRules mPluralRule;
162
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100163 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800164 private final Configuration mConfiguration = new Configuration();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800165
166 static {
167 sPreloadedDrawables = new LongSparseArray[2];
168 sPreloadedDrawables[0] = new LongSparseArray<>();
169 sPreloadedDrawables[1] = new LongSparseArray<>();
170 }
171
172 /**
173 * Creates a new ResourcesImpl object with CompatibilityInfo.
174 *
175 * @param assets Previously created AssetManager.
176 * @param metrics Current display metrics to consider when
177 * selecting/computing resource values.
178 * @param config Desired device configuration to consider when
179 * selecting/computing resource values (optional).
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700180 * @param displayAdjustments this resource's Display override and compatibility info.
181 * Must not be null.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800182 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100183 @UnsupportedAppUsage
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700184 public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700185 @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800186 mAssets = assets;
187 mMetrics.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700188 mDisplayAdjustments = displayAdjustments;
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700189 mConfiguration.setToDefaults();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700190 updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800191 }
192
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700193 public DisplayAdjustments getDisplayAdjustments() {
194 return mDisplayAdjustments;
195 }
196
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100197 @UnsupportedAppUsage
Adam Lesinski082614c2016-03-04 14:33:47 -0800198 public AssetManager getAssets() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800199 return mAssets;
200 }
201
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100202 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800203 DisplayMetrics getDisplayMetrics() {
204 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
205 + "x" + mMetrics.heightPixels + " " + mMetrics.density);
206 return mMetrics;
207 }
208
209 Configuration getConfiguration() {
210 return mConfiguration;
211 }
212
213 Configuration[] getSizeConfigurations() {
214 return mAssets.getSizeConfigurations();
215 }
216
217 CompatibilityInfo getCompatibilityInfo() {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700218 return mDisplayAdjustments.getCompatibilityInfo();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800219 }
220
221 private PluralRules getPluralRule() {
222 synchronized (sSync) {
223 if (mPluralRule == null) {
224 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
225 }
226 return mPluralRule;
227 }
228 }
229
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100230 @UnsupportedAppUsage
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800231 void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
232 throws NotFoundException {
233 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
234 if (found) {
235 return;
236 }
237 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
238 }
239
240 void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
Adam Lesinski082614c2016-03-04 14:33:47 -0800241 boolean resolveRefs) throws NotFoundException {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800242 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
243 if (found) {
244 return;
245 }
246 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
247 }
248
249 void getValue(String name, TypedValue outValue, boolean resolveRefs)
250 throws NotFoundException {
251 int id = getIdentifier(name, "string", null);
252 if (id != 0) {
253 getValue(id, outValue, resolveRefs);
254 return;
255 }
256 throw new NotFoundException("String resource name " + name);
257 }
258
259 int getIdentifier(String name, String defType, String defPackage) {
260 if (name == null) {
261 throw new NullPointerException("name is null");
262 }
263 try {
264 return Integer.parseInt(name);
265 } catch (Exception e) {
266 // Ignore
267 }
268 return mAssets.getResourceIdentifier(name, defType, defPackage);
269 }
270
271 @NonNull
272 String getResourceName(@AnyRes int resid) throws NotFoundException {
273 String str = mAssets.getResourceName(resid);
274 if (str != null) return str;
275 throw new NotFoundException("Unable to find resource ID #0x"
276 + Integer.toHexString(resid));
277 }
278
279 @NonNull
280 String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
281 String str = mAssets.getResourcePackageName(resid);
282 if (str != null) return str;
283 throw new NotFoundException("Unable to find resource ID #0x"
284 + Integer.toHexString(resid));
285 }
286
287 @NonNull
288 String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
289 String str = mAssets.getResourceTypeName(resid);
290 if (str != null) return str;
291 throw new NotFoundException("Unable to find resource ID #0x"
292 + Integer.toHexString(resid));
293 }
294
295 @NonNull
296 String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
297 String str = mAssets.getResourceEntryName(resid);
298 if (str != null) return str;
299 throw new NotFoundException("Unable to find resource ID #0x"
300 + Integer.toHexString(resid));
301 }
302
303 @NonNull
Winson2f3669b2019-01-11 11:28:34 -0800304 String getLastResourceResolution() throws NotFoundException {
305 String str = mAssets.getLastResourceResolution();
306 if (str != null) return str;
307 throw new NotFoundException("Associated AssetManager hasn't resolved a resource");
308 }
309
310 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800311 CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
312 PluralRules rule = getPluralRule();
313 CharSequence res = mAssets.getResourceBagText(id,
314 attrForQuantityCode(rule.select(quantity)));
315 if (res != null) {
316 return res;
317 }
318 res = mAssets.getResourceBagText(id, ID_OTHER);
319 if (res != null) {
320 return res;
321 }
322 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
323 + " quantity=" + quantity
324 + " item=" + rule.select(quantity));
325 }
326
327 private static int attrForQuantityCode(String quantityCode) {
328 switch (quantityCode) {
329 case PluralRules.KEYWORD_ZERO: return 0x01000005;
330 case PluralRules.KEYWORD_ONE: return 0x01000006;
331 case PluralRules.KEYWORD_TWO: return 0x01000007;
332 case PluralRules.KEYWORD_FEW: return 0x01000008;
333 case PluralRules.KEYWORD_MANY: return 0x01000009;
334 default: return ID_OTHER;
335 }
336 }
337
338 @NonNull
339 AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
340 throws NotFoundException {
341 getValue(id, tempValue, true);
342 try {
343 return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
344 } catch (Exception e) {
345 throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
346 + "resource ID #0x" + Integer.toHexString(id), e);
347 }
348 }
349
350 @NonNull
351 InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
352 getValue(id, value, true);
353 try {
354 return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
355 AssetManager.ACCESS_STREAMING);
356 } catch (Exception e) {
Christopher Tatef135b272016-05-27 17:10:30 -0700357 // Note: value.string might be null
358 NotFoundException rnf = new NotFoundException("File "
359 + (value.string == null ? "(null)" : value.string.toString())
360 + " from drawable resource ID #0x" + Integer.toHexString(id));
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800361 rnf.initCause(e);
362 throw rnf;
363 }
364 }
365
366 ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
367 return mAnimatorCache;
368 }
369
370 ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
371 return mStateListAnimatorCache;
372 }
373
Adam Lesinski082614c2016-03-04 14:33:47 -0800374 public void updateConfiguration(Configuration config, DisplayMetrics metrics,
375 CompatibilityInfo compat) {
Adam Lesinski991357f2016-05-10 14:00:03 -0700376 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
377 try {
378 synchronized (mAccessLock) {
379 if (false) {
380 Slog.i(TAG, "**** Updating config of " + this + ": old config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700381 + mConfiguration + " old compat is "
382 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700383 Slog.i(TAG, "**** Updating config of " + this + ": new config is "
384 + config + " new compat is " + compat);
385 }
386 if (compat != null) {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700387 mDisplayAdjustments.setCompatibilityInfo(compat);
Adam Lesinski991357f2016-05-10 14:00:03 -0700388 }
389 if (metrics != null) {
390 mMetrics.setTo(metrics);
391 }
392 // NOTE: We should re-arrange this code to create a Display
393 // with the CompatibilityInfo that is used everywhere we deal
394 // with the display in relation to this app, rather than
395 // doing the conversion here. This impl should be okay because
396 // we make sure to return a compatible display in the places
397 // where there are public APIs to retrieve the display... but
Adam Lesinskib61e4052016-05-19 18:23:05 -0700398 // it would be cleaner and more maintainable to just be
Adam Lesinski991357f2016-05-10 14:00:03 -0700399 // consistently dealing with a compatible display everywhere in
400 // the framework.
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700401 mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800402
Adam Lesinski991357f2016-05-10 14:00:03 -0700403 final @Config int configChanges = calcConfigChanges(config);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800404
Adam Lesinskib61e4052016-05-19 18:23:05 -0700405 // If even after the update there are no Locales set, grab the default locales.
Adam Lesinski991357f2016-05-10 14:00:03 -0700406 LocaleList locales = mConfiguration.getLocales();
407 if (locales.isEmpty()) {
Adam Lesinskib61e4052016-05-19 18:23:05 -0700408 locales = LocaleList.getDefault();
Adam Lesinski991357f2016-05-10 14:00:03 -0700409 mConfiguration.setLocales(locales);
410 }
Adam Lesinskib61e4052016-05-19 18:23:05 -0700411
412 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
413 if (locales.size() > 1) {
414 // The LocaleList has changed. We must query the AssetManager's available
415 // Locales and figure out the best matching Locale in the new LocaleList.
416 String[] availableLocales = mAssets.getNonSystemLocales();
417 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
418 // No app defined locales, so grab the system locales.
419 availableLocales = mAssets.getLocales();
420 if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
421 availableLocales = null;
422 }
423 }
424
425 if (availableLocales != null) {
426 final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
427 availableLocales);
428 if (bestLocale != null && bestLocale != locales.get(0)) {
429 mConfiguration.setLocales(new LocaleList(bestLocale, locales));
430 }
431 }
432 }
433 }
434
Adam Lesinski991357f2016-05-10 14:00:03 -0700435 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
436 mMetrics.densityDpi = mConfiguration.densityDpi;
437 mMetrics.density =
438 mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
439 }
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700440
441 // Protect against an unset fontScale.
442 mMetrics.scaledDensity = mMetrics.density *
443 (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
Adam Lesinski991357f2016-05-10 14:00:03 -0700444
445 final int width, height;
446 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
447 width = mMetrics.widthPixels;
448 height = mMetrics.heightPixels;
449 } else {
450 //noinspection SuspiciousNameCombination
451 width = mMetrics.heightPixels;
452 //noinspection SuspiciousNameCombination
453 height = mMetrics.widthPixels;
454 }
455
456 final int keyboardHidden;
457 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
458 && mConfiguration.hardKeyboardHidden
459 == Configuration.HARDKEYBOARDHIDDEN_YES) {
460 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
461 } else {
462 keyboardHidden = mConfiguration.keyboardHidden;
463 }
464
465 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
Adam Lesinskib61e4052016-05-19 18:23:05 -0700466 adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
Adam Lesinski991357f2016-05-10 14:00:03 -0700467 mConfiguration.orientation,
468 mConfiguration.touchscreen,
469 mConfiguration.densityDpi, mConfiguration.keyboard,
470 keyboardHidden, mConfiguration.navigation, width, height,
471 mConfiguration.smallestScreenWidthDp,
472 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
473 mConfiguration.screenLayout, mConfiguration.uiMode,
Romain Guy408afbf2017-01-25 10:23:03 -0800474 mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
Adam Lesinski991357f2016-05-10 14:00:03 -0700475
476 if (DEBUG_CONFIG) {
477 Slog.i(TAG, "**** Updating config of " + this + ": final config is "
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700478 + mConfiguration + " final compat is "
479 + mDisplayAdjustments.getCompatibilityInfo());
Adam Lesinski991357f2016-05-10 14:00:03 -0700480 }
481
482 mDrawableCache.onConfigurationChange(configChanges);
483 mColorDrawableCache.onConfigurationChange(configChanges);
484 mComplexColorCache.onConfigurationChange(configChanges);
485 mAnimatorCache.onConfigurationChange(configChanges);
486 mStateListAnimatorCache.onConfigurationChange(configChanges);
487
488 flushLayoutCache();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800489 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700490 synchronized (sSync) {
491 if (mPluralRule != null) {
492 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
493 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800494 }
Adam Lesinski991357f2016-05-10 14:00:03 -0700495 } finally {
496 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800497 }
498 }
499
500 /**
Alan Viveretteac85f902016-03-11 15:15:51 -0500501 * Applies the new configuration, returning a bitmask of the changes
502 * between the old and new configurations.
503 *
504 * @param config the new configuration
505 * @return bitmask of config changes
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800506 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500507 public @Config int calcConfigChanges(@Nullable Configuration config) {
508 if (config == null) {
509 // If there is no configuration, assume all flags have changed.
510 return 0xFFFFFFFF;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800511 }
Alan Viveretteac85f902016-03-11 15:15:51 -0500512
513 mTmpConfig.setTo(config);
514 int density = config.densityDpi;
515 if (density == Configuration.DENSITY_DPI_UNDEFINED) {
516 density = mMetrics.noncompatDensityDpi;
517 }
518
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700519 mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
Alan Viveretteac85f902016-03-11 15:15:51 -0500520
521 if (mTmpConfig.getLocales().isEmpty()) {
522 mTmpConfig.setLocales(LocaleList.getDefault());
523 }
524 return mConfiguration.updateFrom(mTmpConfig);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800525 }
526
527 /**
528 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
529 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
530 *
531 * All released versions of android prior to "L" used the deprecated language
532 * tags, so we will need to support them for backwards compatibility.
533 *
534 * Note that this conversion needs to take place *after* the call to
535 * {@code toLanguageTag} because that will convert all the deprecated codes to
536 * the new ones, even if they're set manually.
537 */
538 private static String adjustLanguageTag(String languageTag) {
539 final int separator = languageTag.indexOf('-');
540 final String language;
541 final String remainder;
542
543 if (separator == -1) {
544 language = languageTag;
545 remainder = "";
546 } else {
547 language = languageTag.substring(0, separator);
548 remainder = languageTag.substring(separator);
549 }
550
551 return Locale.adjustLanguageCode(language) + remainder;
552 }
553
554 /**
555 * Call this to remove all cached loaded layout resources from the
556 * Resources object. Only intended for use with performance testing
557 * tools.
558 */
559 public void flushLayoutCache() {
560 synchronized (mCachedXmlBlocks) {
561 Arrays.fill(mCachedXmlBlockCookies, 0);
562 Arrays.fill(mCachedXmlBlockFiles, null);
563
564 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
565 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
566 final XmlBlock oldBlock = cachedXmlBlocks[i];
567 if (oldBlock != null) {
568 oldBlock.close();
569 }
570 }
571 Arrays.fill(cachedXmlBlocks, null);
572 }
573 }
574
Chris Craik1194b0b2018-03-23 13:36:24 -0700575 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700576 Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
577 int density, @Nullable Resources.Theme theme)
578 throws NotFoundException {
579 // If the drawable's XML lives in our current density qualifier,
580 // it's okay to use a scaled version from the cache. Otherwise, we
581 // need to actually load the drawable from XML.
582 final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
583
584 // Pretend the requested density is actually the display density. If
585 // the drawable returned is not the requested density, then force it
586 // to be scaled later by dividing its density by the ratio of
587 // requested density to actual device density. Drawables that have
588 // undefined density or no density don't need to be handled here.
589 if (density > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
590 if (value.density == density) {
591 value.density = mMetrics.densityDpi;
592 } else {
593 value.density = (value.density * mMetrics.densityDpi) / density;
594 }
595 }
596
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800597 try {
598 if (TRACE_FOR_PRELOAD) {
599 // Log only framework resources
600 if ((id >>> 24) == 0x1) {
601 final String name = getResourceName(id);
602 if (name != null) {
603 Log.d("PreloadDrawable", name);
604 }
605 }
606 }
607
608 final boolean isColorDrawable;
609 final DrawableCache caches;
610 final long key;
611 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
612 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
613 isColorDrawable = true;
614 caches = mColorDrawableCache;
615 key = value.data;
616 } else {
617 isColorDrawable = false;
618 caches = mDrawableCache;
619 key = (((long) value.assetCookie) << 32) | value.data;
620 }
621
622 // First, check whether we have a cached version of this drawable
623 // that was inflated against the specified theme. Skip the cache if
624 // we're currently preloading or we're not using the cache.
625 if (!mPreloading && useCache) {
626 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
627 if (cachedDrawable != null) {
Alan Viverette58857c82016-10-29 00:47:45 +0100628 cachedDrawable.setChangingConfigurations(value.changingConfigurations);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800629 return cachedDrawable;
630 }
631 }
632
633 // Next, check preloaded drawables. Preloaded drawables may contain
634 // unresolved theme attributes.
635 final Drawable.ConstantState cs;
636 if (isColorDrawable) {
637 cs = sPreloadedColorDrawables.get(key);
638 } else {
639 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
640 }
641
642 Drawable dr;
ztenghuiee7e8f12017-05-16 15:30:50 -0700643 boolean needsNewDrawableAfterCache = false;
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800644 if (cs != null) {
Makoto Onuki1480b672017-07-14 08:42:50 -0700645 if (TRACE_FOR_DETAILED_PRELOAD) {
646 // Log only framework resources
647 if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
648 final String name = getResourceName(id);
649 if (name != null) {
650 Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
651 + Integer.toHexString(id) + " " + name);
652 }
653 }
654 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800655 dr = cs.newDrawable(wrapper);
656 } else if (isColorDrawable) {
657 dr = new ColorDrawable(value.data);
658 } else {
Chris Craikceb26932018-02-01 15:51:34 -0800659 dr = loadDrawableForCookie(wrapper, value, id, density);
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800660 }
ztenghuiee7e8f12017-05-16 15:30:50 -0700661 // DrawableContainer' constant state has drawables instances. In order to leave the
662 // constant state intact in the cache, we need to create a new DrawableContainer after
663 // added to cache.
664 if (dr instanceof DrawableContainer) {
665 needsNewDrawableAfterCache = true;
666 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800667
668 // Determine if the drawable has unresolved theme attributes. If it
669 // does, we'll need to apply a theme and store it in a theme-specific
670 // cache.
671 final boolean canApplyTheme = dr != null && dr.canApplyTheme();
672 if (canApplyTheme && theme != null) {
673 dr = dr.mutate();
674 dr.applyTheme(theme);
675 dr.clearMutated();
676 }
677
678 // If we were able to obtain a drawable, store it in the appropriate
679 // cache: preload, not themed, null theme, or theme-specific. Don't
680 // pollute the cache with drawables loaded from a foreign density.
Alan Viverette58857c82016-10-29 00:47:45 +0100681 if (dr != null) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800682 dr.setChangingConfigurations(value.changingConfigurations);
Alan Viverette58857c82016-10-29 00:47:45 +0100683 if (useCache) {
684 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
ztenghuiee7e8f12017-05-16 15:30:50 -0700685 if (needsNewDrawableAfterCache) {
686 Drawable.ConstantState state = dr.getConstantState();
687 if (state != null) {
688 dr = state.newDrawable(wrapper);
689 }
690 }
Alan Viverette58857c82016-10-29 00:47:45 +0100691 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800692 }
693
694 return dr;
695 } catch (Exception e) {
696 String name;
697 try {
698 name = getResourceName(id);
699 } catch (NotFoundException e2) {
700 name = "(missing name)";
701 }
702
703 // The target drawable might fail to load for any number of
704 // reasons, but we always want to include the resource name.
705 // Since the client already expects this method to throw a
706 // NotFoundException, just throw one of those.
707 final NotFoundException nfe = new NotFoundException("Drawable " + name
708 + " with resource ID #0x" + Integer.toHexString(id), e);
709 nfe.setStackTrace(new StackTraceElement[0]);
710 throw nfe;
711 }
712 }
713
714 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Adam Lesinski082614c2016-03-04 14:33:47 -0800715 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800716 final Drawable.ConstantState cs = dr.getConstantState();
717 if (cs == null) {
718 return;
719 }
720
721 if (mPreloading) {
722 final int changingConfigs = cs.getChangingConfigurations();
723 if (isColorDrawable) {
724 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
725 sPreloadedColorDrawables.put(key, cs);
726 }
727 } else {
728 if (verifyPreloadConfig(
Alan Viverettea8a66cc2017-03-20 15:00:51 -0400729 changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
730 if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800731 // If this resource does not vary based on layout direction,
732 // we can put it in all of the preload maps.
733 sPreloadedDrawables[0].put(key, cs);
734 sPreloadedDrawables[1].put(key, cs);
735 } else {
736 // Otherwise, only in the layout dir we loaded it for.
737 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
738 }
739 }
740 }
741 } else {
742 synchronized (mAccessLock) {
743 caches.put(key, theme, cs, usesTheme);
744 }
745 }
746 }
747
Alan Viveretteac85f902016-03-11 15:15:51 -0500748 private boolean verifyPreloadConfig(@Config int changingConfigurations,
749 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800750 // We allow preloading of resources even if they vary by font scale (which
751 // doesn't impact resource selection) or density (which we handle specially by
752 // simply turning off all preloading), as well as any other configs specified
753 // by the caller.
754 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
755 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
756 String resName;
757 try {
758 resName = getResourceName(resourceId);
759 } catch (NotFoundException e) {
760 resName = "?";
761 }
762 // This should never happen in production, so we should log a
763 // warning even if we're not debugging.
764 Log.w(TAG, "Preloaded " + name + " resource #0x"
765 + Integer.toHexString(resourceId)
766 + " (" + resName + ") that varies with configuration!!");
767 return false;
768 }
769 if (TRACE_FOR_PRELOAD) {
770 String resName;
771 try {
772 resName = getResourceName(resourceId);
773 } catch (NotFoundException e) {
774 resName = "?";
775 }
776 Log.w(TAG, "Preloading " + name + " resource #0x"
777 + Integer.toHexString(resourceId)
778 + " (" + resName + ")");
779 }
780 return true;
781 }
782
783 /**
Leon Scroggins III513031d2018-02-15 15:31:45 -0500784 * Loads a Drawable from an encoded image stream, or null.
785 *
786 * This call will handle closing ais.
787 */
Chris Craik1194b0b2018-03-23 13:36:24 -0700788 @Nullable
Leon Scroggins III513031d2018-02-15 15:31:45 -0500789 private Drawable decodeImageDrawable(@NonNull AssetInputStream ais,
790 @NonNull Resources wrapper, @NonNull TypedValue value) {
791 ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
792 wrapper, value);
793 try {
794 return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
795 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
796 });
797 } catch (IOException ioe) {
798 // This is okay. This may be something that ImageDecoder does not
799 // support, like SVG.
800 return null;
801 }
802 }
803
804 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800805 * Loads a drawable from XML or resources stream.
Chris Craik1194b0b2018-03-23 13:36:24 -0700806 *
807 * @return Drawable, or null if Drawable cannot be decoded.
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800808 */
Chris Craik1194b0b2018-03-23 13:36:24 -0700809 @Nullable
Adam Lesinski50954d22017-04-14 18:41:52 -0700810 private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
Chris Craikceb26932018-02-01 15:51:34 -0800811 int id, int density) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800812 if (value.string == null) {
813 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
814 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
815 }
816
817 final String file = value.string.toString();
818
819 if (TRACE_FOR_MISS_PRELOAD) {
820 // Log only framework resources
821 if ((id >>> 24) == 0x1) {
822 final String name = getResourceName(id);
823 if (name != null) {
824 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
825 + ": " + name + " at " + file);
826 }
827 }
828 }
829
Chris Craikceb26932018-02-01 15:51:34 -0800830 // For preload tracing.
Makoto Onuki1480b672017-07-14 08:42:50 -0700831 long startTime = 0;
832 int startBitmapCount = 0;
833 long startBitmapSize = 0;
Chris Craikceb26932018-02-01 15:51:34 -0800834 int startDrawableCount = 0;
Makoto Onuki1480b672017-07-14 08:42:50 -0700835 if (TRACE_FOR_DETAILED_PRELOAD) {
836 startTime = System.nanoTime();
837 startBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
838 startBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
Chris Craikceb26932018-02-01 15:51:34 -0800839 startDrawableCount = sPreloadTracingNumLoadedDrawables;
Makoto Onuki1480b672017-07-14 08:42:50 -0700840 }
841
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800842 if (DEBUG_LOAD) {
843 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
844 }
845
Chris Craikceb26932018-02-01 15:51:34 -0800846
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800847 final Drawable dr;
848
849 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
Sunny Goyal99b25d22017-11-01 11:58:13 -0700850 LookupStack stack = mLookupStack.get();
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800851 try {
Sunny Goyal99b25d22017-11-01 11:58:13 -0700852 // Perform a linear search to check if we have already referenced this resource before.
853 if (stack.contains(id)) {
854 throw new Exception("Recursive reference in drawable");
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800855 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700856 stack.push(id);
857 try {
858 if (file.endsWith(".xml")) {
Ashley Rosee3b13572018-08-21 16:57:30 -0400859 if (file.startsWith("res/color/")) {
Winson9653d662019-01-17 16:47:36 -0800860 dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
Ashley Rosee3b13572018-08-21 16:57:30 -0400861 } else {
Winson9653d662019-01-17 16:47:36 -0800862 dr = loadXmlDrawable(wrapper, value, id, density, file);
Ashley Rosee3b13572018-08-21 16:57:30 -0400863 }
Sunny Goyal99b25d22017-11-01 11:58:13 -0700864 } else {
865 final InputStream is = mAssets.openNonAsset(
866 value.assetCookie, file, AssetManager.ACCESS_STREAMING);
Leon Scroggins III513031d2018-02-15 15:31:45 -0500867 AssetInputStream ais = (AssetInputStream) is;
868 dr = decodeImageDrawable(ais, wrapper, value);
Sunny Goyal99b25d22017-11-01 11:58:13 -0700869 }
870 } finally {
871 stack.pop();
872 }
Hyunyoung Songaff04c32017-10-10 10:35:59 -0700873 } catch (Exception | StackOverflowError e) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800874 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
875 final NotFoundException rnf = new NotFoundException(
876 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
877 rnf.initCause(e);
878 throw rnf;
879 }
880 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
881
Makoto Onuki1480b672017-07-14 08:42:50 -0700882 if (TRACE_FOR_DETAILED_PRELOAD) {
883 if (((id >>> 24) == 0x1)) {
884 final String name = getResourceName(id);
885 if (name != null) {
886 final long time = System.nanoTime() - startTime;
887 final int loadedBitmapCount =
888 Bitmap.sPreloadTracingNumInstantiatedBitmaps - startBitmapCount;
889 final long loadedBitmapSize =
890 Bitmap.sPreloadTracingTotalBitmapsSize - startBitmapSize;
891 final int loadedDrawables =
Chris Craikceb26932018-02-01 15:51:34 -0800892 sPreloadTracingNumLoadedDrawables - startDrawableCount;
Makoto Onuki1480b672017-07-14 08:42:50 -0700893
894 sPreloadTracingNumLoadedDrawables++;
895
896 final boolean isRoot = (android.os.Process.myUid() == 0);
897
898 Log.d(TAG_PRELOAD,
899 (isRoot ? "Preloaded FW drawable #"
900 : "Loaded non-preloaded FW drawable #")
901 + Integer.toHexString(id)
902 + " " + name
903 + " " + file
904 + " " + dr.getClass().getCanonicalName()
905 + " #nested_drawables= " + loadedDrawables
906 + " #bitmaps= " + loadedBitmapCount
907 + " total_bitmap_size= " + loadedBitmapSize
908 + " in[us] " + (time / 1000));
909 }
910 }
911 }
912
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800913 return dr;
914 }
915
Winson9653d662019-01-17 16:47:36 -0800916 private Drawable loadColorOrXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
917 int id, int density, String file) {
918 try {
919 ColorStateList csl = loadColorStateList(wrapper, value, id, null);
920 return new ColorStateListDrawable(csl);
921 } catch (NotFoundException originalException) {
922 // If we fail to load as color, try as normal XML drawable
923 try {
924 return loadXmlDrawable(wrapper, value, id, density, file);
925 } catch (Exception ignored) {
926 // If fallback also fails, throw the original exception
927 throw originalException;
928 }
929 }
930 }
931
932 private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
933 int id, int density, String file)
934 throws IOException, XmlPullParserException {
935 try (
936 XmlResourceParser rp =
937 loadXmlResourceParser(file, id, value.assetCookie, "drawable")
938 ) {
939 return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
940 }
941 }
942
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800943 /**
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000944 * Loads a font from XML or resources stream.
945 */
946 @Nullable
Clara Bayarried00bfd2017-01-20 14:58:21 +0000947 public Typeface loadFont(Resources wrapper, TypedValue value, int id) {
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000948 if (value.string == null) {
949 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
950 + Integer.toHexString(id) + ") is not a Font: " + value);
951 }
952
953 final String file = value.string.toString();
Seigo Nonaka2ea169a2017-05-15 16:25:11 -0700954 if (!file.startsWith("res/")) {
955 return null;
956 }
957
Clara Bayarrib12397e2017-01-27 11:02:48 +0000958 Typeface cached = Typeface.findFromCache(mAssets, file);
Clara Bayarried00bfd2017-01-20 14:58:21 +0000959 if (cached != null) {
960 return cached;
961 }
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000962
963 if (DEBUG_LOAD) {
964 Log.v(TAG, "Loading font for cookie " + value.assetCookie + ": " + file);
965 }
966
967 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
968 try {
Clara Bayarried00bfd2017-01-20 14:58:21 +0000969 if (file.endsWith("xml")) {
970 final XmlResourceParser rp = loadXmlResourceParser(
971 file, id, value.assetCookie, "font");
Seigo Nonakaac873c92017-03-07 15:34:53 -0800972 final FontResourcesParser.FamilyResourceEntry familyEntry =
973 FontResourcesParser.parse(rp, wrapper);
974 if (familyEntry == null) {
Seigo Nonakaac873c92017-03-07 15:34:53 -0800975 return null;
976 }
977 return Typeface.createFromResources(familyEntry, mAssets, file);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000978 }
Seigo Nonakaee4b6d82018-10-25 13:12:03 -0700979 return new Typeface.Builder(mAssets, file, false /* isAsset */, value.assetCookie)
980 .build();
Clara Bayarried00bfd2017-01-20 14:58:21 +0000981 } catch (XmlPullParserException e) {
982 Log.e(TAG, "Failed to parse xml resource " + file, e);
983 } catch (IOException e) {
984 Log.e(TAG, "Failed to read xml resource " + file, e);
Clara Bayarri18e9f9f2016-12-19 16:20:29 +0000985 } finally {
986 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
987 }
988 return null;
989 }
990
991 /**
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800992 * Given the value and id, we can get the XML filename as in value.data, based on that, we
993 * first try to load CSL from the cache. If not found, try to get from the constant state.
994 * Last, parse the XML and generate the CSL.
995 */
Chris Craik2e41d492018-02-09 14:21:37 -0800996 @Nullable
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800997 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
Adam Lesinski082614c2016-03-04 14:33:47 -0800998 TypedValue value, int id) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -0800999 final long key = (((long) value.assetCookie) << 32) | value.data;
1000 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
1001 ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
1002 if (complexColor != null) {
1003 return complexColor;
1004 }
1005
1006 final android.content.res.ConstantState<ComplexColor> factory =
1007 sPreloadedComplexColors.get(key);
1008
1009 if (factory != null) {
1010 complexColor = factory.newInstance(wrapper, theme);
1011 }
1012 if (complexColor == null) {
1013 complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
1014 }
1015
Chris Craik2e41d492018-02-09 14:21:37 -08001016 if (complexColor != null) {
1017 complexColor.setBaseChangingConfigurations(value.changingConfigurations);
Alan Viverette0b9295d2016-03-10 10:30:08 -05001018
Chris Craik2e41d492018-02-09 14:21:37 -08001019 if (mPreloading) {
1020 if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
1021 0, value.resourceId, "color")) {
1022 sPreloadedComplexColors.put(key, complexColor.getConstantState());
1023 }
1024 } else {
1025 cache.put(key, theme, complexColor.getConstantState());
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001026 }
1027 }
1028 return complexColor;
1029 }
1030
1031 @Nullable
1032 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001033 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001034 if (TRACE_FOR_PRELOAD) {
1035 // Log only framework resources
1036 if ((id >>> 24) == 0x1) {
1037 final String name = getResourceName(id);
1038 if (name != null) android.util.Log.d("loadComplexColor", name);
1039 }
1040 }
1041
1042 final long key = (((long) value.assetCookie) << 32) | value.data;
1043
1044 // Handle inline color definitions.
1045 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
1046 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
1047 return getColorStateListFromInt(value, key);
1048 }
1049
1050 final String file = value.string.toString();
1051
1052 ComplexColor complexColor;
1053 if (file.endsWith(".xml")) {
1054 try {
1055 complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1056 } catch (Exception e) {
1057 final NotFoundException rnf = new NotFoundException(
1058 "File " + file + " from complex color resource ID #0x"
1059 + Integer.toHexString(id));
1060 rnf.initCause(e);
1061 throw rnf;
1062 }
1063 } else {
1064 throw new NotFoundException(
1065 "File " + file + " from drawable resource ID #0x"
1066 + Integer.toHexString(id) + ": .xml extension required");
1067 }
1068
1069 return complexColor;
1070 }
1071
Chris Craikceb26932018-02-01 15:51:34 -08001072 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001073 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001074 Resources.Theme theme)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001075 throws NotFoundException {
1076 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("PreloadColorStateList", 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 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
1093 if (complexColor != null && complexColor instanceof ColorStateList) {
1094 return (ColorStateList) complexColor;
1095 }
1096
1097 throw new NotFoundException(
1098 "Can't find ColorStateList from drawable resource ID #0x"
1099 + Integer.toHexString(id));
1100 }
1101
1102 @NonNull
1103 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
1104 ColorStateList csl;
1105 final android.content.res.ConstantState<ComplexColor> factory =
1106 sPreloadedComplexColors.get(key);
1107 if (factory != null) {
1108 return (ColorStateList) factory.newInstance();
1109 }
1110
1111 csl = ColorStateList.valueOf(value.data);
1112
1113 if (mPreloading) {
1114 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
1115 "color")) {
1116 sPreloadedComplexColors.put(key, csl.getConstantState());
1117 }
1118 }
1119
1120 return csl;
1121 }
1122
1123 /**
1124 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
1125 * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
1126 *
1127 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
1128 * and selector tag.
1129 *
Chris Craik2e41d492018-02-09 14:21:37 -08001130 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content, or
1131 * {@code null} if the XML file is neither.
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001132 */
Chris Craikceb26932018-02-01 15:51:34 -08001133 @NonNull
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001134 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Adam Lesinski082614c2016-03-04 14:33:47 -08001135 Resources.Theme theme) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001136 if (value.string == null) {
1137 throw new UnsupportedOperationException(
1138 "Can't convert to ComplexColor: type=0x" + value.type);
1139 }
1140
1141 final String file = value.string.toString();
1142
1143 if (TRACE_FOR_MISS_PRELOAD) {
1144 // Log only framework resources
1145 if ((id >>> 24) == 0x1) {
1146 final String name = getResourceName(id);
1147 if (name != null) {
1148 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
1149 + ": " + name + " at " + file);
1150 }
1151 }
1152 }
1153
1154 if (DEBUG_LOAD) {
1155 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
1156 }
1157
1158 ComplexColor complexColor = null;
1159
1160 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
1161 if (file.endsWith(".xml")) {
1162 try {
1163 final XmlResourceParser parser = loadXmlResourceParser(
1164 file, id, value.assetCookie, "ComplexColor");
1165
1166 final AttributeSet attrs = Xml.asAttributeSet(parser);
1167 int type;
1168 while ((type = parser.next()) != XmlPullParser.START_TAG
1169 && type != XmlPullParser.END_DOCUMENT) {
1170 // Seek parser to start tag.
1171 }
1172 if (type != XmlPullParser.START_TAG) {
1173 throw new XmlPullParserException("No start tag found");
1174 }
1175
1176 final String name = parser.getName();
1177 if (name.equals("gradient")) {
1178 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
1179 } else if (name.equals("selector")) {
1180 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
1181 }
1182 parser.close();
1183 } catch (Exception e) {
1184 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1185 final NotFoundException rnf = new NotFoundException(
1186 "File " + file + " from ComplexColor resource ID #0x"
1187 + Integer.toHexString(id));
1188 rnf.initCause(e);
1189 throw rnf;
1190 }
1191 } else {
1192 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1193 throw new NotFoundException(
1194 "File " + file + " from drawable resource ID #0x"
1195 + Integer.toHexString(id) + ": .xml extension required");
1196 }
1197 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1198
1199 return complexColor;
1200 }
1201
1202 /**
1203 * Loads an XML parser for the specified file.
1204 *
1205 * @param file the path for the XML file to parse
1206 * @param id the resource identifier for the file
1207 * @param assetCookie the asset cookie for the file
1208 * @param type the type of resource (used for logging)
1209 * @return a parser for the specified XML file
1210 * @throws NotFoundException if the file could not be loaded
1211 */
1212 @NonNull
Adam Lesinski082614c2016-03-04 14:33:47 -08001213 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
1214 @NonNull String type)
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001215 throws NotFoundException {
1216 if (id != 0) {
1217 try {
1218 synchronized (mCachedXmlBlocks) {
1219 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
1220 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
1221 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
1222 // First see if this block is in our cache.
1223 final int num = cachedXmlBlockFiles.length;
1224 for (int i = 0; i < num; i++) {
1225 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
1226 && cachedXmlBlockFiles[i].equals(file)) {
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001227 return cachedXmlBlocks[i].newParser(id);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001228 }
1229 }
1230
1231 // Not in the cache, create a new block and put it at
1232 // the next slot in the cache.
1233 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
1234 if (block != null) {
1235 final int pos = (mLastCachedXmlBlockIndex + 1) % num;
1236 mLastCachedXmlBlockIndex = pos;
1237 final XmlBlock oldBlock = cachedXmlBlocks[pos];
1238 if (oldBlock != null) {
1239 oldBlock.close();
1240 }
1241 cachedXmlBlockCookies[pos] = assetCookie;
1242 cachedXmlBlockFiles[pos] = file;
1243 cachedXmlBlocks[pos] = block;
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001244 return block.newParser(id);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001245 }
1246 }
1247 } catch (Exception e) {
1248 final NotFoundException rnf = new NotFoundException("File " + file
1249 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
1250 rnf.initCause(e);
1251 throw rnf;
1252 }
1253 }
1254
1255 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
1256 + Integer.toHexString(id));
1257 }
1258
1259 /**
1260 * Start preloading of resource data using this Resources object. Only
1261 * for use by the zygote process for loading common system resources.
1262 * {@hide}
1263 */
1264 public final void startPreloading() {
1265 synchronized (sSync) {
1266 if (sPreloaded) {
1267 throw new IllegalStateException("Resources already preloaded");
1268 }
1269 sPreloaded = true;
1270 mPreloading = true;
1271 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1272 updateConfiguration(null, null, null);
Makoto Onuki1480b672017-07-14 08:42:50 -07001273
1274 if (TRACE_FOR_DETAILED_PRELOAD) {
1275 mPreloadTracingPreloadStartTime = SystemClock.uptimeMillis();
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001276 mPreloadTracingStartBitmapSize = Bitmap.sPreloadTracingTotalBitmapsSize;
1277 mPreloadTracingStartBitmapCount = Bitmap.sPreloadTracingNumInstantiatedBitmaps;
Makoto Onuki1480b672017-07-14 08:42:50 -07001278 Log.d(TAG_PRELOAD, "Preload starting");
1279 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001280 }
1281 }
1282
1283 /**
1284 * Called by zygote when it is done preloading resources, to change back
1285 * to normal Resources operation.
1286 */
1287 void finishPreloading() {
1288 if (mPreloading) {
Makoto Onuki1480b672017-07-14 08:42:50 -07001289 if (TRACE_FOR_DETAILED_PRELOAD) {
1290 final long time = SystemClock.uptimeMillis() - mPreloadTracingPreloadStartTime;
Makoto Onukiaa6560c2017-07-14 14:05:18 -07001291 final long size =
1292 Bitmap.sPreloadTracingTotalBitmapsSize - mPreloadTracingStartBitmapSize;
1293 final long count = Bitmap.sPreloadTracingNumInstantiatedBitmaps
1294 - mPreloadTracingStartBitmapCount;
1295 Log.d(TAG_PRELOAD, "Preload finished, "
1296 + count + " bitmaps of " + size + " bytes in " + time + " ms");
Makoto Onuki1480b672017-07-14 08:42:50 -07001297 }
1298
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001299 mPreloading = false;
1300 flushLayoutCache();
1301 }
1302 }
1303
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001304 @AnyRes
1305 static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
Aurimas Liutikas02d7f452019-03-08 15:29:09 -08001306 if (set == null || !(set instanceof XmlBlock.Parser)) {
Aurimas Liutikasaae06632019-01-30 13:16:27 -08001307 return ID_NULL;
1308 }
1309 return ((XmlBlock.Parser) set).getSourceResId();
1310 }
1311
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001312 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1313 return sPreloadedDrawables[0];
1314 }
1315
1316 ThemeImpl newThemeImpl() {
1317 return new ThemeImpl();
1318 }
1319
Adam Lesinski082614c2016-03-04 14:33:47 -08001320 /**
1321 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1322 */
1323 ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1324 ThemeImpl impl = new ThemeImpl();
1325 impl.mKey.setTo(key);
1326 impl.rebase();
1327 return impl;
1328 }
1329
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001330 public class ThemeImpl {
1331 /**
1332 * Unique key for the series of styles applied to this theme.
1333 */
1334 private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1335
1336 @SuppressWarnings("hiding")
1337 private final AssetManager mAssets;
1338 private final long mTheme;
1339
1340 /**
1341 * Resource identifier for the theme.
1342 */
1343 private int mThemeResId = 0;
1344
1345 /*package*/ ThemeImpl() {
1346 mAssets = ResourcesImpl.this.mAssets;
1347 mTheme = mAssets.createTheme();
1348 }
1349
1350 @Override
1351 protected void finalize() throws Throwable {
1352 super.finalize();
1353 mAssets.releaseTheme(mTheme);
1354 }
1355
1356 /*package*/ Resources.ThemeKey getKey() {
1357 return mKey;
1358 }
1359
1360 /*package*/ long getNativeTheme() {
1361 return mTheme;
1362 }
1363
1364 /*package*/ int getAppliedStyleResId() {
1365 return mThemeResId;
1366 }
1367
1368 void applyStyle(int resId, boolean force) {
1369 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001370 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001371 mThemeResId = resId;
1372 mKey.append(resId, force);
1373 }
1374 }
1375
1376 void setTo(ThemeImpl other) {
1377 synchronized (mKey) {
1378 synchronized (other.mKey) {
Ryan Mitchellb3ae42e2018-10-16 12:48:38 -07001379 mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001380
1381 mThemeResId = other.mThemeResId;
1382 mKey.setTo(other.getKey());
1383 }
1384 }
1385 }
1386
1387 @NonNull
1388 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001389 AttributeSet set,
1390 @StyleableRes int[] attrs,
1391 @AttrRes int defStyleAttr,
1392 @StyleRes int defStyleRes) {
Eric Holkc3745772018-08-16 16:20:22 -07001393 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "obtainStyledAttributes");
1394 TypedArray array;
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001395 synchronized (mKey) {
1396 final int len = attrs.length;
Eric Holkc3745772018-08-16 16:20:22 -07001397 array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001398
1399 // XXX note that for now we only work with compiled XML files.
1400 // To support generic XML files we will need to manually parse
1401 // out the attributes from the XML file (applying type information
1402 // contained in the resources and such).
1403 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001404 mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
1405 array.mDataAddress, array.mIndicesAddress);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001406 array.mTheme = wrapper;
1407 array.mXml = parser;
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001408 }
Eric Holkc3745772018-08-16 16:20:22 -07001409 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1410 return array;
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001411 }
1412
1413 @NonNull
1414 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
Adam Lesinski082614c2016-03-04 14:33:47 -08001415 @NonNull int[] values,
1416 @NonNull int[] attrs) {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001417 synchronized (mKey) {
1418 final int len = attrs.length;
1419 if (values == null || len != values.length) {
1420 throw new IllegalArgumentException(
1421 "Base attribute values must the same length as attrs");
1422 }
1423
1424 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001425 mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001426 array.mTheme = wrapper;
1427 array.mXml = null;
1428 return array;
1429 }
1430 }
1431
1432 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1433 synchronized (mKey) {
1434 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1435 }
1436 }
1437
1438 int[] getAllAttributes() {
1439 return mAssets.getStyleAttributes(getAppliedStyleResId());
1440 }
1441
Alan Viveretteac85f902016-03-11 15:15:51 -05001442 @Config int getChangingConfigurations() {
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001443 synchronized (mKey) {
Alan Viverette9ad386b2017-01-26 14:00:20 -05001444 final @NativeConfig int nativeChangingConfig =
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001445 AssetManager.nativeThemeGetChangingConfigurations(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001446 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1447 }
1448 }
1449
1450 public void dump(int priority, String tag, String prefix) {
1451 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001452 mAssets.dumpTheme(mTheme, priority, tag, prefix);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001453 }
1454 }
1455
1456 String[] getTheme() {
1457 synchronized (mKey) {
1458 final int N = mKey.mCount;
1459 final String[] themes = new String[N * 2];
1460 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1461 final int resId = mKey.mResId[j];
1462 final boolean forced = mKey.mForce[j];
1463 try {
1464 themes[i] = getResourceName(resId);
1465 } catch (NotFoundException e) {
1466 themes[i] = Integer.toHexString(i);
1467 }
1468 themes[i + 1] = forced ? "forced" : "not forced";
1469 }
1470 return themes;
1471 }
1472 }
1473
1474 /**
1475 * Rebases the theme against the parent Resource object's current
1476 * configuration by re-applying the styles passed to
1477 * {@link #applyStyle(int, boolean)}.
1478 */
1479 void rebase() {
1480 synchronized (mKey) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001481 AssetManager.nativeThemeClear(mTheme);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001482
1483 // Reapply the same styles in the same order.
1484 for (int i = 0; i < mKey.mCount; i++) {
1485 final int resId = mKey.mResId[i];
1486 final boolean force = mKey.mForce[i];
Adam Lesinskibebfcc42018-02-12 14:27:46 -08001487 mAssets.applyStyleToTheme(mTheme, resId, force);
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001488 }
1489 }
1490 }
Aurimas Liutikas8f004c82019-01-17 17:20:10 -08001491
1492 /**
1493 * Returns the ordered list of resource ID that are considered when resolving attribute
1494 * values when making an equivalent call to
1495 * {@link #obtainStyledAttributes(Resources.Theme, AttributeSet, int[], int, int)}. The list
1496 * will include a set of explicit styles ({@code explicitStyleRes} and it will include the
1497 * default styles ({@code defStyleAttr} and {@code defStyleRes}).
1498 *
1499 * @param defStyleAttr An attribute in the current theme that contains a
1500 * reference to a style resource that supplies
1501 * defaults values for the TypedArray. Can be
1502 * 0 to not look for defaults.
1503 * @param defStyleRes A resource identifier of a style resource that
1504 * supplies default values for the TypedArray,
1505 * used only if defStyleAttr is 0 or can not be found
1506 * in the theme. Can be 0 to not look for defaults.
1507 * @param explicitStyleRes A resource identifier of an explicit style resource.
1508 * @return ordered list of resource ID that are considered when resolving attribute values.
1509 */
Aurimas Liutikas6c15bc02019-02-28 11:16:06 -08001510 @Nullable
Aurimas Liutikas8f004c82019-01-17 17:20:10 -08001511 public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
1512 @StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
1513 synchronized (mKey) {
1514 return mAssets.getAttributeResolutionStack(
1515 mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
1516 }
1517 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001518 }
Sunny Goyal99b25d22017-11-01 11:58:13 -07001519
1520 private static class LookupStack {
1521
1522 // Pick a reasonable default size for the array, it is grown as needed.
1523 private int[] mIds = new int[4];
1524 private int mSize = 0;
1525
1526 public void push(int id) {
1527 mIds = GrowingArrayUtils.append(mIds, mSize, id);
1528 mSize++;
1529 }
1530
1531 public boolean contains(int id) {
1532 for (int i = 0; i < mSize; i++) {
1533 if (mIds[i] == id) {
1534 return true;
1535 }
1536 }
1537 return false;
1538 }
1539
1540 public void pop() {
1541 mSize--;
1542 }
1543 }
Adam Lesinskifb302cc2016-02-29 16:50:38 -08001544}