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