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