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