ResourcesImpl.java revision 0b9295d06750dc6da032a2b2092e2c500c65393f
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 */ 16package android.content.res; 17 18import org.xmlpull.v1.XmlPullParser; 19import org.xmlpull.v1.XmlPullParserException; 20 21import android.animation.Animator; 22import android.animation.StateListAnimator; 23import android.annotation.AnyRes; 24import android.annotation.AttrRes; 25import android.annotation.NonNull; 26import android.annotation.Nullable; 27import android.annotation.PluralsRes; 28import android.annotation.RawRes; 29import android.annotation.StyleRes; 30import android.annotation.StyleableRes; 31import android.content.pm.ActivityInfo; 32import android.content.pm.ActivityInfo.Config; 33import android.content.res.Resources.NotFoundException; 34import android.graphics.drawable.ColorDrawable; 35import android.graphics.drawable.Drawable; 36import android.icu.text.PluralRules; 37import android.os.Build; 38import android.os.Trace; 39import android.util.AttributeSet; 40import android.util.DisplayMetrics; 41import android.util.LocaleList; 42import android.util.Log; 43import android.util.LongSparseArray; 44import android.util.Slog; 45import android.util.TypedValue; 46import android.util.Xml; 47 48import java.io.InputStream; 49import java.util.Arrays; 50import java.util.Locale; 51 52/** 53 * The implementation of Resource access. This class contains the AssetManager and all caches 54 * associated with it. 55 * 56 * {@link Resources} is just a thing wrapper around this class. When a configuration change 57 * occurs, clients can retain the same {@link Resources} reference because the underlying 58 * {@link ResourcesImpl} object will be updated or re-created. 59 * 60 * @hide 61 */ 62public class ResourcesImpl { 63 static final String TAG = "Resources"; 64 65 private static final boolean DEBUG_LOAD = false; 66 private static final boolean DEBUG_CONFIG = false; 67 private static final boolean TRACE_FOR_PRELOAD = false; 68 private static final boolean TRACE_FOR_MISS_PRELOAD = false; 69 70 private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative( 71 ActivityInfo.CONFIG_LAYOUT_DIRECTION); 72 73 private static final int ID_OTHER = 0x01000004; 74 75 private static final Object sSync = new Object(); 76 77 private static boolean sPreloaded; 78 private boolean mPreloading; 79 80 // Information about preloaded resources. Note that they are not 81 // protected by a lock, because while preloading in zygote we are all 82 // single-threaded, and after that these are immutable. 83 private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables; 84 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables 85 = new LongSparseArray<>(); 86 private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>> 87 sPreloadedComplexColors = new LongSparseArray<>(); 88 89 /** Lock object used to protect access to caches and configuration. */ 90 private final Object mAccessLock = new Object(); 91 92 // These are protected by mAccessLock. 93 private final Configuration mTmpConfig = new Configuration(); 94 private final DrawableCache mDrawableCache = new DrawableCache(); 95 private final DrawableCache mColorDrawableCache = new DrawableCache(); 96 private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache = 97 new ConfigurationBoundResourceCache<>(); 98 private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = 99 new ConfigurationBoundResourceCache<>(); 100 private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = 101 new ConfigurationBoundResourceCache<>(); 102 103 /** Size of the cyclical cache used to map XML files to blocks. */ 104 private static final int XML_BLOCK_CACHE_SIZE = 4; 105 106 // Cyclical cache used for recently-accessed XML files. 107 private int mLastCachedXmlBlockIndex = -1; 108 private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE]; 109 private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE]; 110 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE]; 111 112 113 final AssetManager mAssets; 114 final DisplayMetrics mMetrics = new DisplayMetrics(); 115 116 private PluralRules mPluralRule; 117 118 private final Configuration mConfiguration = new Configuration(); 119 private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; 120 121 static { 122 sPreloadedDrawables = new LongSparseArray[2]; 123 sPreloadedDrawables[0] = new LongSparseArray<>(); 124 sPreloadedDrawables[1] = new LongSparseArray<>(); 125 } 126 127 /** 128 * Creates a new ResourcesImpl object with CompatibilityInfo. 129 * 130 * @param assets Previously created AssetManager. 131 * @param metrics Current display metrics to consider when 132 * selecting/computing resource values. 133 * @param config Desired device configuration to consider when 134 * selecting/computing resource values (optional). 135 * @param compatInfo this resource's compatibility info. Must not be null. 136 */ 137 public ResourcesImpl(AssetManager assets, DisplayMetrics metrics, Configuration config, 138 CompatibilityInfo compatInfo) { 139 mAssets = assets; 140 mMetrics.setToDefaults(); 141 updateConfiguration(config, metrics, compatInfo); 142 mAssets.ensureStringBlocks(); 143 } 144 145 public AssetManager getAssets() { 146 return mAssets; 147 } 148 149 DisplayMetrics getDisplayMetrics() { 150 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels 151 + "x" + mMetrics.heightPixels + " " + mMetrics.density); 152 return mMetrics; 153 } 154 155 Configuration getConfiguration() { 156 return mConfiguration; 157 } 158 159 Configuration[] getSizeConfigurations() { 160 return mAssets.getSizeConfigurations(); 161 } 162 163 CompatibilityInfo getCompatibilityInfo() { 164 return mCompatibilityInfo; 165 } 166 167 private PluralRules getPluralRule() { 168 synchronized (sSync) { 169 if (mPluralRule == null) { 170 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0)); 171 } 172 return mPluralRule; 173 } 174 } 175 176 void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs) 177 throws NotFoundException { 178 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs); 179 if (found) { 180 return; 181 } 182 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)); 183 } 184 185 void getValueForDensity(@AnyRes int id, int density, TypedValue outValue, 186 boolean resolveRefs) throws NotFoundException { 187 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs); 188 if (found) { 189 return; 190 } 191 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)); 192 } 193 194 void getValue(String name, TypedValue outValue, boolean resolveRefs) 195 throws NotFoundException { 196 int id = getIdentifier(name, "string", null); 197 if (id != 0) { 198 getValue(id, outValue, resolveRefs); 199 return; 200 } 201 throw new NotFoundException("String resource name " + name); 202 } 203 204 int getIdentifier(String name, String defType, String defPackage) { 205 if (name == null) { 206 throw new NullPointerException("name is null"); 207 } 208 try { 209 return Integer.parseInt(name); 210 } catch (Exception e) { 211 // Ignore 212 } 213 return mAssets.getResourceIdentifier(name, defType, defPackage); 214 } 215 216 @NonNull 217 String getResourceName(@AnyRes int resid) throws NotFoundException { 218 String str = mAssets.getResourceName(resid); 219 if (str != null) return str; 220 throw new NotFoundException("Unable to find resource ID #0x" 221 + Integer.toHexString(resid)); 222 } 223 224 @NonNull 225 String getResourcePackageName(@AnyRes int resid) throws NotFoundException { 226 String str = mAssets.getResourcePackageName(resid); 227 if (str != null) return str; 228 throw new NotFoundException("Unable to find resource ID #0x" 229 + Integer.toHexString(resid)); 230 } 231 232 @NonNull 233 String getResourceTypeName(@AnyRes int resid) throws NotFoundException { 234 String str = mAssets.getResourceTypeName(resid); 235 if (str != null) return str; 236 throw new NotFoundException("Unable to find resource ID #0x" 237 + Integer.toHexString(resid)); 238 } 239 240 @NonNull 241 String getResourceEntryName(@AnyRes int resid) throws NotFoundException { 242 String str = mAssets.getResourceEntryName(resid); 243 if (str != null) return str; 244 throw new NotFoundException("Unable to find resource ID #0x" 245 + Integer.toHexString(resid)); 246 } 247 248 @NonNull 249 CharSequence getQuantityText(@PluralsRes int id, int quantity) throws NotFoundException { 250 PluralRules rule = getPluralRule(); 251 CharSequence res = mAssets.getResourceBagText(id, 252 attrForQuantityCode(rule.select(quantity))); 253 if (res != null) { 254 return res; 255 } 256 res = mAssets.getResourceBagText(id, ID_OTHER); 257 if (res != null) { 258 return res; 259 } 260 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id) 261 + " quantity=" + quantity 262 + " item=" + rule.select(quantity)); 263 } 264 265 private static int attrForQuantityCode(String quantityCode) { 266 switch (quantityCode) { 267 case PluralRules.KEYWORD_ZERO: return 0x01000005; 268 case PluralRules.KEYWORD_ONE: return 0x01000006; 269 case PluralRules.KEYWORD_TWO: return 0x01000007; 270 case PluralRules.KEYWORD_FEW: return 0x01000008; 271 case PluralRules.KEYWORD_MANY: return 0x01000009; 272 default: return ID_OTHER; 273 } 274 } 275 276 @NonNull 277 AssetFileDescriptor openRawResourceFd(@RawRes int id, TypedValue tempValue) 278 throws NotFoundException { 279 getValue(id, tempValue, true); 280 try { 281 return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString()); 282 } catch (Exception e) { 283 throw new NotFoundException("File " + tempValue.string.toString() + " from drawable " 284 + "resource ID #0x" + Integer.toHexString(id), e); 285 } 286 } 287 288 @NonNull 289 InputStream openRawResource(@RawRes int id, TypedValue value) throws NotFoundException { 290 getValue(id, value, true); 291 try { 292 return mAssets.openNonAsset(value.assetCookie, value.string.toString(), 293 AssetManager.ACCESS_STREAMING); 294 } catch (Exception e) { 295 NotFoundException rnf = new NotFoundException("File " + value.string.toString() + 296 " from drawable resource ID #0x" + Integer.toHexString(id)); 297 rnf.initCause(e); 298 throw rnf; 299 } 300 } 301 302 ConfigurationBoundResourceCache<Animator> getAnimatorCache() { 303 return mAnimatorCache; 304 } 305 306 ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() { 307 return mStateListAnimatorCache; 308 } 309 310 public void updateConfiguration(Configuration config, DisplayMetrics metrics, 311 CompatibilityInfo compat) { 312 synchronized (mAccessLock) { 313 if (false) { 314 Slog.i(TAG, "**** Updating config of " + this + ": old config is " 315 + mConfiguration + " old compat is " + mCompatibilityInfo); 316 Slog.i(TAG, "**** Updating config of " + this + ": new config is " 317 + config + " new compat is " + compat); 318 } 319 if (compat != null) { 320 mCompatibilityInfo = compat; 321 } 322 if (metrics != null) { 323 mMetrics.setTo(metrics); 324 } 325 // NOTE: We should re-arrange this code to create a Display 326 // with the CompatibilityInfo that is used everywhere we deal 327 // with the display in relation to this app, rather than 328 // doing the conversion here. This impl should be okay because 329 // we make sure to return a compatible display in the places 330 // where there are public APIs to retrieve the display... but 331 // it would be cleaner and more maintainble to just be 332 // consistently dealing with a compatible display everywhere in 333 // the framework. 334 mCompatibilityInfo.applyToDisplayMetrics(mMetrics); 335 336 final @Config int configChanges = calcConfigChanges(config); 337 338 LocaleList locales = mConfiguration.getLocales(); 339 if (locales.isEmpty()) { 340 locales = LocaleList.getAdjustedDefault(); 341 mConfiguration.setLocales(locales); 342 } 343 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { 344 mMetrics.densityDpi = mConfiguration.densityDpi; 345 mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 346 } 347 mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; 348 349 final int width, height; 350 if (mMetrics.widthPixels >= mMetrics.heightPixels) { 351 width = mMetrics.widthPixels; 352 height = mMetrics.heightPixels; 353 } else { 354 //noinspection SuspiciousNameCombination 355 width = mMetrics.heightPixels; 356 //noinspection SuspiciousNameCombination 357 height = mMetrics.widthPixels; 358 } 359 360 final int keyboardHidden; 361 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO 362 && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { 363 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; 364 } else { 365 keyboardHidden = mConfiguration.keyboardHidden; 366 } 367 368 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, 369 adjustLanguageTag(locales.get(0).toLanguageTag()), 370 mConfiguration.orientation, 371 mConfiguration.touchscreen, 372 mConfiguration.densityDpi, mConfiguration.keyboard, 373 keyboardHidden, mConfiguration.navigation, width, height, 374 mConfiguration.smallestScreenWidthDp, 375 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, 376 mConfiguration.screenLayout, mConfiguration.uiMode, 377 Build.VERSION.RESOURCES_SDK_INT); 378 379 if (DEBUG_CONFIG) { 380 Slog.i(TAG, "**** Updating config of " + this + ": final config is " 381 + mConfiguration + " final compat is " + mCompatibilityInfo); 382 } 383 384 mDrawableCache.onConfigurationChange(configChanges); 385 mColorDrawableCache.onConfigurationChange(configChanges); 386 mComplexColorCache.onConfigurationChange(configChanges); 387 mAnimatorCache.onConfigurationChange(configChanges); 388 mStateListAnimatorCache.onConfigurationChange(configChanges); 389 390 flushLayoutCache(); 391 } 392 synchronized (sSync) { 393 if (mPluralRule != null) { 394 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0)); 395 } 396 } 397 } 398 399 /** 400 * Applies the new configuration, returning a bitmask of the changes 401 * between the old and new configurations. 402 * 403 * @param config the new configuration 404 * @return bitmask of config changes 405 */ 406 public @Config int calcConfigChanges(@Nullable Configuration config) { 407 if (config == null) { 408 // If there is no configuration, assume all flags have changed. 409 return 0xFFFFFFFF; 410 } 411 412 mTmpConfig.setTo(config); 413 int density = config.densityDpi; 414 if (density == Configuration.DENSITY_DPI_UNDEFINED) { 415 density = mMetrics.noncompatDensityDpi; 416 } 417 418 mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); 419 420 if (mTmpConfig.getLocales().isEmpty()) { 421 mTmpConfig.setLocales(LocaleList.getDefault()); 422 } 423 return mConfiguration.updateFrom(mTmpConfig); 424 } 425 426 /** 427 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) 428 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. 429 * 430 * All released versions of android prior to "L" used the deprecated language 431 * tags, so we will need to support them for backwards compatibility. 432 * 433 * Note that this conversion needs to take place *after* the call to 434 * {@code toLanguageTag} because that will convert all the deprecated codes to 435 * the new ones, even if they're set manually. 436 */ 437 private static String adjustLanguageTag(String languageTag) { 438 final int separator = languageTag.indexOf('-'); 439 final String language; 440 final String remainder; 441 442 if (separator == -1) { 443 language = languageTag; 444 remainder = ""; 445 } else { 446 language = languageTag.substring(0, separator); 447 remainder = languageTag.substring(separator); 448 } 449 450 return Locale.adjustLanguageCode(language) + remainder; 451 } 452 453 /** 454 * Call this to remove all cached loaded layout resources from the 455 * Resources object. Only intended for use with performance testing 456 * tools. 457 */ 458 public void flushLayoutCache() { 459 synchronized (mCachedXmlBlocks) { 460 Arrays.fill(mCachedXmlBlockCookies, 0); 461 Arrays.fill(mCachedXmlBlockFiles, null); 462 463 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; 464 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) { 465 final XmlBlock oldBlock = cachedXmlBlocks[i]; 466 if (oldBlock != null) { 467 oldBlock.close(); 468 } 469 } 470 Arrays.fill(cachedXmlBlocks, null); 471 } 472 } 473 474 @Nullable 475 Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, 476 boolean useCache) throws NotFoundException { 477 try { 478 if (TRACE_FOR_PRELOAD) { 479 // Log only framework resources 480 if ((id >>> 24) == 0x1) { 481 final String name = getResourceName(id); 482 if (name != null) { 483 Log.d("PreloadDrawable", name); 484 } 485 } 486 } 487 488 final boolean isColorDrawable; 489 final DrawableCache caches; 490 final long key; 491 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 492 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 493 isColorDrawable = true; 494 caches = mColorDrawableCache; 495 key = value.data; 496 } else { 497 isColorDrawable = false; 498 caches = mDrawableCache; 499 key = (((long) value.assetCookie) << 32) | value.data; 500 } 501 502 // First, check whether we have a cached version of this drawable 503 // that was inflated against the specified theme. Skip the cache if 504 // we're currently preloading or we're not using the cache. 505 if (!mPreloading && useCache) { 506 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme); 507 if (cachedDrawable != null) { 508 return cachedDrawable; 509 } 510 } 511 512 // Next, check preloaded drawables. Preloaded drawables may contain 513 // unresolved theme attributes. 514 final Drawable.ConstantState cs; 515 if (isColorDrawable) { 516 cs = sPreloadedColorDrawables.get(key); 517 } else { 518 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); 519 } 520 521 Drawable dr; 522 if (cs != null) { 523 dr = cs.newDrawable(wrapper); 524 } else if (isColorDrawable) { 525 dr = new ColorDrawable(value.data); 526 } else { 527 dr = loadDrawableForCookie(wrapper, value, id, null); 528 } 529 530 // Determine if the drawable has unresolved theme attributes. If it 531 // does, we'll need to apply a theme and store it in a theme-specific 532 // cache. 533 final boolean canApplyTheme = dr != null && dr.canApplyTheme(); 534 if (canApplyTheme && theme != null) { 535 dr = dr.mutate(); 536 dr.applyTheme(theme); 537 dr.clearMutated(); 538 } 539 540 // If we were able to obtain a drawable, store it in the appropriate 541 // cache: preload, not themed, null theme, or theme-specific. Don't 542 // pollute the cache with drawables loaded from a foreign density. 543 if (dr != null && useCache) { 544 dr.setChangingConfigurations(value.changingConfigurations); 545 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); 546 } 547 548 return dr; 549 } catch (Exception e) { 550 String name; 551 try { 552 name = getResourceName(id); 553 } catch (NotFoundException e2) { 554 name = "(missing name)"; 555 } 556 557 // The target drawable might fail to load for any number of 558 // reasons, but we always want to include the resource name. 559 // Since the client already expects this method to throw a 560 // NotFoundException, just throw one of those. 561 final NotFoundException nfe = new NotFoundException("Drawable " + name 562 + " with resource ID #0x" + Integer.toHexString(id), e); 563 nfe.setStackTrace(new StackTraceElement[0]); 564 throw nfe; 565 } 566 } 567 568 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, 569 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) { 570 final Drawable.ConstantState cs = dr.getConstantState(); 571 if (cs == null) { 572 return; 573 } 574 575 if (mPreloading) { 576 final int changingConfigs = cs.getChangingConfigurations(); 577 if (isColorDrawable) { 578 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) { 579 sPreloadedColorDrawables.put(key, cs); 580 } 581 } else { 582 if (verifyPreloadConfig( 583 changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) { 584 if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) { 585 // If this resource does not vary based on layout direction, 586 // we can put it in all of the preload maps. 587 sPreloadedDrawables[0].put(key, cs); 588 sPreloadedDrawables[1].put(key, cs); 589 } else { 590 // Otherwise, only in the layout dir we loaded it for. 591 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs); 592 } 593 } 594 } 595 } else { 596 synchronized (mAccessLock) { 597 caches.put(key, theme, cs, usesTheme); 598 } 599 } 600 } 601 602 private boolean verifyPreloadConfig(@Config int changingConfigurations, 603 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) { 604 // We allow preloading of resources even if they vary by font scale (which 605 // doesn't impact resource selection) or density (which we handle specially by 606 // simply turning off all preloading), as well as any other configs specified 607 // by the caller. 608 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE | 609 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) { 610 String resName; 611 try { 612 resName = getResourceName(resourceId); 613 } catch (NotFoundException e) { 614 resName = "?"; 615 } 616 // This should never happen in production, so we should log a 617 // warning even if we're not debugging. 618 Log.w(TAG, "Preloaded " + name + " resource #0x" 619 + Integer.toHexString(resourceId) 620 + " (" + resName + ") that varies with configuration!!"); 621 return false; 622 } 623 if (TRACE_FOR_PRELOAD) { 624 String resName; 625 try { 626 resName = getResourceName(resourceId); 627 } catch (NotFoundException e) { 628 resName = "?"; 629 } 630 Log.w(TAG, "Preloading " + name + " resource #0x" 631 + Integer.toHexString(resourceId) 632 + " (" + resName + ")"); 633 } 634 return true; 635 } 636 637 /** 638 * Loads a drawable from XML or resources stream. 639 */ 640 private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, 641 Resources.Theme theme) { 642 if (value.string == null) { 643 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" 644 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); 645 } 646 647 final String file = value.string.toString(); 648 649 if (TRACE_FOR_MISS_PRELOAD) { 650 // Log only framework resources 651 if ((id >>> 24) == 0x1) { 652 final String name = getResourceName(id); 653 if (name != null) { 654 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id) 655 + ": " + name + " at " + file); 656 } 657 } 658 } 659 660 if (DEBUG_LOAD) { 661 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file); 662 } 663 664 final Drawable dr; 665 666 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 667 try { 668 if (file.endsWith(".xml")) { 669 final XmlResourceParser rp = loadXmlResourceParser( 670 file, id, value.assetCookie, "drawable"); 671 dr = Drawable.createFromXml(wrapper, rp, theme); 672 rp.close(); 673 } else { 674 final InputStream is = mAssets.openNonAsset( 675 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 676 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); 677 is.close(); 678 } 679 } catch (Exception e) { 680 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 681 final NotFoundException rnf = new NotFoundException( 682 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); 683 rnf.initCause(e); 684 throw rnf; 685 } 686 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 687 688 return dr; 689 } 690 691 /** 692 * Given the value and id, we can get the XML filename as in value.data, based on that, we 693 * first try to load CSL from the cache. If not found, try to get from the constant state. 694 * Last, parse the XML and generate the CSL. 695 */ 696 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme, 697 TypedValue value, int id) { 698 final long key = (((long) value.assetCookie) << 32) | value.data; 699 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache; 700 ComplexColor complexColor = cache.getInstance(key, wrapper, theme); 701 if (complexColor != null) { 702 return complexColor; 703 } 704 705 final android.content.res.ConstantState<ComplexColor> factory = 706 sPreloadedComplexColors.get(key); 707 708 if (factory != null) { 709 complexColor = factory.newInstance(wrapper, theme); 710 } 711 if (complexColor == null) { 712 complexColor = loadComplexColorForCookie(wrapper, value, id, theme); 713 } 714 715 if (complexColor != null) { 716 complexColor.setBaseChangingConfigurations(value.changingConfigurations); 717 718 if (mPreloading) { 719 if (verifyPreloadConfig(complexColor.getChangingConfigurations(), 720 0, value.resourceId, "color")) { 721 sPreloadedComplexColors.put(key, complexColor.getConstantState()); 722 } 723 } else { 724 cache.put(key, theme, complexColor.getConstantState()); 725 } 726 } 727 return complexColor; 728 } 729 730 @Nullable 731 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id, 732 Resources.Theme theme) { 733 if (TRACE_FOR_PRELOAD) { 734 // Log only framework resources 735 if ((id >>> 24) == 0x1) { 736 final String name = getResourceName(id); 737 if (name != null) android.util.Log.d("loadComplexColor", name); 738 } 739 } 740 741 final long key = (((long) value.assetCookie) << 32) | value.data; 742 743 // Handle inline color definitions. 744 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 745 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 746 return getColorStateListFromInt(value, key); 747 } 748 749 final String file = value.string.toString(); 750 751 ComplexColor complexColor; 752 if (file.endsWith(".xml")) { 753 try { 754 complexColor = loadComplexColorFromName(wrapper, theme, value, id); 755 } catch (Exception e) { 756 final NotFoundException rnf = new NotFoundException( 757 "File " + file + " from complex color resource ID #0x" 758 + Integer.toHexString(id)); 759 rnf.initCause(e); 760 throw rnf; 761 } 762 } else { 763 throw new NotFoundException( 764 "File " + file + " from drawable resource ID #0x" 765 + Integer.toHexString(id) + ": .xml extension required"); 766 } 767 768 return complexColor; 769 } 770 771 @Nullable 772 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, 773 Resources.Theme theme) 774 throws NotFoundException { 775 if (TRACE_FOR_PRELOAD) { 776 // Log only framework resources 777 if ((id >>> 24) == 0x1) { 778 final String name = getResourceName(id); 779 if (name != null) android.util.Log.d("PreloadColorStateList", name); 780 } 781 } 782 783 final long key = (((long) value.assetCookie) << 32) | value.data; 784 785 // Handle inline color definitions. 786 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 787 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 788 return getColorStateListFromInt(value, key); 789 } 790 791 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id); 792 if (complexColor != null && complexColor instanceof ColorStateList) { 793 return (ColorStateList) complexColor; 794 } 795 796 throw new NotFoundException( 797 "Can't find ColorStateList from drawable resource ID #0x" 798 + Integer.toHexString(id)); 799 } 800 801 @NonNull 802 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) { 803 ColorStateList csl; 804 final android.content.res.ConstantState<ComplexColor> factory = 805 sPreloadedComplexColors.get(key); 806 if (factory != null) { 807 return (ColorStateList) factory.newInstance(); 808 } 809 810 csl = ColorStateList.valueOf(value.data); 811 812 if (mPreloading) { 813 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 814 "color")) { 815 sPreloadedComplexColors.put(key, csl.getConstantState()); 816 } 817 } 818 819 return csl; 820 } 821 822 /** 823 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or 824 * ColorStateList. Note that pure color will be wrapped into a ColorStateList. 825 * 826 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient 827 * and selector tag. 828 * 829 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content. 830 */ 831 @Nullable 832 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, 833 Resources.Theme theme) { 834 if (value.string == null) { 835 throw new UnsupportedOperationException( 836 "Can't convert to ComplexColor: type=0x" + value.type); 837 } 838 839 final String file = value.string.toString(); 840 841 if (TRACE_FOR_MISS_PRELOAD) { 842 // Log only framework resources 843 if ((id >>> 24) == 0x1) { 844 final String name = getResourceName(id); 845 if (name != null) { 846 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id) 847 + ": " + name + " at " + file); 848 } 849 } 850 } 851 852 if (DEBUG_LOAD) { 853 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file); 854 } 855 856 ComplexColor complexColor = null; 857 858 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 859 if (file.endsWith(".xml")) { 860 try { 861 final XmlResourceParser parser = loadXmlResourceParser( 862 file, id, value.assetCookie, "ComplexColor"); 863 864 final AttributeSet attrs = Xml.asAttributeSet(parser); 865 int type; 866 while ((type = parser.next()) != XmlPullParser.START_TAG 867 && type != XmlPullParser.END_DOCUMENT) { 868 // Seek parser to start tag. 869 } 870 if (type != XmlPullParser.START_TAG) { 871 throw new XmlPullParserException("No start tag found"); 872 } 873 874 final String name = parser.getName(); 875 if (name.equals("gradient")) { 876 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme); 877 } else if (name.equals("selector")) { 878 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme); 879 } 880 parser.close(); 881 } catch (Exception e) { 882 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 883 final NotFoundException rnf = new NotFoundException( 884 "File " + file + " from ComplexColor resource ID #0x" 885 + Integer.toHexString(id)); 886 rnf.initCause(e); 887 throw rnf; 888 } 889 } else { 890 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 891 throw new NotFoundException( 892 "File " + file + " from drawable resource ID #0x" 893 + Integer.toHexString(id) + ": .xml extension required"); 894 } 895 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 896 897 return complexColor; 898 } 899 900 /** 901 * Loads an XML parser for the specified file. 902 * 903 * @param file the path for the XML file to parse 904 * @param id the resource identifier for the file 905 * @param assetCookie the asset cookie for the file 906 * @param type the type of resource (used for logging) 907 * @return a parser for the specified XML file 908 * @throws NotFoundException if the file could not be loaded 909 */ 910 @NonNull 911 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, 912 @NonNull String type) 913 throws NotFoundException { 914 if (id != 0) { 915 try { 916 synchronized (mCachedXmlBlocks) { 917 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; 918 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; 919 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; 920 // First see if this block is in our cache. 921 final int num = cachedXmlBlockFiles.length; 922 for (int i = 0; i < num; i++) { 923 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null 924 && cachedXmlBlockFiles[i].equals(file)) { 925 return cachedXmlBlocks[i].newParser(); 926 } 927 } 928 929 // Not in the cache, create a new block and put it at 930 // the next slot in the cache. 931 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); 932 if (block != null) { 933 final int pos = (mLastCachedXmlBlockIndex + 1) % num; 934 mLastCachedXmlBlockIndex = pos; 935 final XmlBlock oldBlock = cachedXmlBlocks[pos]; 936 if (oldBlock != null) { 937 oldBlock.close(); 938 } 939 cachedXmlBlockCookies[pos] = assetCookie; 940 cachedXmlBlockFiles[pos] = file; 941 cachedXmlBlocks[pos] = block; 942 return block.newParser(); 943 } 944 } 945 } catch (Exception e) { 946 final NotFoundException rnf = new NotFoundException("File " + file 947 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); 948 rnf.initCause(e); 949 throw rnf; 950 } 951 } 952 953 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" 954 + Integer.toHexString(id)); 955 } 956 957 /** 958 * Start preloading of resource data using this Resources object. Only 959 * for use by the zygote process for loading common system resources. 960 * {@hide} 961 */ 962 public final void startPreloading() { 963 synchronized (sSync) { 964 if (sPreloaded) { 965 throw new IllegalStateException("Resources already preloaded"); 966 } 967 sPreloaded = true; 968 mPreloading = true; 969 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE; 970 updateConfiguration(null, null, null); 971 } 972 } 973 974 /** 975 * Called by zygote when it is done preloading resources, to change back 976 * to normal Resources operation. 977 */ 978 void finishPreloading() { 979 if (mPreloading) { 980 mPreloading = false; 981 flushLayoutCache(); 982 } 983 } 984 985 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { 986 return sPreloadedDrawables[0]; 987 } 988 989 ThemeImpl newThemeImpl() { 990 return new ThemeImpl(); 991 } 992 993 /** 994 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey. 995 */ 996 ThemeImpl newThemeImpl(Resources.ThemeKey key) { 997 ThemeImpl impl = new ThemeImpl(); 998 impl.mKey.setTo(key); 999 impl.rebase(); 1000 return impl; 1001 } 1002 1003 public class ThemeImpl { 1004 /** 1005 * Unique key for the series of styles applied to this theme. 1006 */ 1007 private final Resources.ThemeKey mKey = new Resources.ThemeKey(); 1008 1009 @SuppressWarnings("hiding") 1010 private final AssetManager mAssets; 1011 private final long mTheme; 1012 1013 /** 1014 * Resource identifier for the theme. 1015 */ 1016 private int mThemeResId = 0; 1017 1018 /*package*/ ThemeImpl() { 1019 mAssets = ResourcesImpl.this.mAssets; 1020 mTheme = mAssets.createTheme(); 1021 } 1022 1023 @Override 1024 protected void finalize() throws Throwable { 1025 super.finalize(); 1026 mAssets.releaseTheme(mTheme); 1027 } 1028 1029 /*package*/ Resources.ThemeKey getKey() { 1030 return mKey; 1031 } 1032 1033 /*package*/ long getNativeTheme() { 1034 return mTheme; 1035 } 1036 1037 /*package*/ int getAppliedStyleResId() { 1038 return mThemeResId; 1039 } 1040 1041 void applyStyle(int resId, boolean force) { 1042 synchronized (mKey) { 1043 AssetManager.applyThemeStyle(mTheme, resId, force); 1044 1045 mThemeResId = resId; 1046 mKey.append(resId, force); 1047 } 1048 } 1049 1050 void setTo(ThemeImpl other) { 1051 synchronized (mKey) { 1052 synchronized (other.mKey) { 1053 AssetManager.copyTheme(mTheme, other.mTheme); 1054 1055 mThemeResId = other.mThemeResId; 1056 mKey.setTo(other.getKey()); 1057 } 1058 } 1059 } 1060 1061 @NonNull 1062 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper, 1063 AttributeSet set, 1064 @StyleableRes int[] attrs, 1065 @AttrRes int defStyleAttr, 1066 @StyleRes int defStyleRes) { 1067 synchronized (mKey) { 1068 final int len = attrs.length; 1069 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1070 1071 // XXX note that for now we only work with compiled XML files. 1072 // To support generic XML files we will need to manually parse 1073 // out the attributes from the XML file (applying type information 1074 // contained in the resources and such). 1075 final XmlBlock.Parser parser = (XmlBlock.Parser) set; 1076 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, 1077 parser != null ? parser.mParseState : 0, 1078 attrs, array.mData, array.mIndices); 1079 array.mTheme = wrapper; 1080 array.mXml = parser; 1081 1082 return array; 1083 } 1084 } 1085 1086 @NonNull 1087 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper, 1088 @NonNull int[] values, 1089 @NonNull int[] attrs) { 1090 synchronized (mKey) { 1091 final int len = attrs.length; 1092 if (values == null || len != values.length) { 1093 throw new IllegalArgumentException( 1094 "Base attribute values must the same length as attrs"); 1095 } 1096 1097 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1098 AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); 1099 array.mTheme = wrapper; 1100 array.mXml = null; 1101 return array; 1102 } 1103 } 1104 1105 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { 1106 synchronized (mKey) { 1107 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); 1108 } 1109 } 1110 1111 int[] getAllAttributes() { 1112 return mAssets.getStyleAttributes(getAppliedStyleResId()); 1113 } 1114 1115 @Config int getChangingConfigurations() { 1116 synchronized (mKey) { 1117 final int nativeChangingConfig = 1118 AssetManager.getThemeChangingConfigurations(mTheme); 1119 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); 1120 } 1121 } 1122 1123 public void dump(int priority, String tag, String prefix) { 1124 synchronized (mKey) { 1125 AssetManager.dumpTheme(mTheme, priority, tag, prefix); 1126 } 1127 } 1128 1129 String[] getTheme() { 1130 synchronized (mKey) { 1131 final int N = mKey.mCount; 1132 final String[] themes = new String[N * 2]; 1133 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { 1134 final int resId = mKey.mResId[j]; 1135 final boolean forced = mKey.mForce[j]; 1136 try { 1137 themes[i] = getResourceName(resId); 1138 } catch (NotFoundException e) { 1139 themes[i] = Integer.toHexString(i); 1140 } 1141 themes[i + 1] = forced ? "forced" : "not forced"; 1142 } 1143 return themes; 1144 } 1145 } 1146 1147 /** 1148 * Rebases the theme against the parent Resource object's current 1149 * configuration by re-applying the styles passed to 1150 * {@link #applyStyle(int, boolean)}. 1151 */ 1152 void rebase() { 1153 synchronized (mKey) { 1154 AssetManager.clearTheme(mTheme); 1155 1156 // Reapply the same styles in the same order. 1157 for (int i = 0; i < mKey.mCount; i++) { 1158 final int resId = mKey.mResId[i]; 1159 final boolean force = mKey.mForce[i]; 1160 AssetManager.applyThemeStyle(mTheme, resId, force); 1161 } 1162 } 1163 } 1164 } 1165} 1166