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