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