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