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