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