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