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