ResourcesImpl.java revision 991357fe25b3addabf85b871df3f4098fc4b833b
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 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration"); 313 try { 314 synchronized (mAccessLock) { 315 if (false) { 316 Slog.i(TAG, "**** Updating config of " + this + ": old config is " 317 + mConfiguration + " old compat is " + mCompatibilityInfo); 318 Slog.i(TAG, "**** Updating config of " + this + ": new config is " 319 + config + " new compat is " + compat); 320 } 321 if (compat != null) { 322 mCompatibilityInfo = compat; 323 } 324 if (metrics != null) { 325 mMetrics.setTo(metrics); 326 } 327 // NOTE: We should re-arrange this code to create a Display 328 // with the CompatibilityInfo that is used everywhere we deal 329 // with the display in relation to this app, rather than 330 // doing the conversion here. This impl should be okay because 331 // we make sure to return a compatible display in the places 332 // where there are public APIs to retrieve the display... but 333 // it would be cleaner and more maintainble to just be 334 // consistently dealing with a compatible display everywhere in 335 // the framework. 336 mCompatibilityInfo.applyToDisplayMetrics(mMetrics); 337 338 final @Config int configChanges = calcConfigChanges(config); 339 340 LocaleList locales = mConfiguration.getLocales(); 341 if (locales.isEmpty()) { 342 locales = LocaleList.getAdjustedDefault(); 343 mConfiguration.setLocales(locales); 344 } 345 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { 346 mMetrics.densityDpi = mConfiguration.densityDpi; 347 mMetrics.density = 348 mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 349 } 350 mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; 351 352 final int width, height; 353 if (mMetrics.widthPixels >= mMetrics.heightPixels) { 354 width = mMetrics.widthPixels; 355 height = mMetrics.heightPixels; 356 } else { 357 //noinspection SuspiciousNameCombination 358 width = mMetrics.heightPixels; 359 //noinspection SuspiciousNameCombination 360 height = mMetrics.widthPixels; 361 } 362 363 final int keyboardHidden; 364 if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO 365 && mConfiguration.hardKeyboardHidden 366 == Configuration.HARDKEYBOARDHIDDEN_YES) { 367 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; 368 } else { 369 keyboardHidden = mConfiguration.keyboardHidden; 370 } 371 372 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, 373 adjustLanguageTag(locales.get(0).toLanguageTag()), 374 mConfiguration.orientation, 375 mConfiguration.touchscreen, 376 mConfiguration.densityDpi, mConfiguration.keyboard, 377 keyboardHidden, mConfiguration.navigation, width, height, 378 mConfiguration.smallestScreenWidthDp, 379 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, 380 mConfiguration.screenLayout, mConfiguration.uiMode, 381 Build.VERSION.RESOURCES_SDK_INT); 382 383 if (DEBUG_CONFIG) { 384 Slog.i(TAG, "**** Updating config of " + this + ": final config is " 385 + mConfiguration + " final compat is " + mCompatibilityInfo); 386 } 387 388 mDrawableCache.onConfigurationChange(configChanges); 389 mColorDrawableCache.onConfigurationChange(configChanges); 390 mComplexColorCache.onConfigurationChange(configChanges); 391 mAnimatorCache.onConfigurationChange(configChanges); 392 mStateListAnimatorCache.onConfigurationChange(configChanges); 393 394 flushLayoutCache(); 395 } 396 synchronized (sSync) { 397 if (mPluralRule != null) { 398 mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0)); 399 } 400 } 401 } finally { 402 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 403 } 404 } 405 406 /** 407 * Applies the new configuration, returning a bitmask of the changes 408 * between the old and new configurations. 409 * 410 * @param config the new configuration 411 * @return bitmask of config changes 412 */ 413 public @Config int calcConfigChanges(@Nullable Configuration config) { 414 if (config == null) { 415 // If there is no configuration, assume all flags have changed. 416 return 0xFFFFFFFF; 417 } 418 419 mTmpConfig.setTo(config); 420 int density = config.densityDpi; 421 if (density == Configuration.DENSITY_DPI_UNDEFINED) { 422 density = mMetrics.noncompatDensityDpi; 423 } 424 425 mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); 426 427 if (mTmpConfig.getLocales().isEmpty()) { 428 mTmpConfig.setLocales(LocaleList.getDefault()); 429 } 430 return mConfiguration.updateFrom(mTmpConfig); 431 } 432 433 /** 434 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) 435 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. 436 * 437 * All released versions of android prior to "L" used the deprecated language 438 * tags, so we will need to support them for backwards compatibility. 439 * 440 * Note that this conversion needs to take place *after* the call to 441 * {@code toLanguageTag} because that will convert all the deprecated codes to 442 * the new ones, even if they're set manually. 443 */ 444 private static String adjustLanguageTag(String languageTag) { 445 final int separator = languageTag.indexOf('-'); 446 final String language; 447 final String remainder; 448 449 if (separator == -1) { 450 language = languageTag; 451 remainder = ""; 452 } else { 453 language = languageTag.substring(0, separator); 454 remainder = languageTag.substring(separator); 455 } 456 457 return Locale.adjustLanguageCode(language) + remainder; 458 } 459 460 /** 461 * Call this to remove all cached loaded layout resources from the 462 * Resources object. Only intended for use with performance testing 463 * tools. 464 */ 465 public void flushLayoutCache() { 466 synchronized (mCachedXmlBlocks) { 467 Arrays.fill(mCachedXmlBlockCookies, 0); 468 Arrays.fill(mCachedXmlBlockFiles, null); 469 470 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; 471 for (int i = 0; i < XML_BLOCK_CACHE_SIZE; i++) { 472 final XmlBlock oldBlock = cachedXmlBlocks[i]; 473 if (oldBlock != null) { 474 oldBlock.close(); 475 } 476 } 477 Arrays.fill(cachedXmlBlocks, null); 478 } 479 } 480 481 @Nullable 482 Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, 483 boolean useCache) throws NotFoundException { 484 try { 485 if (TRACE_FOR_PRELOAD) { 486 // Log only framework resources 487 if ((id >>> 24) == 0x1) { 488 final String name = getResourceName(id); 489 if (name != null) { 490 Log.d("PreloadDrawable", name); 491 } 492 } 493 } 494 495 final boolean isColorDrawable; 496 final DrawableCache caches; 497 final long key; 498 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 499 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 500 isColorDrawable = true; 501 caches = mColorDrawableCache; 502 key = value.data; 503 } else { 504 isColorDrawable = false; 505 caches = mDrawableCache; 506 key = (((long) value.assetCookie) << 32) | value.data; 507 } 508 509 // First, check whether we have a cached version of this drawable 510 // that was inflated against the specified theme. Skip the cache if 511 // we're currently preloading or we're not using the cache. 512 if (!mPreloading && useCache) { 513 final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme); 514 if (cachedDrawable != null) { 515 return cachedDrawable; 516 } 517 } 518 519 // Next, check preloaded drawables. Preloaded drawables may contain 520 // unresolved theme attributes. 521 final Drawable.ConstantState cs; 522 if (isColorDrawable) { 523 cs = sPreloadedColorDrawables.get(key); 524 } else { 525 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); 526 } 527 528 Drawable dr; 529 if (cs != null) { 530 dr = cs.newDrawable(wrapper); 531 } else if (isColorDrawable) { 532 dr = new ColorDrawable(value.data); 533 } else { 534 dr = loadDrawableForCookie(wrapper, value, id, null); 535 } 536 537 // Determine if the drawable has unresolved theme attributes. If it 538 // does, we'll need to apply a theme and store it in a theme-specific 539 // cache. 540 final boolean canApplyTheme = dr != null && dr.canApplyTheme(); 541 if (canApplyTheme && theme != null) { 542 dr = dr.mutate(); 543 dr.applyTheme(theme); 544 dr.clearMutated(); 545 } 546 547 // If we were able to obtain a drawable, store it in the appropriate 548 // cache: preload, not themed, null theme, or theme-specific. Don't 549 // pollute the cache with drawables loaded from a foreign density. 550 if (dr != null && useCache) { 551 dr.setChangingConfigurations(value.changingConfigurations); 552 cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); 553 } 554 555 return dr; 556 } catch (Exception e) { 557 String name; 558 try { 559 name = getResourceName(id); 560 } catch (NotFoundException e2) { 561 name = "(missing name)"; 562 } 563 564 // The target drawable might fail to load for any number of 565 // reasons, but we always want to include the resource name. 566 // Since the client already expects this method to throw a 567 // NotFoundException, just throw one of those. 568 final NotFoundException nfe = new NotFoundException("Drawable " + name 569 + " with resource ID #0x" + Integer.toHexString(id), e); 570 nfe.setStackTrace(new StackTraceElement[0]); 571 throw nfe; 572 } 573 } 574 575 private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, 576 Resources.Theme theme, boolean usesTheme, long key, Drawable dr) { 577 final Drawable.ConstantState cs = dr.getConstantState(); 578 if (cs == null) { 579 return; 580 } 581 582 if (mPreloading) { 583 final int changingConfigs = cs.getChangingConfigurations(); 584 if (isColorDrawable) { 585 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) { 586 sPreloadedColorDrawables.put(key, cs); 587 } 588 } else { 589 if (verifyPreloadConfig( 590 changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) { 591 if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) { 592 // If this resource does not vary based on layout direction, 593 // we can put it in all of the preload maps. 594 sPreloadedDrawables[0].put(key, cs); 595 sPreloadedDrawables[1].put(key, cs); 596 } else { 597 // Otherwise, only in the layout dir we loaded it for. 598 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs); 599 } 600 } 601 } 602 } else { 603 synchronized (mAccessLock) { 604 caches.put(key, theme, cs, usesTheme); 605 } 606 } 607 } 608 609 private boolean verifyPreloadConfig(@Config int changingConfigurations, 610 @Config int allowVarying, @AnyRes int resourceId, @Nullable String name) { 611 // We allow preloading of resources even if they vary by font scale (which 612 // doesn't impact resource selection) or density (which we handle specially by 613 // simply turning off all preloading), as well as any other configs specified 614 // by the caller. 615 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE | 616 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) { 617 String resName; 618 try { 619 resName = getResourceName(resourceId); 620 } catch (NotFoundException e) { 621 resName = "?"; 622 } 623 // This should never happen in production, so we should log a 624 // warning even if we're not debugging. 625 Log.w(TAG, "Preloaded " + name + " resource #0x" 626 + Integer.toHexString(resourceId) 627 + " (" + resName + ") that varies with configuration!!"); 628 return false; 629 } 630 if (TRACE_FOR_PRELOAD) { 631 String resName; 632 try { 633 resName = getResourceName(resourceId); 634 } catch (NotFoundException e) { 635 resName = "?"; 636 } 637 Log.w(TAG, "Preloading " + name + " resource #0x" 638 + Integer.toHexString(resourceId) 639 + " (" + resName + ")"); 640 } 641 return true; 642 } 643 644 /** 645 * Loads a drawable from XML or resources stream. 646 */ 647 private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, 648 Resources.Theme theme) { 649 if (value.string == null) { 650 throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" 651 + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); 652 } 653 654 final String file = value.string.toString(); 655 656 if (TRACE_FOR_MISS_PRELOAD) { 657 // Log only framework resources 658 if ((id >>> 24) == 0x1) { 659 final String name = getResourceName(id); 660 if (name != null) { 661 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id) 662 + ": " + name + " at " + file); 663 } 664 } 665 } 666 667 if (DEBUG_LOAD) { 668 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file); 669 } 670 671 final Drawable dr; 672 673 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 674 try { 675 if (file.endsWith(".xml")) { 676 final XmlResourceParser rp = loadXmlResourceParser( 677 file, id, value.assetCookie, "drawable"); 678 dr = Drawable.createFromXml(wrapper, rp, theme); 679 rp.close(); 680 } else { 681 final InputStream is = mAssets.openNonAsset( 682 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 683 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); 684 is.close(); 685 } 686 } catch (Exception e) { 687 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 688 final NotFoundException rnf = new NotFoundException( 689 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); 690 rnf.initCause(e); 691 throw rnf; 692 } 693 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 694 695 return dr; 696 } 697 698 /** 699 * Given the value and id, we can get the XML filename as in value.data, based on that, we 700 * first try to load CSL from the cache. If not found, try to get from the constant state. 701 * Last, parse the XML and generate the CSL. 702 */ 703 private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme, 704 TypedValue value, int id) { 705 final long key = (((long) value.assetCookie) << 32) | value.data; 706 final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache; 707 ComplexColor complexColor = cache.getInstance(key, wrapper, theme); 708 if (complexColor != null) { 709 return complexColor; 710 } 711 712 final android.content.res.ConstantState<ComplexColor> factory = 713 sPreloadedComplexColors.get(key); 714 715 if (factory != null) { 716 complexColor = factory.newInstance(wrapper, theme); 717 } 718 if (complexColor == null) { 719 complexColor = loadComplexColorForCookie(wrapper, value, id, theme); 720 } 721 722 if (complexColor != null) { 723 complexColor.setBaseChangingConfigurations(value.changingConfigurations); 724 725 if (mPreloading) { 726 if (verifyPreloadConfig(complexColor.getChangingConfigurations(), 727 0, value.resourceId, "color")) { 728 sPreloadedComplexColors.put(key, complexColor.getConstantState()); 729 } 730 } else { 731 cache.put(key, theme, complexColor.getConstantState()); 732 } 733 } 734 return complexColor; 735 } 736 737 @Nullable 738 ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id, 739 Resources.Theme theme) { 740 if (TRACE_FOR_PRELOAD) { 741 // Log only framework resources 742 if ((id >>> 24) == 0x1) { 743 final String name = getResourceName(id); 744 if (name != null) android.util.Log.d("loadComplexColor", name); 745 } 746 } 747 748 final long key = (((long) value.assetCookie) << 32) | value.data; 749 750 // Handle inline color definitions. 751 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 752 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 753 return getColorStateListFromInt(value, key); 754 } 755 756 final String file = value.string.toString(); 757 758 ComplexColor complexColor; 759 if (file.endsWith(".xml")) { 760 try { 761 complexColor = loadComplexColorFromName(wrapper, theme, value, id); 762 } catch (Exception e) { 763 final NotFoundException rnf = new NotFoundException( 764 "File " + file + " from complex color resource ID #0x" 765 + Integer.toHexString(id)); 766 rnf.initCause(e); 767 throw rnf; 768 } 769 } else { 770 throw new NotFoundException( 771 "File " + file + " from drawable resource ID #0x" 772 + Integer.toHexString(id) + ": .xml extension required"); 773 } 774 775 return complexColor; 776 } 777 778 @Nullable 779 ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, 780 Resources.Theme theme) 781 throws NotFoundException { 782 if (TRACE_FOR_PRELOAD) { 783 // Log only framework resources 784 if ((id >>> 24) == 0x1) { 785 final String name = getResourceName(id); 786 if (name != null) android.util.Log.d("PreloadColorStateList", name); 787 } 788 } 789 790 final long key = (((long) value.assetCookie) << 32) | value.data; 791 792 // Handle inline color definitions. 793 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 794 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 795 return getColorStateListFromInt(value, key); 796 } 797 798 ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id); 799 if (complexColor != null && complexColor instanceof ColorStateList) { 800 return (ColorStateList) complexColor; 801 } 802 803 throw new NotFoundException( 804 "Can't find ColorStateList from drawable resource ID #0x" 805 + Integer.toHexString(id)); 806 } 807 808 @NonNull 809 private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) { 810 ColorStateList csl; 811 final android.content.res.ConstantState<ComplexColor> factory = 812 sPreloadedComplexColors.get(key); 813 if (factory != null) { 814 return (ColorStateList) factory.newInstance(); 815 } 816 817 csl = ColorStateList.valueOf(value.data); 818 819 if (mPreloading) { 820 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 821 "color")) { 822 sPreloadedComplexColors.put(key, csl.getConstantState()); 823 } 824 } 825 826 return csl; 827 } 828 829 /** 830 * Load a ComplexColor based on the XML file content. The result can be a GradientColor or 831 * ColorStateList. Note that pure color will be wrapped into a ColorStateList. 832 * 833 * We deferred the parser creation to this function b/c we need to differentiate b/t gradient 834 * and selector tag. 835 * 836 * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content. 837 */ 838 @Nullable 839 private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, 840 Resources.Theme theme) { 841 if (value.string == null) { 842 throw new UnsupportedOperationException( 843 "Can't convert to ComplexColor: type=0x" + value.type); 844 } 845 846 final String file = value.string.toString(); 847 848 if (TRACE_FOR_MISS_PRELOAD) { 849 // Log only framework resources 850 if ((id >>> 24) == 0x1) { 851 final String name = getResourceName(id); 852 if (name != null) { 853 Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id) 854 + ": " + name + " at " + file); 855 } 856 } 857 } 858 859 if (DEBUG_LOAD) { 860 Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file); 861 } 862 863 ComplexColor complexColor = null; 864 865 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 866 if (file.endsWith(".xml")) { 867 try { 868 final XmlResourceParser parser = loadXmlResourceParser( 869 file, id, value.assetCookie, "ComplexColor"); 870 871 final AttributeSet attrs = Xml.asAttributeSet(parser); 872 int type; 873 while ((type = parser.next()) != XmlPullParser.START_TAG 874 && type != XmlPullParser.END_DOCUMENT) { 875 // Seek parser to start tag. 876 } 877 if (type != XmlPullParser.START_TAG) { 878 throw new XmlPullParserException("No start tag found"); 879 } 880 881 final String name = parser.getName(); 882 if (name.equals("gradient")) { 883 complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme); 884 } else if (name.equals("selector")) { 885 complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme); 886 } 887 parser.close(); 888 } catch (Exception e) { 889 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 890 final NotFoundException rnf = new NotFoundException( 891 "File " + file + " from ComplexColor resource ID #0x" 892 + Integer.toHexString(id)); 893 rnf.initCause(e); 894 throw rnf; 895 } 896 } else { 897 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 898 throw new NotFoundException( 899 "File " + file + " from drawable resource ID #0x" 900 + Integer.toHexString(id) + ": .xml extension required"); 901 } 902 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 903 904 return complexColor; 905 } 906 907 /** 908 * Loads an XML parser for the specified file. 909 * 910 * @param file the path for the XML file to parse 911 * @param id the resource identifier for the file 912 * @param assetCookie the asset cookie for the file 913 * @param type the type of resource (used for logging) 914 * @return a parser for the specified XML file 915 * @throws NotFoundException if the file could not be loaded 916 */ 917 @NonNull 918 XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, 919 @NonNull String type) 920 throws NotFoundException { 921 if (id != 0) { 922 try { 923 synchronized (mCachedXmlBlocks) { 924 final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; 925 final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; 926 final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; 927 // First see if this block is in our cache. 928 final int num = cachedXmlBlockFiles.length; 929 for (int i = 0; i < num; i++) { 930 if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null 931 && cachedXmlBlockFiles[i].equals(file)) { 932 return cachedXmlBlocks[i].newParser(); 933 } 934 } 935 936 // Not in the cache, create a new block and put it at 937 // the next slot in the cache. 938 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); 939 if (block != null) { 940 final int pos = (mLastCachedXmlBlockIndex + 1) % num; 941 mLastCachedXmlBlockIndex = pos; 942 final XmlBlock oldBlock = cachedXmlBlocks[pos]; 943 if (oldBlock != null) { 944 oldBlock.close(); 945 } 946 cachedXmlBlockCookies[pos] = assetCookie; 947 cachedXmlBlockFiles[pos] = file; 948 cachedXmlBlocks[pos] = block; 949 return block.newParser(); 950 } 951 } 952 } catch (Exception e) { 953 final NotFoundException rnf = new NotFoundException("File " + file 954 + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id)); 955 rnf.initCause(e); 956 throw rnf; 957 } 958 } 959 960 throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x" 961 + Integer.toHexString(id)); 962 } 963 964 /** 965 * Start preloading of resource data using this Resources object. Only 966 * for use by the zygote process for loading common system resources. 967 * {@hide} 968 */ 969 public final void startPreloading() { 970 synchronized (sSync) { 971 if (sPreloaded) { 972 throw new IllegalStateException("Resources already preloaded"); 973 } 974 sPreloaded = true; 975 mPreloading = true; 976 mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE; 977 updateConfiguration(null, null, null); 978 } 979 } 980 981 /** 982 * Called by zygote when it is done preloading resources, to change back 983 * to normal Resources operation. 984 */ 985 void finishPreloading() { 986 if (mPreloading) { 987 mPreloading = false; 988 flushLayoutCache(); 989 } 990 } 991 992 LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { 993 return sPreloadedDrawables[0]; 994 } 995 996 ThemeImpl newThemeImpl() { 997 return new ThemeImpl(); 998 } 999 1000 /** 1001 * Creates a new ThemeImpl which is already set to the given Resources.ThemeKey. 1002 */ 1003 ThemeImpl newThemeImpl(Resources.ThemeKey key) { 1004 ThemeImpl impl = new ThemeImpl(); 1005 impl.mKey.setTo(key); 1006 impl.rebase(); 1007 return impl; 1008 } 1009 1010 public class ThemeImpl { 1011 /** 1012 * Unique key for the series of styles applied to this theme. 1013 */ 1014 private final Resources.ThemeKey mKey = new Resources.ThemeKey(); 1015 1016 @SuppressWarnings("hiding") 1017 private final AssetManager mAssets; 1018 private final long mTheme; 1019 1020 /** 1021 * Resource identifier for the theme. 1022 */ 1023 private int mThemeResId = 0; 1024 1025 /*package*/ ThemeImpl() { 1026 mAssets = ResourcesImpl.this.mAssets; 1027 mTheme = mAssets.createTheme(); 1028 } 1029 1030 @Override 1031 protected void finalize() throws Throwable { 1032 super.finalize(); 1033 mAssets.releaseTheme(mTheme); 1034 } 1035 1036 /*package*/ Resources.ThemeKey getKey() { 1037 return mKey; 1038 } 1039 1040 /*package*/ long getNativeTheme() { 1041 return mTheme; 1042 } 1043 1044 /*package*/ int getAppliedStyleResId() { 1045 return mThemeResId; 1046 } 1047 1048 void applyStyle(int resId, boolean force) { 1049 synchronized (mKey) { 1050 AssetManager.applyThemeStyle(mTheme, resId, force); 1051 1052 mThemeResId = resId; 1053 mKey.append(resId, force); 1054 } 1055 } 1056 1057 void setTo(ThemeImpl other) { 1058 synchronized (mKey) { 1059 synchronized (other.mKey) { 1060 AssetManager.copyTheme(mTheme, other.mTheme); 1061 1062 mThemeResId = other.mThemeResId; 1063 mKey.setTo(other.getKey()); 1064 } 1065 } 1066 } 1067 1068 @NonNull 1069 TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper, 1070 AttributeSet set, 1071 @StyleableRes int[] attrs, 1072 @AttrRes int defStyleAttr, 1073 @StyleRes int defStyleRes) { 1074 synchronized (mKey) { 1075 final int len = attrs.length; 1076 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1077 1078 // XXX note that for now we only work with compiled XML files. 1079 // To support generic XML files we will need to manually parse 1080 // out the attributes from the XML file (applying type information 1081 // contained in the resources and such). 1082 final XmlBlock.Parser parser = (XmlBlock.Parser) set; 1083 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, 1084 parser != null ? parser.mParseState : 0, 1085 attrs, array.mData, array.mIndices); 1086 array.mTheme = wrapper; 1087 array.mXml = parser; 1088 1089 return array; 1090 } 1091 } 1092 1093 @NonNull 1094 TypedArray resolveAttributes(@NonNull Resources.Theme wrapper, 1095 @NonNull int[] values, 1096 @NonNull int[] attrs) { 1097 synchronized (mKey) { 1098 final int len = attrs.length; 1099 if (values == null || len != values.length) { 1100 throw new IllegalArgumentException( 1101 "Base attribute values must the same length as attrs"); 1102 } 1103 1104 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); 1105 AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); 1106 array.mTheme = wrapper; 1107 array.mXml = null; 1108 return array; 1109 } 1110 } 1111 1112 boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { 1113 synchronized (mKey) { 1114 return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); 1115 } 1116 } 1117 1118 int[] getAllAttributes() { 1119 return mAssets.getStyleAttributes(getAppliedStyleResId()); 1120 } 1121 1122 @Config int getChangingConfigurations() { 1123 synchronized (mKey) { 1124 final int nativeChangingConfig = 1125 AssetManager.getThemeChangingConfigurations(mTheme); 1126 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); 1127 } 1128 } 1129 1130 public void dump(int priority, String tag, String prefix) { 1131 synchronized (mKey) { 1132 AssetManager.dumpTheme(mTheme, priority, tag, prefix); 1133 } 1134 } 1135 1136 String[] getTheme() { 1137 synchronized (mKey) { 1138 final int N = mKey.mCount; 1139 final String[] themes = new String[N * 2]; 1140 for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { 1141 final int resId = mKey.mResId[j]; 1142 final boolean forced = mKey.mForce[j]; 1143 try { 1144 themes[i] = getResourceName(resId); 1145 } catch (NotFoundException e) { 1146 themes[i] = Integer.toHexString(i); 1147 } 1148 themes[i + 1] = forced ? "forced" : "not forced"; 1149 } 1150 return themes; 1151 } 1152 } 1153 1154 /** 1155 * Rebases the theme against the parent Resource object's current 1156 * configuration by re-applying the styles passed to 1157 * {@link #applyStyle(int, boolean)}. 1158 */ 1159 void rebase() { 1160 synchronized (mKey) { 1161 AssetManager.clearTheme(mTheme); 1162 1163 // Reapply the same styles in the same order. 1164 for (int i = 0; i < mKey.mCount; i++) { 1165 final int resId = mKey.mResId[i]; 1166 final boolean force = mKey.mForce[i]; 1167 AssetManager.applyThemeStyle(mTheme, resId, force); 1168 } 1169 } 1170 } 1171 } 1172} 1173