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