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