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