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