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