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