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