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