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