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