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