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