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