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