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