ResourcesImpl.java revision fe98ad919bebaf41191b7fdf0a1d841198d4f46a
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.Typeface;
36import android.graphics.drawable.ColorDrawable;
37import android.graphics.drawable.Drawable;
38import android.icu.text.PluralRules;
39import android.os.Build;
40import android.os.LocaleList;
41import android.os.Trace;
42import android.text.FontConfig;
43import android.util.AttributeSet;
44import android.util.DisplayMetrics;
45import android.util.Log;
46import android.util.LongSparseArray;
47import android.util.Slog;
48import android.util.TypedValue;
49import android.util.Xml;
50import android.view.DisplayAdjustments;
51
52import java.io.IOException;
53import java.io.InputStream;
54import java.util.Arrays;
55import java.util.Locale;
56
57/**
58 * The implementation of Resource access. This class contains the AssetManager and all caches
59 * associated with it.
60 *
61 * {@link Resources} is just a thing wrapper around this class. When a configuration change
62 * occurs, clients can retain the same {@link Resources} reference because the underlying
63 * {@link ResourcesImpl} object will be updated or re-created.
64 *
65 * @hide
66 */
67public class ResourcesImpl {
68    static final String TAG = "Resources";
69
70    private static final boolean DEBUG_LOAD = false;
71    private static final boolean DEBUG_CONFIG = false;
72    private static final boolean TRACE_FOR_PRELOAD = false;
73    private static final boolean TRACE_FOR_MISS_PRELOAD = false;
74
75    private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
76            ActivityInfo.CONFIG_LAYOUT_DIRECTION);
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, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
638                    if ((changingConfigs & LAYOUT_DIR_CONFIG) == 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.createFromCache(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 FontConfig config = FontResourcesParser.parse(rp, wrapper);
771                return Typeface.createFromResources(config, mAssets, file);
772            }
773            return Typeface.createFromResources(mAssets, file, value.assetCookie);
774        } catch (XmlPullParserException e) {
775            Log.e(TAG, "Failed to parse xml resource " + file, e);
776        } catch (IOException e) {
777            Log.e(TAG, "Failed to read xml resource " + file, e);
778        } finally {
779            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
780        }
781        return null;
782    }
783
784    /**
785     * Given the value and id, we can get the XML filename as in value.data, based on that, we
786     * first try to load CSL from the cache. If not found, try to get from the constant state.
787     * Last, parse the XML and generate the CSL.
788     */
789    private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
790            TypedValue value, int id) {
791        final long key = (((long) value.assetCookie) << 32) | value.data;
792        final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
793        ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
794        if (complexColor != null) {
795            return complexColor;
796        }
797
798        final android.content.res.ConstantState<ComplexColor> factory =
799                sPreloadedComplexColors.get(key);
800
801        if (factory != null) {
802            complexColor = factory.newInstance(wrapper, theme);
803        }
804        if (complexColor == null) {
805            complexColor = loadComplexColorForCookie(wrapper, value, id, theme);
806        }
807
808        if (complexColor != null) {
809            complexColor.setBaseChangingConfigurations(value.changingConfigurations);
810
811            if (mPreloading) {
812                if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
813                        0, value.resourceId, "color")) {
814                    sPreloadedComplexColors.put(key, complexColor.getConstantState());
815                }
816            } else {
817                cache.put(key, theme, complexColor.getConstantState());
818            }
819        }
820        return complexColor;
821    }
822
823    @Nullable
824    ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
825            Resources.Theme theme) {
826        if (TRACE_FOR_PRELOAD) {
827            // Log only framework resources
828            if ((id >>> 24) == 0x1) {
829                final String name = getResourceName(id);
830                if (name != null) android.util.Log.d("loadComplexColor", name);
831            }
832        }
833
834        final long key = (((long) value.assetCookie) << 32) | value.data;
835
836        // Handle inline color definitions.
837        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
838                && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
839            return getColorStateListFromInt(value, key);
840        }
841
842        final String file = value.string.toString();
843
844        ComplexColor complexColor;
845        if (file.endsWith(".xml")) {
846            try {
847                complexColor = loadComplexColorFromName(wrapper, theme, value, id);
848            } catch (Exception e) {
849                final NotFoundException rnf = new NotFoundException(
850                        "File " + file + " from complex color resource ID #0x"
851                                + Integer.toHexString(id));
852                rnf.initCause(e);
853                throw rnf;
854            }
855        } else {
856            throw new NotFoundException(
857                    "File " + file + " from drawable resource ID #0x"
858                            + Integer.toHexString(id) + ": .xml extension required");
859        }
860
861        return complexColor;
862    }
863
864    @Nullable
865    ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
866            Resources.Theme theme)
867            throws NotFoundException {
868        if (TRACE_FOR_PRELOAD) {
869            // Log only framework resources
870            if ((id >>> 24) == 0x1) {
871                final String name = getResourceName(id);
872                if (name != null) android.util.Log.d("PreloadColorStateList", name);
873            }
874        }
875
876        final long key = (((long) value.assetCookie) << 32) | value.data;
877
878        // Handle inline color definitions.
879        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
880                && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
881            return getColorStateListFromInt(value, key);
882        }
883
884        ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
885        if (complexColor != null && complexColor instanceof ColorStateList) {
886            return (ColorStateList) complexColor;
887        }
888
889        throw new NotFoundException(
890                "Can't find ColorStateList from drawable resource ID #0x"
891                        + Integer.toHexString(id));
892    }
893
894    @NonNull
895    private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
896        ColorStateList csl;
897        final android.content.res.ConstantState<ComplexColor> factory =
898                sPreloadedComplexColors.get(key);
899        if (factory != null) {
900            return (ColorStateList) factory.newInstance();
901        }
902
903        csl = ColorStateList.valueOf(value.data);
904
905        if (mPreloading) {
906            if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
907                    "color")) {
908                sPreloadedComplexColors.put(key, csl.getConstantState());
909            }
910        }
911
912        return csl;
913    }
914
915    /**
916     * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
917     * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
918     *
919     * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
920     * and selector tag.
921     *
922     * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
923     */
924    @Nullable
925    private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
926            Resources.Theme theme) {
927        if (value.string == null) {
928            throw new UnsupportedOperationException(
929                    "Can't convert to ComplexColor: type=0x" + value.type);
930        }
931
932        final String file = value.string.toString();
933
934        if (TRACE_FOR_MISS_PRELOAD) {
935            // Log only framework resources
936            if ((id >>> 24) == 0x1) {
937                final String name = getResourceName(id);
938                if (name != null) {
939                    Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id)
940                            + ": " + name + " at " + file);
941                }
942            }
943        }
944
945        if (DEBUG_LOAD) {
946            Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
947        }
948
949        ComplexColor complexColor = null;
950
951        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
952        if (file.endsWith(".xml")) {
953            try {
954                final XmlResourceParser parser = loadXmlResourceParser(
955                        file, id, value.assetCookie, "ComplexColor");
956
957                final AttributeSet attrs = Xml.asAttributeSet(parser);
958                int type;
959                while ((type = parser.next()) != XmlPullParser.START_TAG
960                        && type != XmlPullParser.END_DOCUMENT) {
961                    // Seek parser to start tag.
962                }
963                if (type != XmlPullParser.START_TAG) {
964                    throw new XmlPullParserException("No start tag found");
965                }
966
967                final String name = parser.getName();
968                if (name.equals("gradient")) {
969                    complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);
970                } else if (name.equals("selector")) {
971                    complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);
972                }
973                parser.close();
974            } catch (Exception e) {
975                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
976                final NotFoundException rnf = new NotFoundException(
977                        "File " + file + " from ComplexColor resource ID #0x"
978                                + Integer.toHexString(id));
979                rnf.initCause(e);
980                throw rnf;
981            }
982        } else {
983            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
984            throw new NotFoundException(
985                    "File " + file + " from drawable resource ID #0x"
986                            + Integer.toHexString(id) + ": .xml extension required");
987        }
988        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
989
990        return complexColor;
991    }
992
993    /**
994     * Loads an XML parser for the specified file.
995     *
996     * @param file the path for the XML file to parse
997     * @param id the resource identifier for the file
998     * @param assetCookie the asset cookie for the file
999     * @param type the type of resource (used for logging)
1000     * @return a parser for the specified XML file
1001     * @throws NotFoundException if the file could not be loaded
1002     */
1003    @NonNull
1004    XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
1005            @NonNull String type)
1006            throws NotFoundException {
1007        if (id != 0) {
1008            try {
1009                synchronized (mCachedXmlBlocks) {
1010                    final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
1011                    final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
1012                    final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
1013                    // First see if this block is in our cache.
1014                    final int num = cachedXmlBlockFiles.length;
1015                    for (int i = 0; i < num; i++) {
1016                        if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
1017                                && cachedXmlBlockFiles[i].equals(file)) {
1018                            return cachedXmlBlocks[i].newParser();
1019                        }
1020                    }
1021
1022                    // Not in the cache, create a new block and put it at
1023                    // the next slot in the cache.
1024                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
1025                    if (block != null) {
1026                        final int pos = (mLastCachedXmlBlockIndex + 1) % num;
1027                        mLastCachedXmlBlockIndex = pos;
1028                        final XmlBlock oldBlock = cachedXmlBlocks[pos];
1029                        if (oldBlock != null) {
1030                            oldBlock.close();
1031                        }
1032                        cachedXmlBlockCookies[pos] = assetCookie;
1033                        cachedXmlBlockFiles[pos] = file;
1034                        cachedXmlBlocks[pos] = block;
1035                        return block.newParser();
1036                    }
1037                }
1038            } catch (Exception e) {
1039                final NotFoundException rnf = new NotFoundException("File " + file
1040                        + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
1041                rnf.initCause(e);
1042                throw rnf;
1043            }
1044        }
1045
1046        throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
1047                + Integer.toHexString(id));
1048    }
1049
1050    /**
1051     * Start preloading of resource data using this Resources object.  Only
1052     * for use by the zygote process for loading common system resources.
1053     * {@hide}
1054     */
1055    public final void startPreloading() {
1056        synchronized (sSync) {
1057            if (sPreloaded) {
1058                throw new IllegalStateException("Resources already preloaded");
1059            }
1060            sPreloaded = true;
1061            mPreloading = true;
1062            mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
1063            updateConfiguration(null, null, null);
1064        }
1065    }
1066
1067    /**
1068     * Called by zygote when it is done preloading resources, to change back
1069     * to normal Resources operation.
1070     */
1071    void finishPreloading() {
1072        if (mPreloading) {
1073            mPreloading = false;
1074            flushLayoutCache();
1075        }
1076    }
1077
1078    LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
1079        return sPreloadedDrawables[0];
1080    }
1081
1082    ThemeImpl newThemeImpl() {
1083        return new ThemeImpl();
1084    }
1085
1086    /**
1087     * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
1088     */
1089    ThemeImpl newThemeImpl(Resources.ThemeKey key) {
1090        ThemeImpl impl = new ThemeImpl();
1091        impl.mKey.setTo(key);
1092        impl.rebase();
1093        return impl;
1094    }
1095
1096    public class ThemeImpl {
1097        /**
1098         * Unique key for the series of styles applied to this theme.
1099         */
1100        private final Resources.ThemeKey mKey = new Resources.ThemeKey();
1101
1102        @SuppressWarnings("hiding")
1103        private final AssetManager mAssets;
1104        private final long mTheme;
1105
1106        /**
1107         * Resource identifier for the theme.
1108         */
1109        private int mThemeResId = 0;
1110
1111        /*package*/ ThemeImpl() {
1112            mAssets = ResourcesImpl.this.mAssets;
1113            mTheme = mAssets.createTheme();
1114        }
1115
1116        @Override
1117        protected void finalize() throws Throwable {
1118            super.finalize();
1119            mAssets.releaseTheme(mTheme);
1120        }
1121
1122        /*package*/ Resources.ThemeKey getKey() {
1123            return mKey;
1124        }
1125
1126        /*package*/ long getNativeTheme() {
1127            return mTheme;
1128        }
1129
1130        /*package*/ int getAppliedStyleResId() {
1131            return mThemeResId;
1132        }
1133
1134        void applyStyle(int resId, boolean force) {
1135            synchronized (mKey) {
1136                AssetManager.applyThemeStyle(mTheme, resId, force);
1137
1138                mThemeResId = resId;
1139                mKey.append(resId, force);
1140            }
1141        }
1142
1143        void setTo(ThemeImpl other) {
1144            synchronized (mKey) {
1145                synchronized (other.mKey) {
1146                    AssetManager.copyTheme(mTheme, other.mTheme);
1147
1148                    mThemeResId = other.mThemeResId;
1149                    mKey.setTo(other.getKey());
1150                }
1151            }
1152        }
1153
1154        @NonNull
1155        TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
1156                AttributeSet set,
1157                @StyleableRes int[] attrs,
1158                @AttrRes int defStyleAttr,
1159                @StyleRes int defStyleRes) {
1160            synchronized (mKey) {
1161                final int len = attrs.length;
1162                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1163
1164                // XXX note that for now we only work with compiled XML files.
1165                // To support generic XML files we will need to manually parse
1166                // out the attributes from the XML file (applying type information
1167                // contained in the resources and such).
1168                final XmlBlock.Parser parser = (XmlBlock.Parser) set;
1169                AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
1170                        parser != null ? parser.mParseState : 0,
1171                        attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
1172                array.mTheme = wrapper;
1173                array.mXml = parser;
1174
1175                return array;
1176            }
1177        }
1178
1179        @NonNull
1180        TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
1181                @NonNull int[] values,
1182                @NonNull int[] attrs) {
1183            synchronized (mKey) {
1184                final int len = attrs.length;
1185                if (values == null || len != values.length) {
1186                    throw new IllegalArgumentException(
1187                            "Base attribute values must the same length as attrs");
1188                }
1189
1190                final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
1191                AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
1192                array.mTheme = wrapper;
1193                array.mXml = null;
1194                return array;
1195            }
1196        }
1197
1198        boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
1199            synchronized (mKey) {
1200                return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
1201            }
1202        }
1203
1204        int[] getAllAttributes() {
1205            return mAssets.getStyleAttributes(getAppliedStyleResId());
1206        }
1207
1208        @Config int getChangingConfigurations() {
1209            synchronized (mKey) {
1210                final @NativeConfig int nativeChangingConfig =
1211                        AssetManager.getThemeChangingConfigurations(mTheme);
1212                return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
1213            }
1214        }
1215
1216        public void dump(int priority, String tag, String prefix) {
1217            synchronized (mKey) {
1218                AssetManager.dumpTheme(mTheme, priority, tag, prefix);
1219            }
1220        }
1221
1222        String[] getTheme() {
1223            synchronized (mKey) {
1224                final int N = mKey.mCount;
1225                final String[] themes = new String[N * 2];
1226                for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
1227                    final int resId = mKey.mResId[j];
1228                    final boolean forced = mKey.mForce[j];
1229                    try {
1230                        themes[i] = getResourceName(resId);
1231                    } catch (NotFoundException e) {
1232                        themes[i] = Integer.toHexString(i);
1233                    }
1234                    themes[i + 1] = forced ? "forced" : "not forced";
1235                }
1236                return themes;
1237            }
1238        }
1239
1240        /**
1241         * Rebases the theme against the parent Resource object's current
1242         * configuration by re-applying the styles passed to
1243         * {@link #applyStyle(int, boolean)}.
1244         */
1245        void rebase() {
1246            synchronized (mKey) {
1247                AssetManager.clearTheme(mTheme);
1248
1249                // Reapply the same styles in the same order.
1250                for (int i = 0; i < mKey.mCount; i++) {
1251                    final int resId = mKey.mResId[i];
1252                    final boolean force = mKey.mForce[i];
1253                    AssetManager.applyThemeStyle(mTheme, resId, force);
1254                }
1255            }
1256        }
1257    }
1258}
1259