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