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