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