Home | History | Annotate | Download | only in res
      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      */
    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 
    149     public DisplayAdjustments getDisplayAdjustments() {
    150         return mDisplayAdjustments;
    151     }
    152 
    153     public AssetManager getAssets() {
    154         return mAssets;
    155     }
    156 
    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 
    163     Configuration getConfiguration() {
    164         return mConfiguration;
    165     }
    166 
    167     Configuration[] getSizeConfigurations() {
    168         return mAssets.getSizeConfigurations();
    169     }
    170 
    171     CompatibilityInfo getCompatibilityInfo() {
    172         return mDisplayAdjustments.getCompatibilityInfo();
    173     }
    174 
    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 
    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 
    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 
    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 
    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
    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
    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
    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
    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
    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 
    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
    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
    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 
    312     ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
    313         return mAnimatorCache;
    314     }
    315 
    316     ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
    317         return mStateListAnimatorCache;
    318     }
    319 
    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      */
    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      */
    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      */
    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
    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 
    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 
    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      */
    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      */
    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
    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
    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
    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
    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
    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      */
   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      */
   1022     void finishPreloading() {
   1023         if (mPreloading) {
   1024             mPreloading = false;
   1025             flushLayoutCache();
   1026         }
   1027     }
   1028 
   1029     LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
   1030         return sPreloadedDrawables[0];
   1031     }
   1032 
   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      */
   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 
   1062         /*package*/ ThemeImpl() {
   1063             mAssets = ResourcesImpl.this.mAssets;
   1064             mTheme = mAssets.createTheme();
   1065         }
   1066 
   1067         @Override
   1068         protected void finalize() throws Throwable {
   1069             super.finalize();
   1070             mAssets.releaseTheme(mTheme);
   1071         }
   1072 
   1073         /*package*/ Resources.ThemeKey getKey() {
   1074             return mKey;
   1075         }
   1076 
   1077         /*package*/ long getNativeTheme() {
   1078             return mTheme;
   1079         }
   1080 
   1081         /*package*/ int getAppliedStyleResId() {
   1082             return mThemeResId;
   1083         }
   1084 
   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 
   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
   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
   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 
   1149         boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
   1150             synchronized (mKey) {
   1151                 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
   1152             }
   1153         }
   1154 
   1155         int[] getAllAttributes() {
   1156             return mAssets.getStyleAttributes(getAppliedStyleResId());
   1157         }
   1158 
   1159         @Config int getChangingConfigurations() {
   1160             synchronized (mKey) {
   1161                 final int nativeChangingConfig =
   1162                         AssetManager.getThemeChangingConfigurations(mTheme);
   1163                 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
   1164             }
   1165         }
   1166 
   1167         public void dump(int priority, String tag, String prefix) {
   1168             synchronized (mKey) {
   1169                 AssetManager.dumpTheme(mTheme, priority, tag, prefix);
   1170             }
   1171         }
   1172 
   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          */
   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