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