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