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