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