ResourcesImpl.java revision ac85f90466dd60d2af8ffc3942d503a0de606726
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 if (mPreloading) { 717 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 718 "color")) { 719 sPreloadedComplexColors.put(key, complexColor.getConstantState()); 720 } 721 } else { 722 cache.put(key, theme, complexColor.getConstantState()); 723 } 724 } 725 return complexColor; 726 } 727 728 @Nullable 729 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id, 730 Resources.Theme theme) { 731 if (TRACE_FOR_PRELOAD) { 732 // Log only framework resources 733 if ((id >>> 24) == 0x1) { 734 final String name = getResourceName(id); 735 if (name != null) android.util.Log.d("loadComplexColor", name); 736 } 737 } 738 739 final long key = (((long) value.assetCookie) << 32) | value.data; 740 741 // Handle inline color definitions. 742 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 743 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 744 return getColorStateListFromInt(value, key); 745 } 746 747 final String file = value.string.toString(); 748 749 ComplexColor complexColor; 750 if (file.endsWith(".xml")) { 751 try { 752 complexColor = loadComplexColorFromName(wrapper, theme, value, id); 753 } catch (Exception e) { 754 final NotFoundException rnf = new NotFoundException( 755 "File " + file + " from complex color resource ID #0x" 756 + Integer.toHexString(id)); 757 rnf.initCause(e); 758 throw rnf; 759 } 760 } else { 761 throw new NotFoundException( 762 "File " + file + " from drawable resource ID #0x" 763 + Integer.toHexString(id) + ": .xml extension required"); 764 } 765 766 return complexColor; 767 } 768 769 @Nullable 770 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, 771 Resources.Theme theme) 772 throws NotFoundException { 773 if (TRACE_FOR_PRELOAD) { 774 // Log only framework resources 775 if ((id >>> 24) == 0x1) { 776 final String name = getResourceName(id); 777 if (name != null) android.util.Log.d("PreloadColorStateList", name); 778 } 779 } 780 781 final long key = (((long) value.assetCookie) << 32) | value.data; 782 783 // Handle inline color definitions. 784 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 785 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 786 return getColorStateListFromInt(value, key); 787 } 788 789 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id); 790 if (complexColor != null && complexColor instanceof ColorStateList) { 791 return (ColorStateList) complexColor; 792 } 793 794 throw new NotFoundException( 795 "Can't find ColorStateList from drawable resource ID #0x" 796 + Integer.toHexString(id)); 797 } 798 799 @NonNull 800 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) { 801 ColorStateList csl; 802 final android.content.res.ConstantState<ComplexColor> factory = 803 sPreloadedComplexColors.get(key); 804 if (factory != null) { 805 return (ColorStateList) factory.newInstance(); 806 } 807 808 csl = ColorStateList.valueOf(value.data); 809 810 if (mPreloading) { 811 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 812 "color")) { 813 sPreloadedComplexColors.put(key, csl.getConstantState()); 814 } 815 } 816 817 return csl; 818 } 819 820 /** 821 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or 822 * ColorStateList. Note that pure color will be wrapped into a ColorStateList. 823 * 824 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient 825 * and selector tag. 826 * 827 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content. 828 */ 829 @Nullable 830 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, 831 Resources.Theme theme) { 832 if (value.string == null) { 833 throw new UnsupportedOperationException( 834 "Can't convert to ComplexColor: type=0x" + value.type); 835 } 836 837 final String file = value.string.toString(); 838 839 if (TRACE_FOR_MISS_PRELOAD) { 840 // Log only framework resources 841 if ((id >>> 24) == 0x1) { 842 final String name = getResourceName(id); 843 if (name != null) { 844 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id) 845 + ": " + name + " at " + file); 846 } 847 } 848 } 849 850 if (DEBUG_LOAD) { 851 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file); 852 } 853 854 ComplexColor complexColor = null; 855 856 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 857 if (file.endsWith(".xml")) { 858 try { 859 final XmlResourceParser parser = loadXmlResourceParser( 860 file, id, value.assetCookie, "ComplexColor"); 861 862 final AttributeSet attrs = Xml.asAttributeSet(parser); 863 int type; 864 while ((type = parser.next()) != XmlPullParser.START_TAG 865 && type != XmlPullParser.END_DOCUMENT) { 866 // Seek parser to start tag. 867 } 868 if (type != XmlPullParser.START_TAG) { 869 throw new XmlPullParserException("No start tag found"); 870 } 871 872 final String name = parser.getName(); 873 if (name.equals("gradient")) { 874 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme); 875 } else if (name.equals("selector")) { 876 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme); 877 } 878 parser.close(); 879 } catch (Exception e) { 880 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 881 final NotFoundException rnf = new NotFoundException( 882 "File " + file + " from ComplexColor resource ID #0x" 883 + Integer.toHexString(id)); 884 rnf.initCause(e); 885 throw rnf; 886 } 887 } else { 888 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 889 throw new NotFoundException( 890 "File " + file + " from drawable resource ID #0x" 891 + Integer.toHexString(id) + ": .xml extension required"); 892 } 893 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 894 895 return complexColor; 896 } 897 898 /** 899 * Loads an XML parser for the specified file. 900 * 901 * @param file the path for the XML file to parse 902 * @param id the resource identifier for the file 903 * @param assetCookie the asset cookie for the file 904 * @param type the type of resource (used for logging) 905 * @return a parser for the specified XML file 906 * @throws NotFoundException if the file could not be loaded 907 */ 908 @NonNull 909 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, 910 @NonNull String type) 911 throws NotFoundException { 912 if (id != 0) { 913 try { 914 synchronized (mCachedXmlBlocks) { 915 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; 916 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; 917 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; 918 // First see if this block is in our cache. 919 final int num = cachedXmlBlockFiles.length; 920 for (int i = 0; i < num; i++) { 921 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null 922 && cachedXmlBlockFiles[i].equals(file)) { 923 return cachedXmlBlocks[i].newParser(); 924 } 925 } 926 927 // Not in the cache, create a new block and put it at 928 // the next slot in the cache. 929 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); 930 if (block != null) { 931 final int pos = (mLastCachedXmlBlockIndex + 1) % num; 932 mLastCachedXmlBlockIndex = pos; 933 final XmlBlock oldBlock = cachedXmlBlocks[pos]; 934 if (oldBlock != null) { 935 oldBlock.close(); 936 } 937 cachedXmlBlockCookies[pos] = assetCookie; 938 cachedXmlBlockFiles[pos] = file; 939 cachedXmlBlocks[pos] = block; 940 return block.newParser(); 941 } 942 } 943 } catch (Exception e) { 944 final NotFoundException rnf = new NotFoundException("File " + file 945 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); 946 rnf.initCause(e); 947 throw rnf; 948 } 949 } 950 951 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" 952 + Integer.toHexString(id)); 953 } 954 955 /** 956 * Start preloading of resource data using this Resources object. Only 957 * for use by the zygote process for loading common system resources. 958 * {@hide} 959 */ 960 public final void startPreloading() { 961 synchronized (sSync) { 962 if (sPreloaded) { 963 throw new IllegalStateException("Resources already preloaded"); 964 } 965 sPreloaded = true; 966 mPreloading = true; 967 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE; 968 updateConfiguration(null, null, null); 969 } 970 } 971 972 /** 973 * Called by zygote when it is done preloading resources, to change back 974 * to normal Resources operation. 975 */ 976 void finishPreloading() { 977 if (mPreloading) { 978 mPreloading = false; 979 flushLayoutCache(); 980 } 981 } 982 983 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { 984 return sPreloadedDrawables[0]; 985 } 986 987 ThemeImpl newThemeImpl() { 988 return new ThemeImpl(); 989 } 990 991 /** 992 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey. 993 */ 994 ThemeImpl newThemeImpl(Resources.ThemeKey key) { 995 ThemeImpl impl = new ThemeImpl(); 996 impl.mKey.setTo(key); 997 impl.rebase(); 998 return impl; 999 } 1000 1001 public class ThemeImpl { 1002 /** 1003 * Unique key for the series of styles applied to this theme. 1004 */ 1005 private final Resources.ThemeKey mKey = new Resources.ThemeKey(); 1006 1007 @SuppressWarnings("hiding") 1008 private final AssetManager mAssets; 1009 private final long mTheme; 1010 1011 /** 1012 * Resource identifier for the theme. 1013 */ 1014 private int mThemeResId = 0; 1015 1016 /*package*/ ThemeImpl() { 1017 mAssets = ResourcesImpl.this.mAssets; 1018 mTheme = mAssets.createTheme(); 1019 } 1020 1021 @Override 1022 protected void finalize() throws Throwable { 1023 super.finalize(); 1024 mAssets.releaseTheme(mTheme); 1025 } 1026 1027 /*package*/ Resources.ThemeKey getKey() { 1028 return mKey; 1029 } 1030 1031 /*package*/ long getNativeTheme() { 1032 return mTheme; 1033 } 1034 1035 /*package*/ int getAppliedStyleResId() { 1036 return mThemeResId; 1037 } 1038 1039 void applyStyle(int resId, boolean force) { 1040 synchronized (mKey) { 1041 AssetManager.applyThemeStyle(mTheme, resId, force); 1042 1043 mThemeResId = resId; 1044 mKey.append(resId, force); 1045 } 1046 } 1047 1048 void setTo(ThemeImpl other) { 1049 synchronized (mKey) { 1050 synchronized (other.mKey) { 1051 AssetManager.copyTheme(mTheme, other.mTheme); 1052 1053 mThemeResId = other.mThemeResId; 1054 mKey.setTo(other.getKey()); 1055 } 1056 } 1057 } 1058 1059 @NonNull 1060 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper, 1061 AttributeSet set, 1062 @StyleableRes int[] attrs, 1063 @AttrRes int defStyleAttr, 1064 @StyleRes int defStyleRes) { 1065 synchronized (mKey) { 1066 final int len = attrs.length; 1067 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1068 1069 // XXX note that for now we only work with compiled XML files. 1070 // To support generic XML files we will need to manually parse 1071 // out the attributes from the XML file (applying type information 1072 // contained in the resources and such). 1073 final XmlBlock.Parser parser = (XmlBlock.Parser) set; 1074 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, 1075 parser != null ? parser.mParseState : 0, 1076 attrs, array.mData, array.mIndices); 1077 array.mTheme = wrapper; 1078 array.mXml = parser; 1079 1080 return array; 1081 } 1082 } 1083 1084 @NonNull 1085 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper, 1086 @NonNull int[] values, 1087 @NonNull int[] attrs) { 1088 synchronized (mKey) { 1089 final int len = attrs.length; 1090 if (values == null || len != values.length) { 1091 throw new IllegalArgumentException( 1092 "Base attribute values must the same length as attrs"); 1093 } 1094 1095 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1096 AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); 1097 array.mTheme = wrapper; 1098 array.mXml = null; 1099 return array; 1100 } 1101 } 1102 1103 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { 1104 synchronized (mKey) { 1105 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); 1106 } 1107 } 1108 1109 int[] getAllAttributes() { 1110 return mAssets.getStyleAttributes(getAppliedStyleResId()); 1111 } 1112 1113 @Config int getChangingConfigurations() { 1114 synchronized (mKey) { 1115 final int nativeChangingConfig = 1116 AssetManager.getThemeChangingConfigurations(mTheme); 1117 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); 1118 } 1119 } 1120 1121 public void dump(int priority, String tag, String prefix) { 1122 synchronized (mKey) { 1123 AssetManager.dumpTheme(mTheme, priority, tag, prefix); 1124 } 1125 } 1126 1127 String[] getTheme() { 1128 synchronized (mKey) { 1129 final int N = mKey.mCount; 1130 final String[] themes = new String[N * 2]; 1131 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { 1132 final int resId = mKey.mResId[j]; 1133 final boolean forced = mKey.mForce[j]; 1134 try { 1135 themes[i] = getResourceName(resId); 1136 } catch (NotFoundException e) { 1137 themes[i] = Integer.toHexString(i); 1138 } 1139 themes[i + 1] = forced ? "forced" : "not forced"; 1140 } 1141 return themes; 1142 } 1143 } 1144 1145 /** 1146 * Rebases the theme against the parent Resource object's current 1147 * configuration by re-applying the styles passed to 1148 * {@link #applyStyle(int, boolean)}. 1149 */ 1150 void rebase() { 1151 synchronized (mKey) { 1152 AssetManager.clearTheme(mTheme); 1153 1154 // Reapply the same styles in the same order. 1155 for (int i = 0; i < mKey.mCount; i++) { 1156 final int resId = mKey.mResId[i]; 1157 final boolean force = mKey.mForce[i]; 1158 AssetManager.applyThemeStyle(mTheme, resId, force); 1159 } 1160 } 1161 } 1162 } 1163} 1164