• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package android.content.res;
17 
18 import org.xmlpull.v1.XmlPullParser;
19 import org.xmlpull.v1.XmlPullParserException;
20 
21 import android.animation.Animator;
22 import android.animation.StateListAnimator;
23 import android.annotation.AnyRes;
24 import android.annotation.AttrRes;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.PluralsRes;
28 import android.annotation.RawRes;
29 import android.annotation.StyleRes;
30 import android.annotation.StyleableRes;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.ActivityInfo.Config;
33 import android.content.res.Resources.NotFoundException;
34 import android.graphics.drawable.ColorDrawable;
35 import android.graphics.drawable.Drawable;
36 import android.icu.text.PluralRules;
37 import android.os.Build;
38 import android.os.LocaleList;
39 import android.os.Trace;
40 import android.util.AttributeSet;
41 import android.util.DisplayMetrics;
42 import android.util.Log;
43 import android.util.LongSparseArray;
44 import android.util.Slog;
45 import android.util.TypedValue;
46 import android.util.Xml;
47 import android.view.Display;
48 import android.view.DisplayAdjustments;
49 
50 import java.io.InputStream;
51 import java.util.Arrays;
52 import java.util.Locale;
53 
54 /**
55  * The implementation of Resource access. This class contains the AssetManager and all caches
56  * associated with it.
57  *
58  * {@link Resources} is just a thing wrapper around this class. When a configuration change
59  * occurs, clients can retain the same {@link Resources} reference because the underlying
60  * {@link ResourcesImpl} object will be updated or re-created.
61  *
62  * @hide
63  */
64 public class ResourcesImpl {
65     static final String TAG = "Resources";
66 
67     private static final boolean DEBUG_LOAD = false;
68     private static final boolean DEBUG_CONFIG = false;
69     private static final boolean TRACE_FOR_PRELOAD = false;
70     private static final boolean TRACE_FOR_MISS_PRELOAD = false;
71 
72     private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
73             ActivityInfo.CONFIG_LAYOUT_DIRECTION);
74 
75     private static final int ID_OTHER = 0x01000004;
76 
77     private static final Object sSync = new Object();
78 
79     private static boolean sPreloaded;
80     private boolean mPreloading;
81 
82     // Information about preloaded resources.  Note that they are not
83     // protected by a lock, because while preloading in zygote we are all
84     // single-threaded, and after that these are immutable.
85     private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
86     private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
87             = new LongSparseArray<>();
88     private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>
89             sPreloadedComplexColors = new LongSparseArray<>();
90 
91     /** Lock object used to protect access to caches and configuration. */
92     private final Object mAccessLock = new Object();
93 
94     // These are protected by mAccessLock.
95     private final Configuration mTmpConfig = new Configuration();
96     private final DrawableCache mDrawableCache = new DrawableCache();
97     private final DrawableCache mColorDrawableCache = new DrawableCache();
98     private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache =
99             new ConfigurationBoundResourceCache<>();
100     private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
101             new ConfigurationBoundResourceCache<>();
102     private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
103             new ConfigurationBoundResourceCache<>();
104 
105     /** Size of the cyclical cache used to map XML files to blocks. */
106     private static final int XML_BLOCK_CACHE_SIZE = 4;
107 
108     // Cyclical cache used for recently-accessed XML files.
109     private int mLastCachedXmlBlockIndex = -1;
110     private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];
111     private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];
112     private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];
113 
114 
115     final AssetManager mAssets;
116     private final DisplayMetrics mMetrics = new DisplayMetrics();
117     private final DisplayAdjustments mDisplayAdjustments;
118 
119     private PluralRules mPluralRule;
120 
121     private final Configuration mConfiguration = new Configuration();
122 
123     static {
124         sPreloadedDrawables = new LongSparseArray[2];
125         sPreloadedDrawables[0] = new LongSparseArray<>();
126         sPreloadedDrawables[1] = new LongSparseArray<>();
127     }
128 
129     /**
130      * Creates a new ResourcesImpl object with CompatibilityInfo.
131      *
132      * @param assets Previously created AssetManager.
133      * @param metrics Current display metrics to consider when
134      *                selecting/computing resource values.
135      * @param config Desired device configuration to consider when
136      *               selecting/computing resource values (optional).
137      * @param displayAdjustments this resource's Display override and compatibility info.
138      *                           Must not be null.
139      */
ResourcesImpl(@onNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments)140     public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
141             @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
142         mAssets = assets;
143         mMetrics.setToDefaults();
144         mDisplayAdjustments = displayAdjustments;
145         updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
146         mAssets.ensureStringBlocks();
147     }
148 
getDisplayAdjustments()149     public DisplayAdjustments getDisplayAdjustments() {
150         return mDisplayAdjustments;
151     }
152 
getAssets()153     public AssetManager getAssets() {
154         return mAssets;
155     }
156 
getDisplayMetrics()157     DisplayMetrics getDisplayMetrics() {
158         if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
159                 + "x" + mMetrics.heightPixels + " " + mMetrics.density);
160         return mMetrics;
161     }
162 
getConfiguration()163     Configuration getConfiguration() {
164         return mConfiguration;
165     }
166 
getSizeConfigurations()167     Configuration[] getSizeConfigurations() {
168         return mAssets.getSizeConfigurations();
169     }
170 
getCompatibilityInfo()171     CompatibilityInfo getCompatibilityInfo() {
172         return mDisplayAdjustments.getCompatibilityInfo();
173     }
174 
getPluralRule()175     private PluralRules getPluralRule() {
176         synchronized (sSync) {
177             if (mPluralRule == null) {
178                 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
179             }
180             return mPluralRule;
181         }
182     }
183 
getValue(@nyRes int id, TypedValue outValue, boolean resolveRefs)184     void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
185             throws NotFoundException {
186         boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
187         if (found) {
188             return;
189         }
190         throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
191     }
192 
getValueForDensity(@nyRes int id, int density, TypedValue outValue, boolean resolveRefs)193     void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
194             boolean resolveRefs) throws NotFoundException {
195         boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
196         if (found) {
197             return;
198         }
199         throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
200     }
201 
getValue(String name, TypedValue outValue, boolean resolveRefs)202     void getValue(String name, TypedValue outValue, boolean resolveRefs)
203             throws NotFoundException {
204         int id = getIdentifier(name, "string", null);
205         if (id != 0) {
206             getValue(id, outValue, resolveRefs);
207             return;
208         }
209         throw new NotFoundException("String resource name " + name);
210     }
211 
getIdentifier(String name, String defType, String defPackage)212     int getIdentifier(String name, String defType, String defPackage) {
213         if (name == null) {
214             throw new NullPointerException("name is null");
215         }
216         try {
217             return Integer.parseInt(name);
218         } catch (Exception e) {
219             // Ignore
220         }
221         return mAssets.getResourceIdentifier(name, defType, defPackage);
222     }
223 
224     @NonNull
getResourceName(@nyRes int resid)225     String getResourceName(@AnyRes int resid) throws NotFoundException {
226         String str = mAssets.getResourceName(resid);
227         if (str != null) return str;
228         throw new NotFoundException("Unable to find resource ID #0x"
229                 + Integer.toHexString(resid));
230     }
231 
232     @NonNull
getResourcePackageName(@nyRes int resid)233     String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
234         String str = mAssets.getResourcePackageName(resid);
235         if (str != null) return str;
236         throw new NotFoundException("Unable to find resource ID #0x"
237                 + Integer.toHexString(resid));
238     }
239 
240     @NonNull
getResourceTypeName(@nyRes int resid)241     String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
242         String str = mAssets.getResourceTypeName(resid);
243         if (str != null) return str;
244         throw new NotFoundException("Unable to find resource ID #0x"
245                 + Integer.toHexString(resid));
246     }
247 
248     @NonNull
getResourceEntryName(@nyRes int resid)249     String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
250         String str = mAssets.getResourceEntryName(resid);
251         if (str != null) return str;
252         throw new NotFoundException("Unable to find resource ID #0x"
253                 + Integer.toHexString(resid));
254     }
255 
256     @NonNull
getQuantityText(@luralsRes int id, int quantity)257     CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException {
258         PluralRules rule = getPluralRule();
259         CharSequence res = mAssets.getResourceBagText(id,
260                 attrForQuantityCode(rule.select(quantity)));
261         if (res != null) {
262             return res;
263         }
264         res = mAssets.getResourceBagText(id, ID_OTHER);
265         if (res != null) {
266             return res;
267         }
268         throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
269                 + " quantity=" + quantity
270                 + " item=" + rule.select(quantity));
271     }
272 
attrForQuantityCode(String quantityCode)273     private static int attrForQuantityCode(String quantityCode) {
274         switch (quantityCode) {
275             case PluralRules.KEYWORD_ZERO: return 0x01000005;
276             case PluralRules.KEYWORD_ONE:  return 0x01000006;
277             case PluralRules.KEYWORD_TWO:  return 0x01000007;
278             case PluralRules.KEYWORD_FEW:  return 0x01000008;
279             case PluralRules.KEYWORD_MANY: return 0x01000009;
280             default:                       return ID_OTHER;
281         }
282     }
283 
284     @NonNull
openRawResourceFd(@awRes int id, TypedValue tempValue)285     AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue)
286             throws NotFoundException {
287         getValue(id, tempValue, true);
288         try {
289             return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
290         } catch (Exception e) {
291             throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
292                     + "resource ID #0x" + Integer.toHexString(id), e);
293         }
294     }
295 
296     @NonNull
openRawResource(@awRes int id, TypedValue value)297     InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException {
298         getValue(id, value, true);
299         try {
300             return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
301                     AssetManager.ACCESS_STREAMING);
302         } catch (Exception e) {
303             // Note: value.string might be null
304             NotFoundException rnf = new NotFoundException("File "
305                     + (value.string == null ? "(null)" : value.string.toString())
306                     + " from drawable resource ID #0x" + Integer.toHexString(id));
307             rnf.initCause(e);
308             throw rnf;
309         }
310     }
311 
getAnimatorCache()312     ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
313         return mAnimatorCache;
314     }
315 
getStateListAnimatorCache()316     ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
317         return mStateListAnimatorCache;
318     }
319 
updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat)320     public void updateConfiguration(Configuration config, DisplayMetrics metrics,
321                                     CompatibilityInfo compat) {
322         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
323         try {
324             synchronized (mAccessLock) {
325                 if (false) {
326                     Slog.i(TAG, "**** Updating config of " + this + ": old config is "
327                             + mConfiguration + " old compat is "
328                             + mDisplayAdjustments.getCompatibilityInfo());
329                     Slog.i(TAG, "**** Updating config of " + this + ": new config is "
330                             + config + " new compat is " + compat);
331                 }
332                 if (compat != null) {
333                     mDisplayAdjustments.setCompatibilityInfo(compat);
334                 }
335                 if (metrics != null) {
336                     mMetrics.setTo(metrics);
337                 }
338                 // NOTE: We should re-arrange this code to create a Display
339                 // with the CompatibilityInfo that is used everywhere we deal
340                 // with the display in relation to this app, rather than
341                 // doing the conversion here.  This impl should be okay because
342                 // we make sure to return a compatible display in the places
343                 // where there are public APIs to retrieve the display...  but
344                 // it would be cleaner and more maintainable to just be
345                 // consistently dealing with a compatible display everywhere in
346                 // the framework.
347                 mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
348 
349                 final @Config int configChanges = calcConfigChanges(config);
350 
351                 // If even after the update there are no Locales set, grab the default locales.
352                 LocaleList locales = mConfiguration.getLocales();
353                 if (locales.isEmpty()) {
354                     locales = LocaleList.getDefault();
355                     mConfiguration.setLocales(locales);
356                 }
357 
358                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
359                     if (locales.size() > 1) {
360                         // The LocaleList has changed. We must query the AssetManager's available
361                         // Locales and figure out the best matching Locale in the new LocaleList.
362                         String[] availableLocales = mAssets.getNonSystemLocales();
363                         if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
364                             // No app defined locales, so grab the system locales.
365                             availableLocales = mAssets.getLocales();
366                             if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
367                                 availableLocales = null;
368                             }
369                         }
370 
371                         if (availableLocales != null) {
372                             final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
373                                     availableLocales);
374                             if (bestLocale != null && bestLocale != locales.get(0)) {
375                                 mConfiguration.setLocales(new LocaleList(bestLocale, locales));
376                             }
377                         }
378                     }
379                 }
380 
381                 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
382                     mMetrics.densityDpi = mConfiguration.densityDpi;
383                     mMetrics.density =
384                             mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
385                 }
386                 mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
387 
388                 final int width, height;
389                 if (mMetrics.widthPixels >= mMetrics.heightPixels) {
390                     width = mMetrics.widthPixels;
391                     height = mMetrics.heightPixels;
392                 } else {
393                     //noinspection SuspiciousNameCombination
394                     width = mMetrics.heightPixels;
395                     //noinspection SuspiciousNameCombination
396                     height = mMetrics.widthPixels;
397                 }
398 
399                 final int keyboardHidden;
400                 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
401                         && mConfiguration.hardKeyboardHidden
402                         == Configuration.HARDKEYBOARDHIDDEN_YES) {
403                     keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
404                 } else {
405                     keyboardHidden = mConfiguration.keyboardHidden;
406                 }
407 
408                 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
409                         adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
410                         mConfiguration.orientation,
411                         mConfiguration.touchscreen,
412                         mConfiguration.densityDpi, mConfiguration.keyboard,
413                         keyboardHidden, mConfiguration.navigation, width, height,
414                         mConfiguration.smallestScreenWidthDp,
415                         mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
416                         mConfiguration.screenLayout, mConfiguration.uiMode,
417                         Build.VERSION.RESOURCES_SDK_INT);
418 
419                 if (DEBUG_CONFIG) {
420                     Slog.i(TAG, "**** Updating config of " + this + ": final config is "
421                             + mConfiguration + " final compat is "
422                             + mDisplayAdjustments.getCompatibilityInfo());
423                 }
424 
425                 mDrawableCache.onConfigurationChange(configChanges);
426                 mColorDrawableCache.onConfigurationChange(configChanges);
427                 mComplexColorCache.onConfigurationChange(configChanges);
428                 mAnimatorCache.onConfigurationChange(configChanges);
429                 mStateListAnimatorCache.onConfigurationChange(configChanges);
430 
431                 flushLayoutCache();
432             }
433             synchronized (sSync) {
434                 if (mPluralRule != null) {
435                     mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
436                 }
437             }
438         } finally {
439             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
440         }
441     }
442 
443     /**
444      * Applies the new configuration, returning a bitmask of the changes
445      * between the old and new configurations.
446      *
447      * @param config the new configuration
448      * @return bitmask of config changes
449      */
calcConfigChanges(@ullable Configuration config)450     public @Config int calcConfigChanges(@Nullable Configuration config) {
451         if (config == null) {
452             // If there is no configuration, assume all flags have changed.
453             return 0xFFFFFFFF;
454         }
455 
456         mTmpConfig.setTo(config);
457         int density = config.densityDpi;
458         if (density == Configuration.DENSITY_DPI_UNDEFINED) {
459             density = mMetrics.noncompatDensityDpi;
460         }
461 
462         mDisplayAdjustments.getCompatibilityInfo().applyToConfiguration(density, mTmpConfig);
463 
464         if (mTmpConfig.getLocales().isEmpty()) {
465             mTmpConfig.setLocales(LocaleList.getDefault());
466         }
467         return mConfiguration.updateFrom(mTmpConfig);
468     }
469 
470     /**
471      * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
472      * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
473      *
474      * All released versions of android prior to "L" used the deprecated language
475      * tags, so we will need to support them for backwards compatibility.
476      *
477      * Note that this conversion needs to take place *after* the call to
478      * {@code toLanguageTag} because that will convert all the deprecated codes to
479      * the new ones, even if they're set manually.
480      */
adjustLanguageTag(String languageTag)481     private static String adjustLanguageTag(String languageTag) {
482         final int separator = languageTag.indexOf('-');
483         final String language;
484         final String remainder;
485 
486         if (separator == -1) {
487             language = languageTag;
488             remainder = "";
489         } else {
490             language = languageTag.substring(0, separator);
491             remainder = languageTag.substring(separator);
492         }
493 
494         return Locale.adjustLanguageCode(language) + remainder;
495     }
496 
497     /**
498      * Call this to remove all cached loaded layout resources from the
499      * Resources object.  Only intended for use with performance testing
500      * tools.
501      */
flushLayoutCache()502     public void flushLayoutCache() {
503         synchronized (mCachedXmlBlocks) {
504             Arrays.fill(mCachedXmlBlockCookies, 0);
505             Arrays.fill(mCachedXmlBlockFiles, null);
506 
507             final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
508             for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
509                 final XmlBlock oldBlock = cachedXmlBlocks[i];
510                 if (oldBlock != null) {
511                     oldBlock.close();
512                 }
513             }
514             Arrays.fill(cachedXmlBlocks, null);
515         }
516     }
517 
518     @Nullable
loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache)519     Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
520             boolean useCache) throws NotFoundException {
521         try {
522             if (TRACE_FOR_PRELOAD) {
523                 // Log only framework resources
524                 if ((id >>> 24) == 0x1) {
525                     final String name = getResourceName(id);
526                     if (name != null) {
527                         Log.d("PreloadDrawable", name);
528                     }
529                 }
530             }
531 
532             final boolean isColorDrawable;
533             final DrawableCache caches;
534             final long key;
535             if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
536                     && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
537                 isColorDrawable = true;
538                 caches = mColorDrawableCache;
539                 key = value.data;
540             } else {
541                 isColorDrawable = false;
542                 caches = mDrawableCache;
543                 key = (((long) value.assetCookie) << 32) | value.data;
544             }
545 
546             // First, check whether we have a cached version of this drawable
547             // that was inflated against the specified theme. Skip the cache if
548             // we're currently preloading or we're not using the cache.
549             if (!mPreloading && useCache) {
550                 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
551                 if (cachedDrawable != null) {
552                     return cachedDrawable;
553                 }
554             }
555 
556             // Next, check preloaded drawables. Preloaded drawables may contain
557             // unresolved theme attributes.
558             final Drawable.ConstantState cs;
559             if (isColorDrawable) {
560                 cs = sPreloadedColorDrawables.get(key);
561             } else {
562                 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
563             }
564 
565             Drawable dr;
566             if (cs != null) {
567                 dr = cs.newDrawable(wrapper);
568             } else if (isColorDrawable) {
569                 dr = new ColorDrawable(value.data);
570             } else {
571                 dr = loadDrawableForCookie(wrapper, value, id, null);
572             }
573 
574             // Determine if the drawable has unresolved theme attributes. If it
575             // does, we'll need to apply a theme and store it in a theme-specific
576             // cache.
577             final boolean canApplyTheme = dr != null && dr.canApplyTheme();
578             if (canApplyTheme && theme != null) {
579                 dr = dr.mutate();
580                 dr.applyTheme(theme);
581                 dr.clearMutated();
582             }
583 
584             // If we were able to obtain a drawable, store it in the appropriate
585             // cache: preload, not themed, null theme, or theme-specific. Don't
586             // pollute the cache with drawables loaded from a foreign density.
587             if (dr != null && useCache) {
588                 dr.setChangingConfigurations(value.changingConfigurations);
589                 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
590             }
591 
592             return dr;
593         } catch (Exception e) {
594             String name;
595             try {
596                 name = getResourceName(id);
597             } catch (NotFoundException e2) {
598                 name = "(missing name)";
599             }
600 
601             // The target drawable might fail to load for any number of
602             // reasons, but we always want to include the resource name.
603             // Since the client already expects this method to throw a
604             // NotFoundException, just throw one of those.
605             final NotFoundException nfe = new NotFoundException("Drawable " + name
606                     + " with resource ID #0x" + Integer.toHexString(id), e);
607             nfe.setStackTrace(new StackTraceElement[0]);
608             throw nfe;
609         }
610     }
611 
cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, Resources.Theme theme, boolean usesTheme, long key, Drawable dr)612     private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
613             Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
614         final Drawable.ConstantState cs = dr.getConstantState();
615         if (cs == null) {
616             return;
617         }
618 
619         if (mPreloading) {
620             final int changingConfigs = cs.getChangingConfigurations();
621             if (isColorDrawable) {
622                 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
623                     sPreloadedColorDrawables.put(key, cs);
624                 }
625             } else {
626                 if (verifyPreloadConfig(
627                         changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
628                     if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
629                         // If this resource does not vary based on layout direction,
630                         // we can put it in all of the preload maps.
631                         sPreloadedDrawables[0].put(key, cs);
632                         sPreloadedDrawables[1].put(key, cs);
633                     } else {
634                         // Otherwise, only in the layout dir we loaded it for.
635                         sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
636                     }
637                 }
638             }
639         } else {
640             synchronized (mAccessLock) {
641                 caches.put(key, theme, cs, usesTheme);
642             }
643         }
644     }
645 
verifyPreloadConfig(@onfig int changingConfigurations, @Config int allowVarying, @AnyRes int resourceId, @Nullable String name)646     private boolean verifyPreloadConfig(@Config int changingConfigurations,
647             @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
648         // We allow preloading of resources even if they vary by font scale (which
649         // doesn't impact resource selection) or density (which we handle specially by
650         // simply turning off all preloading), as well as any other configs specified
651         // by the caller.
652         if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
653                 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
654             String resName;
655             try {
656                 resName = getResourceName(resourceId);
657             } catch (NotFoundException e) {
658                 resName = "?";
659             }
660             // This should never happen in production, so we should log a
661             // warning even if we're not debugging.
662             Log.w(TAG, "Preloaded " + name + " resource #0x"
663                     + Integer.toHexString(resourceId)
664                     + " (" + resName + ") that varies with configuration!!");
665             return false;
666         }
667         if (TRACE_FOR_PRELOAD) {
668             String resName;
669             try {
670                 resName = getResourceName(resourceId);
671             } catch (NotFoundException e) {
672                 resName = "?";
673             }
674             Log.w(TAG, "Preloading " + name + " resource #0x"
675                     + Integer.toHexString(resourceId)
676                     + " (" + resName + ")");
677         }
678         return true;
679     }
680 
681     /**
682      * Loads a drawable from XML or resources stream.
683      */
loadDrawableForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme)684     private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
685             Resources.Theme theme) {
686         if (value.string == null) {
687             throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
688                     + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
689         }
690 
691         final String file = value.string.toString();
692 
693         if (TRACE_FOR_MISS_PRELOAD) {
694             // Log only framework resources
695             if ((id >>> 24) == 0x1) {
696                 final String name = getResourceName(id);
697                 if (name != null) {
698                     Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
699                             + ": " + name + " at " + file);
700                 }
701             }
702         }
703 
704         if (DEBUG_LOAD) {
705             Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
706         }
707 
708         final Drawable dr;
709 
710         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
711         try {
712             if (file.endsWith(".xml")) {
713                 final XmlResourceParser rp = loadXmlResourceParser(
714                         file, id, value.assetCookie, "drawable");
715                 dr = Drawable.createFromXml(wrapper, rp, theme);
716                 rp.close();
717             } else {
718                 final InputStream is = mAssets.openNonAsset(
719                         value.assetCookie, file, AssetManager.ACCESS_STREAMING);
720                 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
721                 is.close();
722             }
723         } catch (Exception e) {
724             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
725             final NotFoundException rnf = new NotFoundException(
726                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
727             rnf.initCause(e);
728             throw rnf;
729         }
730         Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
731 
732         return dr;
733     }
734 
735     /**
736      * Given the value and id, we can get the XML filename as in value.data, based on that, we
737      * first try to load CSL from the cache. If not found, try to get from the constant state.
738      * Last, parse the XML and generate the CSL.
739      */
loadComplexColorFromName(Resources wrapper, Resources.Theme theme, TypedValue value, int id)740     private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
741             TypedValue value, int id) {
742         final long key = (((long) value.assetCookie) << 32) | value.data;
743         final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
744         ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
745         if (complexColor != null) {
746             return complexColor;
747         }
748 
749         final android.content.res.ConstantState<ComplexColor> factory =
750                 sPreloadedComplexColors.get(key);
751 
752         if (factory != null) {
753             complexColor = factory.newInstance(wrapper, theme);
754         }
755         if (complexColor == null) {
756             complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
757         }
758 
759         if (complexColor != null) {
760             complexColor.setBaseChangingConfigurations(value.changingConfigurations);
761 
762             if (mPreloading) {
763                 if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
764                         0, value.resourceId, "color")) {
765                     sPreloadedComplexColors.put(key, complexColor.getConstantState());
766                 }
767             } else {
768                 cache.put(key, theme, complexColor.getConstantState());
769             }
770         }
771         return complexColor;
772     }
773 
774     @Nullable
loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id, Resources.Theme theme)775     ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
776             Resources.Theme theme) {
777         if (TRACE_FOR_PRELOAD) {
778             // Log only framework resources
779             if ((id >>> 24) == 0x1) {
780                 final String name = getResourceName(id);
781                 if (name != null) android.util.Log.d("loadComplexColor", name);
782             }
783         }
784 
785         final long key = (((long) value.assetCookie) << 32) | value.data;
786 
787         // Handle inline color definitions.
788         if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
789                 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
790             return getColorStateListFromInt(value, key);
791         }
792 
793         final String file = value.string.toString();
794 
795         ComplexColor complexColor;
796         if (file.endsWith(".xml")) {
797             try {
798                 complexColor = loadComplexColorFromName(wrapper, theme, value, id);
799             } catch (Exception e) {
800                 final NotFoundException rnf = new NotFoundException(
801                         "File " + file + " from complex color resource ID #0x"
802                                 + Integer.toHexString(id));
803                 rnf.initCause(e);
804                 throw rnf;
805             }
806         } else {
807             throw new NotFoundException(
808                     "File " + file + " from drawable resource ID #0x"
809                             + Integer.toHexString(id) + ": .xml extension required");
810         }
811 
812         return complexColor;
813     }
814 
815     @Nullable
loadColorStateList(Resources wrapper, TypedValue value, int id, Resources.Theme theme)816     ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
817             Resources.Theme theme)
818             throws NotFoundException {
819         if (TRACE_FOR_PRELOAD) {
820             // Log only framework resources
821             if ((id >>> 24) == 0x1) {
822                 final String name = getResourceName(id);
823                 if (name != null) android.util.Log.d("PreloadColorStateList", name);
824             }
825         }
826 
827         final long key = (((long) value.assetCookie) << 32) | value.data;
828 
829         // Handle inline color definitions.
830         if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
831                 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
832             return getColorStateListFromInt(value, key);
833         }
834 
835         ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
836         if (complexColor != null && complexColor instanceof ColorStateList) {
837             return (ColorStateList) complexColor;
838         }
839 
840         throw new NotFoundException(
841                 "Can't find ColorStateList from drawable resource ID #0x"
842                         + Integer.toHexString(id));
843     }
844 
845     @NonNull
getColorStateListFromInt(@onNull TypedValue value, long key)846     private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
847         ColorStateList csl;
848         final android.content.res.ConstantState<ComplexColor> factory =
849                 sPreloadedComplexColors.get(key);
850         if (factory != null) {
851             return (ColorStateList) factory.newInstance();
852         }
853 
854         csl = ColorStateList.valueOf(value.data);
855 
856         if (mPreloading) {
857             if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
858                     "color")) {
859                 sPreloadedComplexColors.put(key, csl.getConstantState());
860             }
861         }
862 
863         return csl;
864     }
865 
866     /**
867      * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
868      * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
869      *
870      * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
871      * and selector tag.
872      *
873      * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
874      */
875     @Nullable
loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme)876     private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
877             Resources.Theme theme) {
878         if (value.string == null) {
879             throw new UnsupportedOperationException(
880                     "Can't convert to ComplexColor: type=0x" + value.type);
881         }
882 
883         final String file = value.string.toString();
884 
885         if (TRACE_FOR_MISS_PRELOAD) {
886             // Log only framework resources
887             if ((id >>> 24) == 0x1) {
888                 final String name = getResourceName(id);
889                 if (name != null) {
890                     Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
891                             + ": " + name + " at " + file);
892                 }
893             }
894         }
895 
896         if (DEBUG_LOAD) {
897             Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
898         }
899 
900         ComplexColor complexColor = null;
901 
902         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
903         if (file.endsWith(".xml")) {
904             try {
905                 final XmlResourceParser parser = loadXmlResourceParser(
906                         file, id, value.assetCookie, "ComplexColor");
907 
908                 final AttributeSet attrs = Xml.asAttributeSet(parser);
909                 int type;
910                 while ((type = parser.next()) != XmlPullParser.START_TAG
911                         && type != XmlPullParser.END_DOCUMENT) {
912                     // Seek parser to start tag.
913                 }
914                 if (type != XmlPullParser.START_TAG) {
915                     throw new XmlPullParserException("No start tag found");
916                 }
917 
918                 final String name = parser.getName();
919                 if (name.equals("gradient")) {
920                     complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
921                 } else if (name.equals("selector")) {
922                     complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
923                 }
924                 parser.close();
925             } catch (Exception e) {
926                 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
927                 final NotFoundException rnf = new NotFoundException(
928                         "File " + file + " from ComplexColor resource ID #0x"
929                                 + Integer.toHexString(id));
930                 rnf.initCause(e);
931                 throw rnf;
932             }
933         } else {
934             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
935             throw new NotFoundException(
936                     "File " + file + " from drawable resource ID #0x"
937                             + Integer.toHexString(id) + ": .xml extension required");
938         }
939         Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
940 
941         return complexColor;
942     }
943 
944     /**
945      * Loads an XML parser for the specified file.
946      *
947      * @param file the path for the XML file to parse
948      * @param id the resource identifier for the file
949      * @param assetCookie the asset cookie for the file
950      * @param type the type of resource (used for logging)
951      * @return a parser for the specified XML file
952      * @throws NotFoundException if the file could not be loaded
953      */
954     @NonNull
loadXmlResourceParser(@onNull String file, @AnyRes int id, int assetCookie, @NonNull String type)955     XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
956             @NonNull String type)
957             throws NotFoundException {
958         if (id != 0) {
959             try {
960                 synchronized (mCachedXmlBlocks) {
961                     final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
962                     final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
963                     final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
964                     // First see if this block is in our cache.
965                     final int num = cachedXmlBlockFiles.length;
966                     for (int i = 0; i < num; i++) {
967                         if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
968                                 && cachedXmlBlockFiles[i].equals(file)) {
969                             return cachedXmlBlocks[i].newParser();
970                         }
971                     }
972 
973                     // Not in the cache, create a new block and put it at
974                     // the next slot in the cache.
975                     final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
976                     if (block != null) {
977                         final int pos = (mLastCachedXmlBlockIndex + 1) % num;
978                         mLastCachedXmlBlockIndex = pos;
979                         final XmlBlock oldBlock = cachedXmlBlocks[pos];
980                         if (oldBlock != null) {
981                             oldBlock.close();
982                         }
983                         cachedXmlBlockCookies[pos] = assetCookie;
984                         cachedXmlBlockFiles[pos] = file;
985                         cachedXmlBlocks[pos] = block;
986                         return block.newParser();
987                     }
988                 }
989             } catch (Exception e) {
990                 final NotFoundException rnf = new NotFoundException("File " + file
991                         + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
992                 rnf.initCause(e);
993                 throw rnf;
994             }
995         }
996 
997         throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
998                 + Integer.toHexString(id));
999     }
1000 
1001     /**
1002      * Start preloading of resource data using this Resources object.  Only
1003      * for use by the zygote process for loading common system resources.
1004      * {@hide}
1005      */
startPreloading()1006     public final void startPreloading() {
1007         synchronized (sSync) {
1008             if (sPreloaded) {
1009                 throw new IllegalStateException("Resources already preloaded");
1010             }
1011             sPreloaded = true;
1012             mPreloading = true;
1013             mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1014             updateConfiguration(null, null, null);
1015         }
1016     }
1017 
1018     /**
1019      * Called by zygote when it is done preloading resources, to change back
1020      * to normal Resources operation.
1021      */
finishPreloading()1022     void finishPreloading() {
1023         if (mPreloading) {
1024             mPreloading = false;
1025             flushLayoutCache();
1026         }
1027     }
1028 
getPreloadedDrawables()1029     LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1030         return sPreloadedDrawables[0];
1031     }
1032 
newThemeImpl()1033     ThemeImpl newThemeImpl() {
1034         return new ThemeImpl();
1035     }
1036 
1037     /**
1038      * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1039      */
newThemeImpl(Resources.ThemeKey key)1040     ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1041         ThemeImpl impl = new ThemeImpl();
1042         impl.mKey.setTo(key);
1043         impl.rebase();
1044         return impl;
1045     }
1046 
1047     public class ThemeImpl {
1048         /**
1049          * Unique key for the series of styles applied to this theme.
1050          */
1051         private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1052 
1053         @SuppressWarnings("hiding")
1054         private final AssetManager mAssets;
1055         private final long mTheme;
1056 
1057         /**
1058          * Resource identifier for the theme.
1059          */
1060         private int mThemeResId = 0;
1061 
ThemeImpl()1062         /*package*/ ThemeImpl() {
1063             mAssets = ResourcesImpl.this.mAssets;
1064             mTheme = mAssets.createTheme();
1065         }
1066 
1067         @Override
finalize()1068         protected void finalize() throws Throwable {
1069             super.finalize();
1070             mAssets.releaseTheme(mTheme);
1071         }
1072 
getKey()1073         /*package*/ Resources.ThemeKey getKey() {
1074             return mKey;
1075         }
1076 
getNativeTheme()1077         /*package*/ long getNativeTheme() {
1078             return mTheme;
1079         }
1080 
getAppliedStyleResId()1081         /*package*/ int getAppliedStyleResId() {
1082             return mThemeResId;
1083         }
1084 
applyStyle(int resId, boolean force)1085         void applyStyle(int resId, boolean force) {
1086             synchronized (mKey) {
1087                 AssetManager.applyThemeStyle(mTheme, resId, force);
1088 
1089                 mThemeResId = resId;
1090                 mKey.append(resId, force);
1091             }
1092         }
1093 
setTo(ThemeImpl other)1094         void setTo(ThemeImpl other) {
1095             synchronized (mKey) {
1096                 synchronized (other.mKey) {
1097                     AssetManager.copyTheme(mTheme, other.mTheme);
1098 
1099                     mThemeResId = other.mThemeResId;
1100                     mKey.setTo(other.getKey());
1101                 }
1102             }
1103         }
1104 
1105         @NonNull
obtainStyledAttributes(@onNull Resources.Theme wrapper, AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)1106         TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
1107                 AttributeSet set,
1108                 @StyleableRes int[] attrs,
1109                 @AttrRes int defStyleAttr,
1110                 @StyleRes int defStyleRes) {
1111             synchronized (mKey) {
1112                 final int len = attrs.length;
1113                 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1114 
1115                 // XXX note that for now we only work with compiled XML files.
1116                 // To support generic XML files we will need to manually parse
1117                 // out the attributes from the XML file (applying type information
1118                 // contained in the resources and such).
1119                 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
1120                 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1121                         parser != null ? parser.mParseState : 0,
1122                         attrs, array.mData, array.mIndices);
1123                 array.mTheme = wrapper;
1124                 array.mXml = parser;
1125 
1126                 return array;
1127             }
1128         }
1129 
1130         @NonNull
resolveAttributes(@onNull Resources.Theme wrapper, @NonNull int[] values, @NonNull int[] attrs)1131         TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
1132                 @NonNull int[] values,
1133                 @NonNull int[] attrs) {
1134             synchronized (mKey) {
1135                 final int len = attrs.length;
1136                 if (values == null || len != values.length) {
1137                     throw new IllegalArgumentException(
1138                             "Base attribute values must the same length as attrs");
1139                 }
1140 
1141                 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1142                 AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
1143                 array.mTheme = wrapper;
1144                 array.mXml = null;
1145                 return array;
1146             }
1147         }
1148 
resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs)1149         boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1150             synchronized (mKey) {
1151                 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1152             }
1153         }
1154 
getAllAttributes()1155         int[] getAllAttributes() {
1156             return mAssets.getStyleAttributes(getAppliedStyleResId());
1157         }
1158 
getChangingConfigurations()1159         @Config int getChangingConfigurations() {
1160             synchronized (mKey) {
1161                 final int nativeChangingConfig =
1162                         AssetManager.getThemeChangingConfigurations(mTheme);
1163                 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1164             }
1165         }
1166 
dump(int priority, String tag, String prefix)1167         public void dump(int priority, String tag, String prefix) {
1168             synchronized (mKey) {
1169                 AssetManager.dumpTheme(mTheme, priority, tag, prefix);
1170             }
1171         }
1172 
getTheme()1173         String[] getTheme() {
1174             synchronized (mKey) {
1175                 final int N = mKey.mCount;
1176                 final String[] themes = new String[N * 2];
1177                 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1178                     final int resId = mKey.mResId[j];
1179                     final boolean forced = mKey.mForce[j];
1180                     try {
1181                         themes[i] = getResourceName(resId);
1182                     } catch (NotFoundException e) {
1183                         themes[i] = Integer.toHexString(i);
1184                     }
1185                     themes[i + 1] = forced ? "forced" : "not forced";
1186                 }
1187                 return themes;
1188             }
1189         }
1190 
1191         /**
1192          * Rebases the theme against the parent Resource object's current
1193          * configuration by re-applying the styles passed to
1194          * {@link #applyStyle(int, boolean)}.
1195          */
rebase()1196         void rebase() {
1197             synchronized (mKey) {
1198                 AssetManager.clearTheme(mTheme);
1199 
1200                 // Reapply the same styles in the same order.
1201                 for (int i = 0; i < mKey.mCount; i++) {
1202                     final int resId = mKey.mResId[i];
1203                     final boolean force = mKey.mForce[i];
1204                     AssetManager.applyThemeStyle(mTheme, resId, force);
1205                 }
1206             }
1207         }
1208     }
1209 }
1210