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