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