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