ResourcesImpl.java revision b61e405397200f78b1c652143cba7c751df05a00
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.LocaleList;
39import android.os.Trace;
40import android.util.AttributeSet;
41import android.util.DisplayMetrics;
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        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
313        try {
314            synchronized (mAccessLock) {
315                if (false) {
316                    Slog.i(TAG, "**** Updating config of " + this + ": old config is "
317                            + mConfiguration + " old compat is " + mCompatibilityInfo);
318                    Slog.i(TAG, "**** Updating config of " + this + ": new config is "
319                            + config + " new compat is " + compat);
320                }
321                if (compat != null) {
322                    mCompatibilityInfo = compat;
323                }
324                if (metrics != null) {
325                    mMetrics.setTo(metrics);
326                }
327                // NOTE: We should re-arrange this code to create a Display
328                // with the CompatibilityInfo that is used everywhere we deal
329                // with the display in relation to this app, rather than
330                // doing the conversion here.  This impl should be okay because
331                // we make sure to return a compatible display in the places
332                // where there are public APIs to retrieve the display...  but
333                // it would be cleaner and more maintainable to just be
334                // consistently dealing with a compatible display everywhere in
335                // the framework.
336                mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
337
338                final @Config int configChanges = calcConfigChanges(config);
339
340                // If even after the update there are no Locales set, grab the default locales.
341                LocaleList locales = mConfiguration.getLocales();
342                if (locales.isEmpty()) {
343                    locales = LocaleList.getDefault();
344                    mConfiguration.setLocales(locales);
345                }
346
347                if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
348                    if (locales.size() > 1) {
349                        // The LocaleList has changed. We must query the AssetManager's available
350                        // Locales and figure out the best matching Locale in the new LocaleList.
351                        String[] availableLocales = mAssets.getNonSystemLocales();
352                        if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
353                            // No app defined locales, so grab the system locales.
354                            availableLocales = mAssets.getLocales();
355                            if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
356                                availableLocales = null;
357                            }
358                        }
359
360                        if (availableLocales != null) {
361                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
362                                    availableLocales);
363                            if (bestLocale != null && bestLocale != locales.get(0)) {
364                                mConfiguration.setLocales(new LocaleList(bestLocale, locales));
365                            }
366                        }
367                    }
368                }
369
370                if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
371                    mMetrics.densityDpi = mConfiguration.densityDpi;
372                    mMetrics.density =
373                            mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
374                }
375                mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
376
377                final int width, height;
378                if (mMetrics.widthPixels >= mMetrics.heightPixels) {
379                    width = mMetrics.widthPixels;
380                    height = mMetrics.heightPixels;
381                } else {
382                    //noinspection SuspiciousNameCombination
383                    width = mMetrics.heightPixels;
384                    //noinspection SuspiciousNameCombination
385                    height = mMetrics.widthPixels;
386                }
387
388                final int keyboardHidden;
389                if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
390                        && mConfiguration.hardKeyboardHidden
391                        == Configuration.HARDKEYBOARDHIDDEN_YES) {
392                    keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
393                } else {
394                    keyboardHidden = mConfiguration.keyboardHidden;
395                }
396
397                mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
398                        adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
399                        mConfiguration.orientation,
400                        mConfiguration.touchscreen,
401                        mConfiguration.densityDpi, mConfiguration.keyboard,
402                        keyboardHidden, mConfiguration.navigation, width, height,
403                        mConfiguration.smallestScreenWidthDp,
404                        mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
405                        mConfiguration.screenLayout, mConfiguration.uiMode,
406                        Build.VERSION.RESOURCES_SDK_INT);
407
408                if (DEBUG_CONFIG) {
409                    Slog.i(TAG, "**** Updating config of " + this + ": final config is "
410                            + mConfiguration + " final compat is " + mCompatibilityInfo);
411                }
412
413                mDrawableCache.onConfigurationChange(configChanges);
414                mColorDrawableCache.onConfigurationChange(configChanges);
415                mComplexColorCache.onConfigurationChange(configChanges);
416                mAnimatorCache.onConfigurationChange(configChanges);
417                mStateListAnimatorCache.onConfigurationChange(configChanges);
418
419                flushLayoutCache();
420            }
421            synchronized (sSync) {
422                if (mPluralRule != null) {
423                    mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
424                }
425            }
426        } finally {
427            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
428        }
429    }
430
431    /**
432     * Applies the new configuration, returning a bitmask of the changes
433     * between the old and new configurations.
434     *
435     * @param config the new configuration
436     * @return bitmask of config changes
437     */
438    public @Config int calcConfigChanges(@Nullable Configuration config) {
439        if (config == null) {
440            // If there is no configuration, assume all flags have changed.
441            return 0xFFFFFFFF;
442        }
443
444        mTmpConfig.setTo(config);
445        int density = config.densityDpi;
446        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
447            density = mMetrics.noncompatDensityDpi;
448        }
449
450        mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
451
452        if (mTmpConfig.getLocales().isEmpty()) {
453            mTmpConfig.setLocales(LocaleList.getDefault());
454        }
455        return mConfiguration.updateFrom(mTmpConfig);
456    }
457
458    /**
459     * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
460     * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
461     *
462     * All released versions of android prior to "L" used the deprecated language
463     * tags, so we will need to support them for backwards compatibility.
464     *
465     * Note that this conversion needs to take place *after* the call to
466     * {@code toLanguageTag} because that will convert all the deprecated codes to
467     * the new ones, even if they're set manually.
468     */
469    private static String adjustLanguageTag(String languageTag) {
470        final int separator = languageTag.indexOf('-');
471        final String language;
472        final String remainder;
473
474        if (separator == -1) {
475            language = languageTag;
476            remainder = "";
477        } else {
478            language = languageTag.substring(0, separator);
479            remainder = languageTag.substring(separator);
480        }
481
482        return Locale.adjustLanguageCode(language) + remainder;
483    }
484
485    /**
486     * Call this to remove all cached loaded layout resources from the
487     * Resources object.  Only intended for use with performance testing
488     * tools.
489     */
490    public void flushLayoutCache() {
491        synchronized (mCachedXmlBlocks) {
492            Arrays.fill(mCachedXmlBlockCookies, 0);
493            Arrays.fill(mCachedXmlBlockFiles, null);
494
495            final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
496            for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) {
497                final XmlBlock oldBlock = cachedXmlBlocks[i];
498                if (oldBlock != null) {
499                    oldBlock.close();
500                }
501            }
502            Arrays.fill(cachedXmlBlocks, null);
503        }
504    }
505
506    @Nullable
507    Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
508            boolean useCache) throws NotFoundException {
509        try {
510            if (TRACE_FOR_PRELOAD) {
511                // Log only framework resources
512                if ((id >>> 24) == 0x1) {
513                    final String name = getResourceName(id);
514                    if (name != null) {
515                        Log.d("PreloadDrawable", name);
516                    }
517                }
518            }
519
520            final boolean isColorDrawable;
521            final DrawableCache caches;
522            final long key;
523            if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
524                    && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
525                isColorDrawable = true;
526                caches = mColorDrawableCache;
527                key = value.data;
528            } else {
529                isColorDrawable = false;
530                caches = mDrawableCache;
531                key = (((long) value.assetCookie) << 32) | value.data;
532            }
533
534            // First, check whether we have a cached version of this drawable
535            // that was inflated against the specified theme. Skip the cache if
536            // we're currently preloading or we're not using the cache.
537            if (!mPreloading && useCache) {
538                final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
539                if (cachedDrawable != null) {
540                    return cachedDrawable;
541                }
542            }
543
544            // Next, check preloaded drawables. Preloaded drawables may contain
545            // unresolved theme attributes.
546            final Drawable.ConstantState cs;
547            if (isColorDrawable) {
548                cs = sPreloadedColorDrawables.get(key);
549            } else {
550                cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
551            }
552
553            Drawable dr;
554            if (cs != null) {
555                dr = cs.newDrawable(wrapper);
556            } else if (isColorDrawable) {
557                dr = new ColorDrawable(value.data);
558            } else {
559                dr = loadDrawableForCookie(wrapper, value, id, null);
560            }
561
562            // Determine if the drawable has unresolved theme attributes. If it
563            // does, we'll need to apply a theme and store it in a theme-specific
564            // cache.
565            final boolean canApplyTheme = dr != null && dr.canApplyTheme();
566            if (canApplyTheme && theme != null) {
567                dr = dr.mutate();
568                dr.applyTheme(theme);
569                dr.clearMutated();
570            }
571
572            // If we were able to obtain a drawable, store it in the appropriate
573            // cache: preload, not themed, null theme, or theme-specific. Don't
574            // pollute the cache with drawables loaded from a foreign density.
575            if (dr != null && useCache) {
576                dr.setChangingConfigurations(value.changingConfigurations);
577                cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
578            }
579
580            return dr;
581        } catch (Exception e) {
582            String name;
583            try {
584                name = getResourceName(id);
585            } catch (NotFoundException e2) {
586                name = "(missing name)";
587            }
588
589            // The target drawable might fail to load for any number of
590            // reasons, but we always want to include the resource name.
591            // Since the client already expects this method to throw a
592            // NotFoundException, just throw one of those.
593            final NotFoundException nfe = new NotFoundException("Drawable " + name
594                    + " with resource ID #0x" + Integer.toHexString(id), e);
595            nfe.setStackTrace(new StackTraceElement[0]);
596            throw nfe;
597        }
598    }
599
600    private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
601            Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
602        final Drawable.ConstantState cs = dr.getConstantState();
603        if (cs == null) {
604            return;
605        }
606
607        if (mPreloading) {
608            final int changingConfigs = cs.getChangingConfigurations();
609            if (isColorDrawable) {
610                if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
611                    sPreloadedColorDrawables.put(key, cs);
612                }
613            } else {
614                if (verifyPreloadConfig(
615                        changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
616                    if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
617                        // If this resource does not vary based on layout direction,
618                        // we can put it in all of the preload maps.
619                        sPreloadedDrawables[0].put(key, cs);
620                        sPreloadedDrawables[1].put(key, cs);
621                    } else {
622                        // Otherwise, only in the layout dir we loaded it for.
623                        sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
624                    }
625                }
626            }
627        } else {
628            synchronized (mAccessLock) {
629                caches.put(key, theme, cs, usesTheme);
630            }
631        }
632    }
633
634    private boolean verifyPreloadConfig(@Config int changingConfigurations,
635            @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) {
636        // We allow preloading of resources even if they vary by font scale (which
637        // doesn't impact resource selection) or density (which we handle specially by
638        // simply turning off all preloading), as well as any other configs specified
639        // by the caller.
640        if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
641                ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
642            String resName;
643            try {
644                resName = getResourceName(resourceId);
645            } catch (NotFoundException e) {
646                resName = "?";
647            }
648            // This should never happen in production, so we should log a
649            // warning even if we're not debugging.
650            Log.w(TAG, "Preloaded " + name + " resource #0x"
651                    + Integer.toHexString(resourceId)
652                    + " (" + resName + ") that varies with configuration!!");
653            return false;
654        }
655        if (TRACE_FOR_PRELOAD) {
656            String resName;
657            try {
658                resName = getResourceName(resourceId);
659            } catch (NotFoundException e) {
660                resName = "?";
661            }
662            Log.w(TAG, "Preloading " + name + " resource #0x"
663                    + Integer.toHexString(resourceId)
664                    + " (" + resName + ")");
665        }
666        return true;
667    }
668
669    /**
670     * Loads a drawable from XML or resources stream.
671     */
672    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
673            Resources.Theme theme) {
674        if (value.string == null) {
675            throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
676                    + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
677        }
678
679        final String file = value.string.toString();
680
681        if (TRACE_FOR_MISS_PRELOAD) {
682            // Log only framework resources
683            if ((id >>> 24) == 0x1) {
684                final String name = getResourceName(id);
685                if (name != null) {
686                    Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
687                            + ": " + name + " at " + file);
688                }
689            }
690        }
691
692        if (DEBUG_LOAD) {
693            Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
694        }
695
696        final Drawable dr;
697
698        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
699        try {
700            if (file.endsWith(".xml")) {
701                final XmlResourceParser rp = loadXmlResourceParser(
702                        file, id, value.assetCookie, "drawable");
703                dr = Drawable.createFromXml(wrapper, rp, theme);
704                rp.close();
705            } else {
706                final InputStream is = mAssets.openNonAsset(
707                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
708                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
709                is.close();
710            }
711        } catch (Exception e) {
712            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
713            final NotFoundException rnf = new NotFoundException(
714                    "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
715            rnf.initCause(e);
716            throw rnf;
717        }
718        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
719
720        return dr;
721    }
722
723    /**
724     * Given the value and id, we can get the XML filename as in value.data, based on that, we
725     * first try to load CSL from the cache. If not found, try to get from the constant state.
726     * Last, parse the XML and generate the CSL.
727     */
728    private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
729            TypedValue value, int id) {
730        final long key = (((long) value.assetCookie) << 32) | value.data;
731        final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
732        ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
733        if (complexColor != null) {
734            return complexColor;
735        }
736
737        final android.content.res.ConstantState<ComplexColor> factory =
738                sPreloadedComplexColors.get(key);
739
740        if (factory != null) {
741            complexColor = factory.newInstance(wrapper, theme);
742        }
743        if (complexColor == null) {
744            complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
745        }
746
747        if (complexColor != null) {
748            complexColor.setBaseChangingConfigurations(value.changingConfigurations);
749
750            if (mPreloading) {
751                if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
752                        0, value.resourceId, "color")) {
753                    sPreloadedComplexColors.put(key, complexColor.getConstantState());
754                }
755            } else {
756                cache.put(key, theme, complexColor.getConstantState());
757            }
758        }
759        return complexColor;
760    }
761
762    @Nullable
763    ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
764            Resources.Theme theme) {
765        if (TRACE_FOR_PRELOAD) {
766            // Log only framework resources
767            if ((id >>> 24) == 0x1) {
768                final String name = getResourceName(id);
769                if (name != null) android.util.Log.d("loadComplexColor", name);
770            }
771        }
772
773        final long key = (((long) value.assetCookie) << 32) | value.data;
774
775        // Handle inline color definitions.
776        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
777                && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
778            return getColorStateListFromInt(value, key);
779        }
780
781        final String file = value.string.toString();
782
783        ComplexColor complexColor;
784        if (file.endsWith(".xml")) {
785            try {
786                complexColor = loadComplexColorFromName(wrapper, theme, value, id);
787            } catch (Exception e) {
788                final NotFoundException rnf = new NotFoundException(
789                        "File " + file + " from complex color resource ID #0x"
790                                + Integer.toHexString(id));
791                rnf.initCause(e);
792                throw rnf;
793            }
794        } else {
795            throw new NotFoundException(
796                    "File " + file + " from drawable resource ID #0x"
797                            + Integer.toHexString(id) + ": .xml extension required");
798        }
799
800        return complexColor;
801    }
802
803    @Nullable
804    ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
805            Resources.Theme theme)
806            throws NotFoundException {
807        if (TRACE_FOR_PRELOAD) {
808            // Log only framework resources
809            if ((id >>> 24) == 0x1) {
810                final String name = getResourceName(id);
811                if (name != null) android.util.Log.d("PreloadColorStateList", name);
812            }
813        }
814
815        final long key = (((long) value.assetCookie) << 32) | value.data;
816
817        // Handle inline color definitions.
818        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
819                && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
820            return getColorStateListFromInt(value, key);
821        }
822
823        ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
824        if (complexColor != null && complexColor instanceof ColorStateList) {
825            return (ColorStateList) complexColor;
826        }
827
828        throw new NotFoundException(
829                "Can't find ColorStateList from drawable resource ID #0x"
830                        + Integer.toHexString(id));
831    }
832
833    @NonNull
834    private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
835        ColorStateList csl;
836        final android.content.res.ConstantState<ComplexColor> factory =
837                sPreloadedComplexColors.get(key);
838        if (factory != null) {
839            return (ColorStateList) factory.newInstance();
840        }
841
842        csl = ColorStateList.valueOf(value.data);
843
844        if (mPreloading) {
845            if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
846                    "color")) {
847                sPreloadedComplexColors.put(key, csl.getConstantState());
848            }
849        }
850
851        return csl;
852    }
853
854    /**
855     * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
856     * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
857     *
858     * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
859     * and selector tag.
860     *
861     * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
862     */
863    @Nullable
864    private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
865            Resources.Theme theme) {
866        if (value.string == null) {
867            throw new UnsupportedOperationException(
868                    "Can't convert to ComplexColor: type=0x" + value.type);
869        }
870
871        final String file = value.string.toString();
872
873        if (TRACE_FOR_MISS_PRELOAD) {
874            // Log only framework resources
875            if ((id >>> 24) == 0x1) {
876                final String name = getResourceName(id);
877                if (name != null) {
878                    Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
879                            + ": " + name + " at " + file);
880                }
881            }
882        }
883
884        if (DEBUG_LOAD) {
885            Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
886        }
887
888        ComplexColor complexColor = null;
889
890        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
891        if (file.endsWith(".xml")) {
892            try {
893                final XmlResourceParser parser = loadXmlResourceParser(
894                        file, id, value.assetCookie, "ComplexColor");
895
896                final AttributeSet attrs = Xml.asAttributeSet(parser);
897                int type;
898                while ((type = parser.next()) != XmlPullParser.START_TAG
899                        && type != XmlPullParser.END_DOCUMENT) {
900                    // Seek parser to start tag.
901                }
902                if (type != XmlPullParser.START_TAG) {
903                    throw new XmlPullParserException("No start tag found");
904                }
905
906                final String name = parser.getName();
907                if (name.equals("gradient")) {
908                    complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
909                } else if (name.equals("selector")) {
910                    complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
911                }
912                parser.close();
913            } catch (Exception e) {
914                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
915                final NotFoundException rnf = new NotFoundException(
916                        "File " + file + " from ComplexColor resource ID #0x"
917                                + Integer.toHexString(id));
918                rnf.initCause(e);
919                throw rnf;
920            }
921        } else {
922            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
923            throw new NotFoundException(
924                    "File " + file + " from drawable resource ID #0x"
925                            + Integer.toHexString(id) + ": .xml extension required");
926        }
927        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
928
929        return complexColor;
930    }
931
932    /**
933     * Loads an XML parser for the specified file.
934     *
935     * @param file the path for the XML file to parse
936     * @param id the resource identifier for the file
937     * @param assetCookie the asset cookie for the file
938     * @param type the type of resource (used for logging)
939     * @return a parser for the specified XML file
940     * @throws NotFoundException if the file could not be loaded
941     */
942    @NonNull
943    XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
944            @NonNull String type)
945            throws NotFoundException {
946        if (id != 0) {
947            try {
948                synchronized (mCachedXmlBlocks) {
949                    final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
950                    final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
951                    final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
952                    // First see if this block is in our cache.
953                    final int num = cachedXmlBlockFiles.length;
954                    for (int i = 0; i < num; i++) {
955                        if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
956                                && cachedXmlBlockFiles[i].equals(file)) {
957                            return cachedXmlBlocks[i].newParser();
958                        }
959                    }
960
961                    // Not in the cache, create a new block and put it at
962                    // the next slot in the cache.
963                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
964                    if (block != null) {
965                        final int pos = (mLastCachedXmlBlockIndex + 1) % num;
966                        mLastCachedXmlBlockIndex = pos;
967                        final XmlBlock oldBlock = cachedXmlBlocks[pos];
968                        if (oldBlock != null) {
969                            oldBlock.close();
970                        }
971                        cachedXmlBlockCookies[pos] = assetCookie;
972                        cachedXmlBlockFiles[pos] = file;
973                        cachedXmlBlocks[pos] = block;
974                        return block.newParser();
975                    }
976                }
977            } catch (Exception e) {
978                final NotFoundException rnf = new NotFoundException("File " + file
979                        + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
980                rnf.initCause(e);
981                throw rnf;
982            }
983        }
984
985        throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
986                + Integer.toHexString(id));
987    }
988
989    /**
990     * Start preloading of resource data using this Resources object.  Only
991     * for use by the zygote process for loading common system resources.
992     * {@hide}
993     */
994    public final void startPreloading() {
995        synchronized (sSync) {
996            if (sPreloaded) {
997                throw new IllegalStateException("Resources already preloaded");
998            }
999            sPreloaded = true;
1000            mPreloading = true;
1001            mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1002            updateConfiguration(null, null, null);
1003        }
1004    }
1005
1006    /**
1007     * Called by zygote when it is done preloading resources, to change back
1008     * to normal Resources operation.
1009     */
1010    void finishPreloading() {
1011        if (mPreloading) {
1012            mPreloading = false;
1013            flushLayoutCache();
1014        }
1015    }
1016
1017    LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1018        return sPreloadedDrawables[0];
1019    }
1020
1021    ThemeImpl newThemeImpl() {
1022        return new ThemeImpl();
1023    }
1024
1025    /**
1026     * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1027     */
1028    ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1029        ThemeImpl impl = new ThemeImpl();
1030        impl.mKey.setTo(key);
1031        impl.rebase();
1032        return impl;
1033    }
1034
1035    public class ThemeImpl {
1036        /**
1037         * Unique key for the series of styles applied to this theme.
1038         */
1039        private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1040
1041        @SuppressWarnings("hiding")
1042        private final AssetManager mAssets;
1043        private final long mTheme;
1044
1045        /**
1046         * Resource identifier for the theme.
1047         */
1048        private int mThemeResId = 0;
1049
1050        /*package*/ ThemeImpl() {
1051            mAssets = ResourcesImpl.this.mAssets;
1052            mTheme = mAssets.createTheme();
1053        }
1054
1055        @Override
1056        protected void finalize() throws Throwable {
1057            super.finalize();
1058            mAssets.releaseTheme(mTheme);
1059        }
1060
1061        /*package*/ Resources.ThemeKey getKey() {
1062            return mKey;
1063        }
1064
1065        /*package*/ long getNativeTheme() {
1066            return mTheme;
1067        }
1068
1069        /*package*/ int getAppliedStyleResId() {
1070            return mThemeResId;
1071        }
1072
1073        void applyStyle(int resId, boolean force) {
1074            synchronized (mKey) {
1075                AssetManager.applyThemeStyle(mTheme, resId, force);
1076
1077                mThemeResId = resId;
1078                mKey.append(resId, force);
1079            }
1080        }
1081
1082        void setTo(ThemeImpl other) {
1083            synchronized (mKey) {
1084                synchronized (other.mKey) {
1085                    AssetManager.copyTheme(mTheme, other.mTheme);
1086
1087                    mThemeResId = other.mThemeResId;
1088                    mKey.setTo(other.getKey());
1089                }
1090            }
1091        }
1092
1093        @NonNull
1094        TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
1095                AttributeSet set,
1096                @StyleableRes int[] attrs,
1097                @AttrRes int defStyleAttr,
1098                @StyleRes int defStyleRes) {
1099            synchronized (mKey) {
1100                final int len = attrs.length;
1101                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1102
1103                // XXX note that for now we only work with compiled XML files.
1104                // To support generic XML files we will need to manually parse
1105                // out the attributes from the XML file (applying type information
1106                // contained in the resources and such).
1107                final XmlBlock.Parser parser = (XmlBlock.Parser) set;
1108                AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1109                        parser != null ? parser.mParseState : 0,
1110                        attrs, array.mData, array.mIndices);
1111                array.mTheme = wrapper;
1112                array.mXml = parser;
1113
1114                return array;
1115            }
1116        }
1117
1118        @NonNull
1119        TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
1120                @NonNull int[] values,
1121                @NonNull int[] attrs) {
1122            synchronized (mKey) {
1123                final int len = attrs.length;
1124                if (values == null || len != values.length) {
1125                    throw new IllegalArgumentException(
1126                            "Base attribute values must the same length as attrs");
1127                }
1128
1129                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1130                AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
1131                array.mTheme = wrapper;
1132                array.mXml = null;
1133                return array;
1134            }
1135        }
1136
1137        boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1138            synchronized (mKey) {
1139                return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1140            }
1141        }
1142
1143        int[] getAllAttributes() {
1144            return mAssets.getStyleAttributes(getAppliedStyleResId());
1145        }
1146
1147        @Config int getChangingConfigurations() {
1148            synchronized (mKey) {
1149                final int nativeChangingConfig =
1150                        AssetManager.getThemeChangingConfigurations(mTheme);
1151                return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1152            }
1153        }
1154
1155        public void dump(int priority, String tag, String prefix) {
1156            synchronized (mKey) {
1157                AssetManager.dumpTheme(mTheme, priority, tag, prefix);
1158            }
1159        }
1160
1161        String[] getTheme() {
1162            synchronized (mKey) {
1163                final int N = mKey.mCount;
1164                final String[] themes = new String[N * 2];
1165                for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1166                    final int resId = mKey.mResId[j];
1167                    final boolean forced = mKey.mForce[j];
1168                    try {
1169                        themes[i] = getResourceName(resId);
1170                    } catch (NotFoundException e) {
1171                        themes[i] = Integer.toHexString(i);
1172                    }
1173                    themes[i + 1] = forced ? "forced" : "not forced";
1174                }
1175                return themes;
1176            }
1177        }
1178
1179        /**
1180         * Rebases the theme against the parent Resource object's current
1181         * configuration by re-applying the styles passed to
1182         * {@link #applyStyle(int, boolean)}.
1183         */
1184        void rebase() {
1185            synchronized (mKey) {
1186                AssetManager.clearTheme(mTheme);
1187
1188                // Reapply the same styles in the same order.
1189                for (int i = 0; i < mKey.mCount; i++) {
1190                    final int resId = mKey.mResId[i];
1191                    final boolean force = mKey.mForce[i];
1192                    AssetManager.applyThemeStyle(mTheme, resId, force);
1193                }
1194            }
1195        }
1196    }
1197}
1198