Resources.java revision 52b999f0721b53e9c6e18a4bd664e89aeb65b2d5
1/* 2 * Copyright (C) 2006 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 17package android.content.res; 18 19import com.android.internal.util.XmlUtils; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.content.pm.ActivityInfo; 25import android.graphics.Movie; 26import android.graphics.drawable.Drawable; 27import android.graphics.drawable.ColorDrawable; 28import android.graphics.drawable.Drawable.ConstantState; 29import android.os.Build; 30import android.os.Bundle; 31import android.os.IBinder; 32import android.os.Trace; 33import android.util.AttributeSet; 34import android.util.DisplayMetrics; 35import android.util.Log; 36import android.util.Slog; 37import android.util.SparseArray; 38import android.util.TypedValue; 39import android.util.LongSparseArray; 40 41import java.io.IOException; 42import java.io.InputStream; 43import java.lang.ref.WeakReference; 44import java.util.Locale; 45 46import libcore.icu.NativePluralRules; 47 48/** 49 * Class for accessing an application's resources. This sits on top of the 50 * asset manager of the application (accessible through {@link #getAssets}) and 51 * provides a high-level API for getting typed data from the assets. 52 * 53 * <p>The Android resource system keeps track of all non-code assets associated with an 54 * application. You can use this class to access your application's resources. You can generally 55 * acquire the {@link android.content.res.Resources} instance associated with your application 56 * with {@link android.content.Context#getResources getResources()}.</p> 57 * 58 * <p>The Android SDK tools compile your application's resources into the application binary 59 * at build time. To use a resource, you must install it correctly in the source tree (inside 60 * your project's {@code res/} directory) and build your application. As part of the build 61 * process, the SDK tools generate symbols for each resource, which you can use in your application 62 * code to access the resources.</p> 63 * 64 * <p>Using application resources makes it easy to update various characteristics of your 65 * application without modifying code, and—by providing sets of alternative 66 * resources—enables you to optimize your application for a variety of device configurations 67 * (such as for different languages and screen sizes). This is an important aspect of developing 68 * Android applications that are compatible on different types of devices.</p> 69 * 70 * <p>For more information about using resources, see the documentation about <a 71 * href="{@docRoot}guide/topics/resources/index.html">Application Resources</a>.</p> 72 */ 73public class Resources { 74 static final String TAG = "Resources"; 75 76 private static final boolean DEBUG_LOAD = false; 77 private static final boolean DEBUG_CONFIG = false; 78 private static final boolean TRACE_FOR_PRELOAD = false; 79 private static final boolean TRACE_FOR_MISS_PRELOAD = false; 80 81 private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative( 82 ActivityInfo.CONFIG_LAYOUT_DIRECTION); 83 84 private static final int ID_OTHER = 0x01000004; 85 86 private static final Object sSync = new Object(); 87 88 // Information about preloaded resources. Note that they are not 89 // protected by a lock, because while preloading in zygote we are all 90 // single-threaded, and after that these are immutable. 91 private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; 92 private static final LongSparseArray<ConstantState> sPreloadedColorDrawables 93 = new LongSparseArray<ConstantState>(); 94 private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists 95 = new LongSparseArray<ColorStateList>(); 96 97 // Used by BridgeResources in layoutlib 98 static Resources mSystem = null; 99 100 private static boolean sPreloaded; 101 private static int sPreloadedDensity; 102 103 // These are protected by mAccessLock. 104 private final Object mAccessLock = new Object(); 105 private final Configuration mTmpConfig = new Configuration(); 106 private final ThemedCaches<ConstantState> mDrawableCache = 107 new ThemedCaches<ConstantState>(); 108 private final ThemedCaches<ConstantState> mColorDrawableCache = 109 new ThemedCaches<ConstantState>(); 110 private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = 111 new LongSparseArray<WeakReference<ColorStateList>>(); 112 113 private TypedValue mTmpValue = new TypedValue(); 114 private boolean mPreloading; 115 116 private TypedArray mCachedStyledAttributes = null; 117 private RuntimeException mLastRetrievedAttrs = null; 118 119 private int mLastCachedXmlBlockIndex = -1; 120 private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; 121 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; 122 123 private final AssetManager mAssets; 124 private final Configuration mConfiguration = new Configuration(); 125 private final DisplayMetrics mMetrics = new DisplayMetrics(); 126 private NativePluralRules mPluralRule; 127 128 private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; 129 130 @SuppressWarnings("unused") 131 private WeakReference<IBinder> mToken; 132 133 static { 134 sPreloadedDrawables = new LongSparseArray[2]; 135 sPreloadedDrawables[0] = new LongSparseArray<ConstantState>(); 136 sPreloadedDrawables[1] = new LongSparseArray<ConstantState>(); 137 } 138 139 /** @hide */ 140 public static int selectDefaultTheme(int curTheme, int targetSdkVersion) { 141 return selectSystemTheme(curTheme, targetSdkVersion, 142 com.android.internal.R.style.Theme, 143 com.android.internal.R.style.Theme_Holo, 144 com.android.internal.R.style.Theme_DeviceDefault); 145 } 146 147 /** @hide */ 148 public static int selectSystemTheme(int curTheme, int targetSdkVersion, 149 int orig, int holo, int deviceDefault) { 150 if (curTheme != 0) { 151 return curTheme; 152 } 153 if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) { 154 return orig; 155 } 156 if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 157 return holo; 158 } 159 return deviceDefault; 160 } 161 162 /** 163 * This exception is thrown by the resource APIs when a requested resource 164 * can not be found. 165 */ 166 public static class NotFoundException extends RuntimeException { 167 public NotFoundException() { 168 } 169 170 public NotFoundException(String name) { 171 super(name); 172 } 173 } 174 175 /** 176 * Create a new Resources object on top of an existing set of assets in an 177 * AssetManager. 178 * 179 * @param assets Previously created AssetManager. 180 * @param metrics Current display metrics to consider when 181 * selecting/computing resource values. 182 * @param config Desired device configuration to consider when 183 * selecting/computing resource values (optional). 184 */ 185 public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { 186 this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); 187 } 188 189 /** 190 * Creates a new Resources object with CompatibilityInfo. 191 * 192 * @param assets Previously created AssetManager. 193 * @param metrics Current display metrics to consider when 194 * selecting/computing resource values. 195 * @param config Desired device configuration to consider when 196 * selecting/computing resource values (optional). 197 * @param compatInfo this resource's compatibility info. Must not be null. 198 * @param token The Activity token for determining stack affiliation. Usually null. 199 * @hide 200 */ 201 public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, 202 CompatibilityInfo compatInfo, IBinder token) { 203 mAssets = assets; 204 mMetrics.setToDefaults(); 205 if (compatInfo != null) { 206 mCompatibilityInfo = compatInfo; 207 } 208 mToken = new WeakReference<IBinder>(token); 209 updateConfiguration(config, metrics); 210 assets.ensureStringBlocks(); 211 } 212 213 /** 214 * Return a global shared Resources object that provides access to only 215 * system resources (no application resources), and is not configured for 216 * the current screen (can not use dimension units, does not change based 217 * on orientation, etc). 218 */ 219 public static Resources getSystem() { 220 synchronized (sSync) { 221 Resources ret = mSystem; 222 if (ret == null) { 223 ret = new Resources(); 224 mSystem = ret; 225 } 226 227 return ret; 228 } 229 } 230 231 /** 232 * Return the string value associated with a particular resource ID. The 233 * returned object will be a String if this is a plain string; it will be 234 * some other type of CharSequence if it is styled. 235 * {@more} 236 * 237 * @param id The desired resource identifier, as generated by the aapt 238 * tool. This integer encodes the package, type, and resource 239 * entry. The value 0 is an invalid identifier. 240 * 241 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 242 * 243 * @return CharSequence The string data associated with the resource, plus 244 * possibly styled text information. 245 */ 246 public CharSequence getText(int id) throws NotFoundException { 247 CharSequence res = mAssets.getResourceText(id); 248 if (res != null) { 249 return res; 250 } 251 throw new NotFoundException("String resource ID #0x" 252 + Integer.toHexString(id)); 253 } 254 255 /** 256 * Returns the character sequence necessary for grammatically correct pluralization 257 * of the given resource ID for the given quantity. 258 * Note that the character sequence is selected based solely on grammatical necessity, 259 * and that such rules differ between languages. Do not assume you know which string 260 * will be returned for a given quantity. See 261 * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a> 262 * for more detail. 263 * 264 * @param id The desired resource identifier, as generated by the aapt 265 * tool. This integer encodes the package, type, and resource 266 * entry. The value 0 is an invalid identifier. 267 * @param quantity The number used to get the correct string for the current language's 268 * plural rules. 269 * 270 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 271 * 272 * @return CharSequence The string data associated with the resource, plus 273 * possibly styled text information. 274 */ 275 public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { 276 NativePluralRules rule = getPluralRule(); 277 CharSequence res = mAssets.getResourceBagText(id, 278 attrForQuantityCode(rule.quantityForInt(quantity))); 279 if (res != null) { 280 return res; 281 } 282 res = mAssets.getResourceBagText(id, ID_OTHER); 283 if (res != null) { 284 return res; 285 } 286 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id) 287 + " quantity=" + quantity 288 + " item=" + stringForQuantityCode(rule.quantityForInt(quantity))); 289 } 290 291 private NativePluralRules getPluralRule() { 292 synchronized (sSync) { 293 if (mPluralRule == null) { 294 mPluralRule = NativePluralRules.forLocale(mConfiguration.locale); 295 } 296 return mPluralRule; 297 } 298 } 299 300 private static int attrForQuantityCode(int quantityCode) { 301 switch (quantityCode) { 302 case NativePluralRules.ZERO: return 0x01000005; 303 case NativePluralRules.ONE: return 0x01000006; 304 case NativePluralRules.TWO: return 0x01000007; 305 case NativePluralRules.FEW: return 0x01000008; 306 case NativePluralRules.MANY: return 0x01000009; 307 default: return ID_OTHER; 308 } 309 } 310 311 private static String stringForQuantityCode(int quantityCode) { 312 switch (quantityCode) { 313 case NativePluralRules.ZERO: return "zero"; 314 case NativePluralRules.ONE: return "one"; 315 case NativePluralRules.TWO: return "two"; 316 case NativePluralRules.FEW: return "few"; 317 case NativePluralRules.MANY: return "many"; 318 default: return "other"; 319 } 320 } 321 322 /** 323 * Return the string value associated with a particular resource ID. It 324 * will be stripped of any styled text information. 325 * {@more} 326 * 327 * @param id The desired resource identifier, as generated by the aapt 328 * tool. This integer encodes the package, type, and resource 329 * entry. The value 0 is an invalid identifier. 330 * 331 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 332 * 333 * @return String The string data associated with the resource, 334 * stripped of styled text information. 335 */ 336 public String getString(int id) throws NotFoundException { 337 CharSequence res = getText(id); 338 if (res != null) { 339 return res.toString(); 340 } 341 throw new NotFoundException("String resource ID #0x" 342 + Integer.toHexString(id)); 343 } 344 345 346 /** 347 * Return the string value associated with a particular resource ID, 348 * substituting the format arguments as defined in {@link java.util.Formatter} 349 * and {@link java.lang.String#format}. It will be stripped of any styled text 350 * information. 351 * {@more} 352 * 353 * @param id The desired resource identifier, as generated by the aapt 354 * tool. This integer encodes the package, type, and resource 355 * entry. The value 0 is an invalid identifier. 356 * 357 * @param formatArgs The format arguments that will be used for substitution. 358 * 359 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 360 * 361 * @return String The string data associated with the resource, 362 * stripped of styled text information. 363 */ 364 public String getString(int id, Object... formatArgs) throws NotFoundException { 365 String raw = getString(id); 366 return String.format(mConfiguration.locale, raw, formatArgs); 367 } 368 369 /** 370 * Formats the string necessary for grammatically correct pluralization 371 * of the given resource ID for the given quantity, using the given arguments. 372 * Note that the string is selected based solely on grammatical necessity, 373 * and that such rules differ between languages. Do not assume you know which string 374 * will be returned for a given quantity. See 375 * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a> 376 * for more detail. 377 * 378 * <p>Substitution of format arguments works as if using 379 * {@link java.util.Formatter} and {@link java.lang.String#format}. 380 * The resulting string will be stripped of any styled text information. 381 * 382 * @param id The desired resource identifier, as generated by the aapt 383 * tool. This integer encodes the package, type, and resource 384 * entry. The value 0 is an invalid identifier. 385 * @param quantity The number used to get the correct string for the current language's 386 * plural rules. 387 * @param formatArgs The format arguments that will be used for substitution. 388 * 389 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 390 * 391 * @return String The string data associated with the resource, 392 * stripped of styled text information. 393 */ 394 public String getQuantityString(int id, int quantity, Object... formatArgs) 395 throws NotFoundException { 396 String raw = getQuantityText(id, quantity).toString(); 397 return String.format(mConfiguration.locale, raw, formatArgs); 398 } 399 400 /** 401 * Returns the string necessary for grammatically correct pluralization 402 * of the given resource ID for the given quantity. 403 * Note that the string is selected based solely on grammatical necessity, 404 * and that such rules differ between languages. Do not assume you know which string 405 * will be returned for a given quantity. See 406 * <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String Resources</a> 407 * for more detail. 408 * 409 * @param id The desired resource identifier, as generated by the aapt 410 * tool. This integer encodes the package, type, and resource 411 * entry. The value 0 is an invalid identifier. 412 * @param quantity The number used to get the correct string for the current language's 413 * plural rules. 414 * 415 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 416 * 417 * @return String The string data associated with the resource, 418 * stripped of styled text information. 419 */ 420 public String getQuantityString(int id, int quantity) throws NotFoundException { 421 return getQuantityText(id, quantity).toString(); 422 } 423 424 /** 425 * Return the string value associated with a particular resource ID. The 426 * returned object will be a String if this is a plain string; it will be 427 * some other type of CharSequence if it is styled. 428 * 429 * @param id The desired resource identifier, as generated by the aapt 430 * tool. This integer encodes the package, type, and resource 431 * entry. The value 0 is an invalid identifier. 432 * 433 * @param def The default CharSequence to return. 434 * 435 * @return CharSequence The string data associated with the resource, plus 436 * possibly styled text information, or def if id is 0 or not found. 437 */ 438 public CharSequence getText(int id, CharSequence def) { 439 CharSequence res = id != 0 ? mAssets.getResourceText(id) : null; 440 return res != null ? res : def; 441 } 442 443 /** 444 * Return the styled text array associated with a particular resource ID. 445 * 446 * @param id The desired resource identifier, as generated by the aapt 447 * tool. This integer encodes the package, type, and resource 448 * entry. The value 0 is an invalid identifier. 449 * 450 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 451 * 452 * @return The styled text array associated with the resource. 453 */ 454 public CharSequence[] getTextArray(int id) throws NotFoundException { 455 CharSequence[] res = mAssets.getResourceTextArray(id); 456 if (res != null) { 457 return res; 458 } 459 throw new NotFoundException("Text array resource ID #0x" 460 + Integer.toHexString(id)); 461 } 462 463 /** 464 * Return the string array associated with a particular resource ID. 465 * 466 * @param id The desired resource identifier, as generated by the aapt 467 * tool. This integer encodes the package, type, and resource 468 * entry. The value 0 is an invalid identifier. 469 * 470 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 471 * 472 * @return The string array associated with the resource. 473 */ 474 public String[] getStringArray(int id) throws NotFoundException { 475 String[] res = mAssets.getResourceStringArray(id); 476 if (res != null) { 477 return res; 478 } 479 throw new NotFoundException("String array resource ID #0x" 480 + Integer.toHexString(id)); 481 } 482 483 /** 484 * Return the int array associated with a particular resource ID. 485 * 486 * @param id The desired resource identifier, as generated by the aapt 487 * tool. This integer encodes the package, type, and resource 488 * entry. The value 0 is an invalid identifier. 489 * 490 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 491 * 492 * @return The int array associated with the resource. 493 */ 494 public int[] getIntArray(int id) throws NotFoundException { 495 int[] res = mAssets.getArrayIntResource(id); 496 if (res != null) { 497 return res; 498 } 499 throw new NotFoundException("Int array resource ID #0x" 500 + Integer.toHexString(id)); 501 } 502 503 /** 504 * Return an array of heterogeneous values. 505 * 506 * @param id The desired resource identifier, as generated by the aapt 507 * tool. This integer encodes the package, type, and resource 508 * entry. The value 0 is an invalid identifier. 509 * 510 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 511 * 512 * @return Returns a TypedArray holding an array of the array values. 513 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 514 * when done with it. 515 */ 516 public TypedArray obtainTypedArray(int id) throws NotFoundException { 517 int len = mAssets.getArraySize(id); 518 if (len < 0) { 519 throw new NotFoundException("Array resource ID #0x" 520 + Integer.toHexString(id)); 521 } 522 523 TypedArray array = TypedArray.obtain(this, len); 524 array.mLength = mAssets.retrieveArray(id, array.mData); 525 array.mIndices[0] = 0; 526 527 return array; 528 } 529 530 /** 531 * Retrieve a dimensional for a particular resource ID. Unit 532 * conversions are based on the current {@link DisplayMetrics} associated 533 * with the resources. 534 * 535 * @param id The desired resource identifier, as generated by the aapt 536 * tool. This integer encodes the package, type, and resource 537 * entry. The value 0 is an invalid identifier. 538 * 539 * @return Resource dimension value multiplied by the appropriate 540 * metric. 541 * 542 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 543 * 544 * @see #getDimensionPixelOffset 545 * @see #getDimensionPixelSize 546 */ 547 public float getDimension(int id) throws NotFoundException { 548 synchronized (mAccessLock) { 549 TypedValue value = mTmpValue; 550 if (value == null) { 551 mTmpValue = value = new TypedValue(); 552 } 553 getValue(id, value, true); 554 if (value.type == TypedValue.TYPE_DIMENSION) { 555 return TypedValue.complexToDimension(value.data, mMetrics); 556 } 557 throw new NotFoundException( 558 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 559 + Integer.toHexString(value.type) + " is not valid"); 560 } 561 } 562 563 /** 564 * Retrieve a dimensional for a particular resource ID for use 565 * as an offset in raw pixels. This is the same as 566 * {@link #getDimension}, except the returned value is converted to 567 * integer pixels for you. An offset conversion involves simply 568 * truncating the base value to an integer. 569 * 570 * @param id The desired resource identifier, as generated by the aapt 571 * tool. This integer encodes the package, type, and resource 572 * entry. The value 0 is an invalid identifier. 573 * 574 * @return Resource dimension value multiplied by the appropriate 575 * metric and truncated to integer pixels. 576 * 577 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 578 * 579 * @see #getDimension 580 * @see #getDimensionPixelSize 581 */ 582 public int getDimensionPixelOffset(int id) throws NotFoundException { 583 synchronized (mAccessLock) { 584 TypedValue value = mTmpValue; 585 if (value == null) { 586 mTmpValue = value = new TypedValue(); 587 } 588 getValue(id, value, true); 589 if (value.type == TypedValue.TYPE_DIMENSION) { 590 return TypedValue.complexToDimensionPixelOffset( 591 value.data, mMetrics); 592 } 593 throw new NotFoundException( 594 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 595 + Integer.toHexString(value.type) + " is not valid"); 596 } 597 } 598 599 /** 600 * Retrieve a dimensional for a particular resource ID for use 601 * as a size in raw pixels. This is the same as 602 * {@link #getDimension}, except the returned value is converted to 603 * integer pixels for use as a size. A size conversion involves 604 * rounding the base value, and ensuring that a non-zero base value 605 * is at least one pixel in size. 606 * 607 * @param id The desired resource identifier, as generated by the aapt 608 * tool. This integer encodes the package, type, and resource 609 * entry. The value 0 is an invalid identifier. 610 * 611 * @return Resource dimension value multiplied by the appropriate 612 * metric and truncated to integer pixels. 613 * 614 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 615 * 616 * @see #getDimension 617 * @see #getDimensionPixelOffset 618 */ 619 public int getDimensionPixelSize(int id) throws NotFoundException { 620 synchronized (mAccessLock) { 621 TypedValue value = mTmpValue; 622 if (value == null) { 623 mTmpValue = value = new TypedValue(); 624 } 625 getValue(id, value, true); 626 if (value.type == TypedValue.TYPE_DIMENSION) { 627 return TypedValue.complexToDimensionPixelSize( 628 value.data, mMetrics); 629 } 630 throw new NotFoundException( 631 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 632 + Integer.toHexString(value.type) + " is not valid"); 633 } 634 } 635 636 /** 637 * Retrieve a fractional unit for a particular resource ID. 638 * 639 * @param id The desired resource identifier, as generated by the aapt 640 * tool. This integer encodes the package, type, and resource 641 * entry. The value 0 is an invalid identifier. 642 * @param base The base value of this fraction. In other words, a 643 * standard fraction is multiplied by this value. 644 * @param pbase The parent base value of this fraction. In other 645 * words, a parent fraction (nn%p) is multiplied by this 646 * value. 647 * 648 * @return Attribute fractional value multiplied by the appropriate 649 * base value. 650 * 651 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 652 */ 653 public float getFraction(int id, int base, int pbase) { 654 synchronized (mAccessLock) { 655 TypedValue value = mTmpValue; 656 if (value == null) { 657 mTmpValue = value = new TypedValue(); 658 } 659 getValue(id, value, true); 660 if (value.type == TypedValue.TYPE_FRACTION) { 661 return TypedValue.complexToFraction(value.data, base, pbase); 662 } 663 throw new NotFoundException( 664 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 665 + Integer.toHexString(value.type) + " is not valid"); 666 } 667 } 668 669 /** 670 * Return a drawable object associated with a particular resource ID. 671 * Various types of objects will be returned depending on the underlying 672 * resource -- for example, a solid color, PNG image, scalable image, etc. 673 * The Drawable API hides these implementation details. 674 * 675 * <p class="note"><strong>Note:</strong> Prior to 676 * {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, this function 677 * would not correctly retrieve the final configuration density when 678 * the resource ID passed here is an alias to another Drawable resource. 679 * This means that if the density configuration of the alias resource 680 * is different than the actual resource, the density of the returned 681 * Drawable would be incorrect, resulting in bad scaling. To work 682 * around this, you can instead retrieve the Drawable through 683 * {@link TypedArray#getDrawable TypedArray.getDrawable}. Use 684 * {@link android.content.Context#obtainStyledAttributes(int[]) 685 * Context.obtainStyledAttributes} with 686 * an array containing the resource ID of interest to create the TypedArray.</p> 687 * 688 * @param id The desired resource identifier, as generated by the aapt 689 * tool. This integer encodes the package, type, and resource 690 * entry. The value 0 is an invalid identifier. 691 * @return Drawable An object that can be used to draw this resource. 692 * @throws NotFoundException Throws NotFoundException if the given ID does 693 * not exist. 694 */ 695 public Drawable getDrawable(int id) throws NotFoundException { 696 return getDrawable(id, null); 697 } 698 699 /** 700 * Return a drawable object associated with a particular resource ID and 701 * styled for the specified theme. 702 * 703 * @param id The desired resource identifier, as generated by the aapt 704 * tool. This integer encodes the package, type, and resource 705 * entry. The value 0 is an invalid identifier. 706 * @param theme The theme used to style the drawable attributes. 707 * @return Drawable An object that can be used to draw this resource. 708 * @throws NotFoundException Throws NotFoundException if the given ID does 709 * not exist. 710 */ 711 public Drawable getDrawable(int id, Theme theme) throws NotFoundException { 712 TypedValue value; 713 synchronized (mAccessLock) { 714 value = mTmpValue; 715 if (value == null) { 716 value = new TypedValue(); 717 } else { 718 mTmpValue = null; 719 } 720 getValue(id, value, true); 721 } 722 final Drawable res = loadDrawable(value, id, theme); 723 synchronized (mAccessLock) { 724 if (mTmpValue == null) { 725 mTmpValue = value; 726 } 727 } 728 return res; 729 } 730 731 /** 732 * Return a drawable object associated with a particular resource ID for the 733 * given screen density in DPI. This will set the drawable's density to be 734 * the device's density multiplied by the ratio of actual drawable density 735 * to requested density. This allows the drawable to be scaled up to the 736 * correct size if needed. Various types of objects will be returned 737 * depending on the underlying resource -- for example, a solid color, PNG 738 * image, scalable image, etc. The Drawable API hides these implementation 739 * details. 740 * 741 * @param id The desired resource identifier, as generated by the aapt tool. 742 * This integer encodes the package, type, and resource entry. 743 * The value 0 is an invalid identifier. 744 * @param density the desired screen density indicated by the resource as 745 * found in {@link DisplayMetrics}. 746 * @return Drawable An object that can be used to draw this resource. 747 * @throws NotFoundException Throws NotFoundException if the given ID does 748 * not exist. 749 * @see #getDrawableForDensity(int, int, Theme) 750 */ 751 public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { 752 return getDrawableForDensity(id, density, null); 753 } 754 755 /** 756 * Return a drawable object associated with a particular resource ID for the 757 * given screen density in DPI and styled for the specified theme. 758 * 759 * @param id The desired resource identifier, as generated by the aapt tool. 760 * This integer encodes the package, type, and resource entry. 761 * The value 0 is an invalid identifier. 762 * @param density The desired screen density indicated by the resource as 763 * found in {@link DisplayMetrics}. 764 * @param theme The theme used to style the drawable attributes. 765 * @return Drawable An object that can be used to draw this resource. 766 * @throws NotFoundException Throws NotFoundException if the given ID does 767 * not exist. 768 */ 769 public Drawable getDrawableForDensity(int id, int density, Theme theme) { 770 TypedValue value; 771 synchronized (mAccessLock) { 772 value = mTmpValue; 773 if (value == null) { 774 value = new TypedValue(); 775 } else { 776 mTmpValue = null; 777 } 778 getValueForDensity(id, density, value, true); 779 780 /* 781 * Pretend the requested density is actually the display density. If 782 * the drawable returned is not the requested density, then force it 783 * to be scaled later by dividing its density by the ratio of 784 * requested density to actual device density. Drawables that have 785 * undefined density or no density don't need to be handled here. 786 */ 787 if (value.density > 0 && value.density != TypedValue.DENSITY_NONE) { 788 if (value.density == density) { 789 value.density = mMetrics.densityDpi; 790 } else { 791 value.density = (value.density * mMetrics.densityDpi) / density; 792 } 793 } 794 } 795 796 final Drawable res = loadDrawable(value, id, theme); 797 synchronized (mAccessLock) { 798 if (mTmpValue == null) { 799 mTmpValue = value; 800 } 801 } 802 return res; 803 } 804 805 /** 806 * Return a movie object associated with the particular resource ID. 807 * @param id The desired resource identifier, as generated by the aapt 808 * tool. This integer encodes the package, type, and resource 809 * entry. The value 0 is an invalid identifier. 810 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 811 * 812 */ 813 public Movie getMovie(int id) throws NotFoundException { 814 InputStream is = openRawResource(id); 815 Movie movie = Movie.decodeStream(is); 816 try { 817 is.close(); 818 } 819 catch (java.io.IOException e) { 820 // don't care, since the return value is valid 821 } 822 return movie; 823 } 824 825 /** 826 * Return a color integer associated with a particular resource ID. 827 * If the resource holds a complex 828 * {@link android.content.res.ColorStateList}, then the default color from 829 * the set is returned. 830 * 831 * @param id The desired resource identifier, as generated by the aapt 832 * tool. This integer encodes the package, type, and resource 833 * entry. The value 0 is an invalid identifier. 834 * 835 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 836 * 837 * @return Returns a single color value in the form 0xAARRGGBB. 838 */ 839 public int getColor(int id) throws NotFoundException { 840 TypedValue value; 841 synchronized (mAccessLock) { 842 value = mTmpValue; 843 if (value == null) { 844 value = new TypedValue(); 845 } 846 getValue(id, value, true); 847 if (value.type >= TypedValue.TYPE_FIRST_INT 848 && value.type <= TypedValue.TYPE_LAST_INT) { 849 mTmpValue = value; 850 return value.data; 851 } else if (value.type != TypedValue.TYPE_STRING) { 852 throw new NotFoundException( 853 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 854 + Integer.toHexString(value.type) + " is not valid"); 855 } 856 mTmpValue = null; 857 } 858 ColorStateList csl = loadColorStateList(value, id); 859 synchronized (mAccessLock) { 860 if (mTmpValue == null) { 861 mTmpValue = value; 862 } 863 } 864 return csl.getDefaultColor(); 865 } 866 867 /** 868 * Return a color state list associated with a particular resource ID. The 869 * resource may contain either a single raw color value, or a complex 870 * {@link android.content.res.ColorStateList} holding multiple possible colors. 871 * 872 * @param id The desired resource identifier of a {@link ColorStateList}, 873 * as generated by the aapt tool. This integer encodes the package, type, and resource 874 * entry. The value 0 is an invalid identifier. 875 * 876 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 877 * 878 * @return Returns a ColorStateList object containing either a single 879 * solid color or multiple colors that can be selected based on a state. 880 */ 881 public ColorStateList getColorStateList(int id) throws NotFoundException { 882 TypedValue value; 883 synchronized (mAccessLock) { 884 value = mTmpValue; 885 if (value == null) { 886 value = new TypedValue(); 887 } else { 888 mTmpValue = null; 889 } 890 getValue(id, value, true); 891 } 892 ColorStateList res = loadColorStateList(value, id); 893 synchronized (mAccessLock) { 894 if (mTmpValue == null) { 895 mTmpValue = value; 896 } 897 } 898 return res; 899 } 900 901 /** 902 * Return a boolean associated with a particular resource ID. This can be 903 * used with any integral resource value, and will return true if it is 904 * non-zero. 905 * 906 * @param id The desired resource identifier, as generated by the aapt 907 * tool. This integer encodes the package, type, and resource 908 * entry. The value 0 is an invalid identifier. 909 * 910 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 911 * 912 * @return Returns the boolean value contained in the resource. 913 */ 914 public boolean getBoolean(int id) throws NotFoundException { 915 synchronized (mAccessLock) { 916 TypedValue value = mTmpValue; 917 if (value == null) { 918 mTmpValue = value = new TypedValue(); 919 } 920 getValue(id, value, true); 921 if (value.type >= TypedValue.TYPE_FIRST_INT 922 && value.type <= TypedValue.TYPE_LAST_INT) { 923 return value.data != 0; 924 } 925 throw new NotFoundException( 926 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 927 + Integer.toHexString(value.type) + " is not valid"); 928 } 929 } 930 931 /** 932 * Return an integer associated with a particular resource ID. 933 * 934 * @param id The desired resource identifier, as generated by the aapt 935 * tool. This integer encodes the package, type, and resource 936 * entry. The value 0 is an invalid identifier. 937 * 938 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 939 * 940 * @return Returns the integer value contained in the resource. 941 */ 942 public int getInteger(int id) throws NotFoundException { 943 synchronized (mAccessLock) { 944 TypedValue value = mTmpValue; 945 if (value == null) { 946 mTmpValue = value = new TypedValue(); 947 } 948 getValue(id, value, true); 949 if (value.type >= TypedValue.TYPE_FIRST_INT 950 && value.type <= TypedValue.TYPE_LAST_INT) { 951 return value.data; 952 } 953 throw new NotFoundException( 954 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 955 + Integer.toHexString(value.type) + " is not valid"); 956 } 957 } 958 959 /** 960 * Return an XmlResourceParser through which you can read a view layout 961 * description for the given resource ID. This parser has limited 962 * functionality -- in particular, you can't change its input, and only 963 * the high-level events are available. 964 * 965 * <p>This function is really a simple wrapper for calling 966 * {@link #getXml} with a layout resource. 967 * 968 * @param id The desired resource identifier, as generated by the aapt 969 * tool. This integer encodes the package, type, and resource 970 * entry. The value 0 is an invalid identifier. 971 * 972 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 973 * 974 * @return A new parser object through which you can read 975 * the XML data. 976 * 977 * @see #getXml 978 */ 979 public XmlResourceParser getLayout(int id) throws NotFoundException { 980 return loadXmlResourceParser(id, "layout"); 981 } 982 983 /** 984 * Return an XmlResourceParser through which you can read an animation 985 * description for the given resource ID. This parser has limited 986 * functionality -- in particular, you can't change its input, and only 987 * the high-level events are available. 988 * 989 * <p>This function is really a simple wrapper for calling 990 * {@link #getXml} with an animation resource. 991 * 992 * @param id The desired resource identifier, as generated by the aapt 993 * tool. This integer encodes the package, type, and resource 994 * entry. The value 0 is an invalid identifier. 995 * 996 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 997 * 998 * @return A new parser object through which you can read 999 * the XML data. 1000 * 1001 * @see #getXml 1002 */ 1003 public XmlResourceParser getAnimation(int id) throws NotFoundException { 1004 return loadXmlResourceParser(id, "anim"); 1005 } 1006 1007 /** 1008 * Return an XmlResourceParser through which you can read a generic XML 1009 * resource for the given resource ID. 1010 * 1011 * <p>The XmlPullParser implementation returned here has some limited 1012 * functionality. In particular, you can't change its input, and only 1013 * high-level parsing events are available (since the document was 1014 * pre-parsed for you at build time, which involved merging text and 1015 * stripping comments). 1016 * 1017 * @param id The desired resource identifier, as generated by the aapt 1018 * tool. This integer encodes the package, type, and resource 1019 * entry. The value 0 is an invalid identifier. 1020 * 1021 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1022 * 1023 * @return A new parser object through which you can read 1024 * the XML data. 1025 * 1026 * @see android.util.AttributeSet 1027 */ 1028 public XmlResourceParser getXml(int id) throws NotFoundException { 1029 return loadXmlResourceParser(id, "xml"); 1030 } 1031 1032 /** 1033 * Open a data stream for reading a raw resource. This can only be used 1034 * with resources whose value is the name of an asset files -- that is, it can be 1035 * used to open drawable, sound, and raw resources; it will fail on string 1036 * and color resources. 1037 * 1038 * @param id The resource identifier to open, as generated by the appt 1039 * tool. 1040 * 1041 * @return InputStream Access to the resource data. 1042 * 1043 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1044 * 1045 */ 1046 public InputStream openRawResource(int id) throws NotFoundException { 1047 TypedValue value; 1048 synchronized (mAccessLock) { 1049 value = mTmpValue; 1050 if (value == null) { 1051 value = new TypedValue(); 1052 } else { 1053 mTmpValue = null; 1054 } 1055 } 1056 InputStream res = openRawResource(id, value); 1057 synchronized (mAccessLock) { 1058 if (mTmpValue == null) { 1059 mTmpValue = value; 1060 } 1061 } 1062 return res; 1063 } 1064 1065 /** 1066 * Open a data stream for reading a raw resource. This can only be used 1067 * with resources whose value is the name of an asset file -- that is, it can be 1068 * used to open drawable, sound, and raw resources; it will fail on string 1069 * and color resources. 1070 * 1071 * @param id The resource identifier to open, as generated by the appt tool. 1072 * @param value The TypedValue object to hold the resource information. 1073 * 1074 * @return InputStream Access to the resource data. 1075 * 1076 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1077 */ 1078 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 1079 getValue(id, value, true); 1080 1081 try { 1082 return mAssets.openNonAsset(value.assetCookie, value.string.toString(), 1083 AssetManager.ACCESS_STREAMING); 1084 } catch (Exception e) { 1085 NotFoundException rnf = new NotFoundException("File " + value.string.toString() + 1086 " from drawable resource ID #0x" + Integer.toHexString(id)); 1087 rnf.initCause(e); 1088 throw rnf; 1089 } 1090 } 1091 1092 /** 1093 * Open a file descriptor for reading a raw resource. This can only be used 1094 * with resources whose value is the name of an asset files -- that is, it can be 1095 * used to open drawable, sound, and raw resources; it will fail on string 1096 * and color resources. 1097 * 1098 * <p>This function only works for resources that are stored in the package 1099 * as uncompressed data, which typically includes things like mp3 files 1100 * and png images. 1101 * 1102 * @param id The resource identifier to open, as generated by the appt 1103 * tool. 1104 * 1105 * @return AssetFileDescriptor A new file descriptor you can use to read 1106 * the resource. This includes the file descriptor itself, as well as the 1107 * offset and length of data where the resource appears in the file. A 1108 * null is returned if the file exists but is compressed. 1109 * 1110 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1111 * 1112 */ 1113 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 1114 TypedValue value; 1115 synchronized (mAccessLock) { 1116 value = mTmpValue; 1117 if (value == null) { 1118 value = new TypedValue(); 1119 } else { 1120 mTmpValue = null; 1121 } 1122 getValue(id, value, true); 1123 } 1124 try { 1125 return mAssets.openNonAssetFd( 1126 value.assetCookie, value.string.toString()); 1127 } catch (Exception e) { 1128 NotFoundException rnf = new NotFoundException( 1129 "File " + value.string.toString() 1130 + " from drawable resource ID #0x" 1131 + Integer.toHexString(id)); 1132 rnf.initCause(e); 1133 throw rnf; 1134 } finally { 1135 synchronized (mAccessLock) { 1136 if (mTmpValue == null) { 1137 mTmpValue = value; 1138 } 1139 } 1140 } 1141 } 1142 1143 /** 1144 * Return the raw data associated with a particular resource ID. 1145 * 1146 * @param id The desired resource identifier, as generated by the aapt 1147 * tool. This integer encodes the package, type, and resource 1148 * entry. The value 0 is an invalid identifier. 1149 * @param outValue Object in which to place the resource data. 1150 * @param resolveRefs If true, a resource that is a reference to another 1151 * resource will be followed so that you receive the 1152 * actual final resource data. If false, the TypedValue 1153 * will be filled in with the reference itself. 1154 * 1155 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1156 * 1157 */ 1158 public void getValue(int id, TypedValue outValue, boolean resolveRefs) 1159 throws NotFoundException { 1160 boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs); 1161 if (found) { 1162 return; 1163 } 1164 throw new NotFoundException("Resource ID #0x" 1165 + Integer.toHexString(id)); 1166 } 1167 1168 /** 1169 * Get the raw value associated with a resource with associated density. 1170 * 1171 * @param id resource identifier 1172 * @param density density in DPI 1173 * @param resolveRefs If true, a resource that is a reference to another 1174 * resource will be followed so that you receive the actual final 1175 * resource data. If false, the TypedValue will be filled in with 1176 * the reference itself. 1177 * @throws NotFoundException Throws NotFoundException if the given ID does 1178 * not exist. 1179 * @see #getValue(String, TypedValue, boolean) 1180 */ 1181 public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) 1182 throws NotFoundException { 1183 boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs); 1184 if (found) { 1185 return; 1186 } 1187 throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)); 1188 } 1189 1190 /** 1191 * Return the raw data associated with a particular resource ID. 1192 * See getIdentifier() for information on how names are mapped to resource 1193 * IDs, and getString(int) for information on how string resources are 1194 * retrieved. 1195 * 1196 * <p>Note: use of this function is discouraged. It is much more 1197 * efficient to retrieve resources by identifier than by name. 1198 * 1199 * @param name The name of the desired resource. This is passed to 1200 * getIdentifier() with a default type of "string". 1201 * @param outValue Object in which to place the resource data. 1202 * @param resolveRefs If true, a resource that is a reference to another 1203 * resource will be followed so that you receive the 1204 * actual final resource data. If false, the TypedValue 1205 * will be filled in with the reference itself. 1206 * 1207 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1208 * 1209 */ 1210 public void getValue(String name, TypedValue outValue, boolean resolveRefs) 1211 throws NotFoundException { 1212 int id = getIdentifier(name, "string", null); 1213 if (id != 0) { 1214 getValue(id, outValue, resolveRefs); 1215 return; 1216 } 1217 throw new NotFoundException("String resource name " + name); 1218 } 1219 1220 /** 1221 * This class holds the current attribute values for a particular theme. 1222 * In other words, a Theme is a set of values for resource attributes; 1223 * these are used in conjunction with {@link TypedArray} 1224 * to resolve the final value for an attribute. 1225 * 1226 * <p>The Theme's attributes come into play in two ways: (1) a styled 1227 * attribute can explicit reference a value in the theme through the 1228 * "?themeAttribute" syntax; (2) if no value has been defined for a 1229 * particular styled attribute, as a last resort we will try to find that 1230 * attribute's value in the Theme. 1231 * 1232 * <p>You will normally use the {@link #obtainStyledAttributes} APIs to 1233 * retrieve XML attributes with style and theme information applied. 1234 */ 1235 public final class Theme { 1236 /** 1237 * Place new attribute values into the theme. The style resource 1238 * specified by <var>resid</var> will be retrieved from this Theme's 1239 * resources, its values placed into the Theme object. 1240 * 1241 * <p>The semantics of this function depends on the <var>force</var> 1242 * argument: If false, only values that are not already defined in 1243 * the theme will be copied from the system resource; otherwise, if 1244 * any of the style's attributes are already defined in the theme, the 1245 * current values in the theme will be overwritten. 1246 * 1247 * @param resid The resource ID of a style resource from which to 1248 * obtain attribute values. 1249 * @param force If true, values in the style resource will always be 1250 * used in the theme; otherwise, they will only be used 1251 * if not already defined in the theme. 1252 */ 1253 public void applyStyle(int resid, boolean force) { 1254 AssetManager.applyThemeStyle(mTheme, resid, force); 1255 1256 if (!mHasStyle) { 1257 mHasStyle = true; 1258 mThemeResId = resid; 1259 } else if (resid != mThemeResId) { 1260 mThemeResId = 0; 1261 } 1262 } 1263 1264 /** 1265 * Set this theme to hold the same contents as the theme 1266 * <var>other</var>. If both of these themes are from the same 1267 * Resources object, they will be identical after this function 1268 * returns. If they are from different Resources, only the resources 1269 * they have in common will be set in this theme. 1270 * 1271 * @param other The existing Theme to copy from. 1272 */ 1273 public void setTo(Theme other) { 1274 AssetManager.copyTheme(mTheme, other.mTheme); 1275 1276 mHasStyle = other.mHasStyle; 1277 mThemeResId = other.mThemeResId; 1278 } 1279 1280 /** 1281 * Return a TypedArray holding the values defined by 1282 * <var>Theme</var> which are listed in <var>attrs</var>. 1283 * 1284 * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done 1285 * with the array. 1286 * 1287 * @param attrs The desired attributes. 1288 * 1289 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1290 * 1291 * @return Returns a TypedArray holding an array of the attribute values. 1292 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1293 * when done with it. 1294 * 1295 * @see Resources#obtainAttributes 1296 * @see #obtainStyledAttributes(int, int[]) 1297 * @see #obtainStyledAttributes(AttributeSet, int[], int, int) 1298 */ 1299 public TypedArray obtainStyledAttributes(int[] attrs) { 1300 final int len = attrs.length; 1301 final TypedArray array = TypedArray.obtain(Resources.this, len); 1302 array.mTheme = this; 1303 AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices); 1304 return array; 1305 } 1306 1307 /** 1308 * Return a TypedArray holding the values defined by the style 1309 * resource <var>resid</var> which are listed in <var>attrs</var>. 1310 * 1311 * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done 1312 * with the array. 1313 * 1314 * @param resid The desired style resource. 1315 * @param attrs The desired attributes in the style. 1316 * 1317 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1318 * 1319 * @return Returns a TypedArray holding an array of the attribute values. 1320 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1321 * when done with it. 1322 * 1323 * @see Resources#obtainAttributes 1324 * @see #obtainStyledAttributes(int[]) 1325 * @see #obtainStyledAttributes(AttributeSet, int[], int, int) 1326 */ 1327 public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException { 1328 final int len = attrs.length; 1329 final TypedArray array = TypedArray.obtain(Resources.this, len); 1330 array.mTheme = this; 1331 if (false) { 1332 int[] data = array.mData; 1333 1334 System.out.println("**********************************************************"); 1335 System.out.println("**********************************************************"); 1336 System.out.println("**********************************************************"); 1337 System.out.println("Attributes:"); 1338 String s = " Attrs:"; 1339 int i; 1340 for (i=0; i<attrs.length; i++) { 1341 s = s + " 0x" + Integer.toHexString(attrs[i]); 1342 } 1343 System.out.println(s); 1344 s = " Found:"; 1345 TypedValue value = new TypedValue(); 1346 for (i=0; i<attrs.length; i++) { 1347 int d = i*AssetManager.STYLE_NUM_ENTRIES; 1348 value.type = data[d+AssetManager.STYLE_TYPE]; 1349 value.data = data[d+AssetManager.STYLE_DATA]; 1350 value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; 1351 value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; 1352 s = s + " 0x" + Integer.toHexString(attrs[i]) 1353 + "=" + value; 1354 } 1355 System.out.println(s); 1356 } 1357 AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, array.mData, array.mIndices); 1358 return array; 1359 } 1360 1361 /** 1362 * Return a TypedArray holding the attribute values in 1363 * <var>set</var> 1364 * that are listed in <var>attrs</var>. In addition, if the given 1365 * AttributeSet specifies a style class (through the "style" attribute), 1366 * that style will be applied on top of the base attributes it defines. 1367 * 1368 * <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done 1369 * with the array. 1370 * 1371 * <p>When determining the final value of a particular attribute, there 1372 * are four inputs that come into play:</p> 1373 * 1374 * <ol> 1375 * <li> Any attribute values in the given AttributeSet. 1376 * <li> The style resource specified in the AttributeSet (named 1377 * "style"). 1378 * <li> The default style specified by <var>defStyleAttr</var> and 1379 * <var>defStyleRes</var> 1380 * <li> The base values in this theme. 1381 * </ol> 1382 * 1383 * <p>Each of these inputs is considered in-order, with the first listed 1384 * taking precedence over the following ones. In other words, if in the 1385 * AttributeSet you have supplied <code><Button 1386 * textColor="#ff000000"></code>, then the button's text will 1387 * <em>always</em> be black, regardless of what is specified in any of 1388 * the styles. 1389 * 1390 * @param set The base set of attribute values. May be null. 1391 * @param attrs The desired attributes to be retrieved. 1392 * @param defStyleAttr An attribute in the current theme that contains a 1393 * reference to a style resource that supplies 1394 * defaults values for the TypedArray. Can be 1395 * 0 to not look for defaults. 1396 * @param defStyleRes A resource identifier of a style resource that 1397 * supplies default values for the TypedArray, 1398 * used only if defStyleAttr is 0 or can not be found 1399 * in the theme. Can be 0 to not look for defaults. 1400 * 1401 * @return Returns a TypedArray holding an array of the attribute values. 1402 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1403 * when done with it. 1404 * 1405 * @see Resources#obtainAttributes 1406 * @see #obtainStyledAttributes(int[]) 1407 * @see #obtainStyledAttributes(int, int[]) 1408 */ 1409 public TypedArray obtainStyledAttributes(AttributeSet set, 1410 int[] attrs, int defStyleAttr, int defStyleRes) { 1411 final int len = attrs.length; 1412 final TypedArray array = TypedArray.obtain(Resources.this, len); 1413 1414 // XXX note that for now we only work with compiled XML files. 1415 // To support generic XML files we will need to manually parse 1416 // out the attributes from the XML file (applying type information 1417 // contained in the resources and such). 1418 final XmlBlock.Parser parser = (XmlBlock.Parser)set; 1419 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, 1420 parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices); 1421 1422 array.mTheme = this; 1423 array.mXml = parser; 1424 1425 if (false) { 1426 int[] data = array.mData; 1427 1428 System.out.println("Attributes:"); 1429 String s = " Attrs:"; 1430 int i; 1431 for (i=0; i<set.getAttributeCount(); i++) { 1432 s = s + " " + set.getAttributeName(i); 1433 int id = set.getAttributeNameResource(i); 1434 if (id != 0) { 1435 s = s + "(0x" + Integer.toHexString(id) + ")"; 1436 } 1437 s = s + "=" + set.getAttributeValue(i); 1438 } 1439 System.out.println(s); 1440 s = " Found:"; 1441 TypedValue value = new TypedValue(); 1442 for (i=0; i<attrs.length; i++) { 1443 int d = i*AssetManager.STYLE_NUM_ENTRIES; 1444 value.type = data[d+AssetManager.STYLE_TYPE]; 1445 value.data = data[d+AssetManager.STYLE_DATA]; 1446 value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; 1447 value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; 1448 s = s + " 0x" + Integer.toHexString(attrs[i]) 1449 + "=" + value; 1450 } 1451 System.out.println(s); 1452 } 1453 1454 return array; 1455 } 1456 1457 /** 1458 * Retrieve the values for a set of attributes in the Theme. The 1459 * contents of the typed array are ultimately filled in by 1460 * {@link Resources#getValue}. 1461 * 1462 * @param values The base set of attribute values, must be equal 1463 * in length to {@code attrs} or {@code null}. All values 1464 * must be of type {@link TypedValue#TYPE_ATTRIBUTE}. 1465 * @param attrs The desired attributes to be retrieved. 1466 * @param defStyleAttr An attribute in the current theme that contains a 1467 * reference to a style resource that supplies 1468 * defaults values for the TypedArray. Can be 1469 * 0 to not look for defaults. 1470 * @param defStyleRes A resource identifier of a style resource that 1471 * supplies default values for the TypedArray, 1472 * used only if defStyleAttr is 0 or can not be found 1473 * in the theme. Can be 0 to not look for defaults. 1474 * @return Returns a TypedArray holding an array of the attribute 1475 * values. Be sure to call {@link TypedArray#recycle()} 1476 * when done with it. 1477 * @hide 1478 */ 1479 public TypedArray resolveAttributes(int[] values, int[] attrs, 1480 int defStyleAttr, int defStyleRes) { 1481 final int len = attrs.length; 1482 if (values != null && len != values.length) { 1483 throw new IllegalArgumentException( 1484 "Base attribute values must be null or the same length as attrs"); 1485 } 1486 1487 final TypedArray array = TypedArray.obtain(Resources.this, len); 1488 AssetManager.resolveAttrs(mTheme, defStyleAttr, defStyleRes, 1489 values, attrs, array.mData, array.mIndices); 1490 array.mTheme = this; 1491 array.mXml = null; 1492 1493 return array; 1494 } 1495 1496 /** 1497 * Retrieve the value of an attribute in the Theme. The contents of 1498 * <var>outValue</var> are ultimately filled in by 1499 * {@link Resources#getValue}. 1500 * 1501 * @param resid The resource identifier of the desired theme 1502 * attribute. 1503 * @param outValue Filled in with the ultimate resource value supplied 1504 * by the attribute. 1505 * @param resolveRefs If true, resource references will be walked; if 1506 * false, <var>outValue</var> may be a 1507 * TYPE_REFERENCE. In either case, it will never 1508 * be a TYPE_ATTRIBUTE. 1509 * 1510 * @return boolean Returns true if the attribute was found and 1511 * <var>outValue</var> is valid, else false. 1512 */ 1513 public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { 1514 boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); 1515 if (false) { 1516 System.out.println( 1517 "resolveAttribute #" + Integer.toHexString(resid) 1518 + " got=" + got + ", type=0x" + Integer.toHexString(outValue.type) 1519 + ", data=0x" + Integer.toHexString(outValue.data)); 1520 } 1521 return got; 1522 } 1523 1524 /** 1525 * Returns the resources to which this theme belongs. 1526 * 1527 * @return Resources to which this theme belongs. 1528 */ 1529 public Resources getResources() { 1530 return Resources.this; 1531 } 1532 1533 /** 1534 * Return a drawable object associated with a particular resource ID 1535 * and styled for the Theme. 1536 * 1537 * @param id The desired resource identifier, as generated by the aapt 1538 * tool. This integer encodes the package, type, and resource 1539 * entry. The value 0 is an invalid identifier. 1540 * @return Drawable An object that can be used to draw this resource. 1541 * @throws NotFoundException Throws NotFoundException if the given ID 1542 * does not exist. 1543 */ 1544 public Drawable getDrawable(int id) throws NotFoundException { 1545 return Resources.this.getDrawable(id, this); 1546 } 1547 1548 /** 1549 * Print contents of this theme out to the log. For debugging only. 1550 * 1551 * @param priority The log priority to use. 1552 * @param tag The log tag to use. 1553 * @param prefix Text to prefix each line printed. 1554 */ 1555 public void dump(int priority, String tag, String prefix) { 1556 AssetManager.dumpTheme(mTheme, priority, tag, prefix); 1557 } 1558 1559 @Override 1560 protected void finalize() throws Throwable { 1561 super.finalize(); 1562 mAssets.releaseTheme(mTheme); 1563 } 1564 1565 /*package*/ boolean canCacheDrawables() { 1566 return mHasStyle && mThemeResId != 0; 1567 } 1568 1569 /*package*/ Theme() { 1570 mAssets = Resources.this.mAssets; 1571 mTheme = mAssets.createTheme(); 1572 } 1573 1574 @SuppressWarnings("hiding") 1575 private final AssetManager mAssets; 1576 private final long mTheme; 1577 1578 /** 1579 * Resource identifier for the theme. If multiple styles have been 1580 * applied to this theme, this value will be 0 (invalid). 1581 */ 1582 private int mThemeResId = 0; 1583 private boolean mHasStyle = false; 1584 } 1585 1586 /** 1587 * Generate a new Theme object for this set of Resources. It initially 1588 * starts out empty. 1589 * 1590 * @return Theme The newly created Theme container. 1591 */ 1592 public final Theme newTheme() { 1593 return new Theme(); 1594 } 1595 1596 /** 1597 * Retrieve a set of basic attribute values from an AttributeSet, not 1598 * performing styling of them using a theme and/or style resources. 1599 * 1600 * @param set The current attribute values to retrieve. 1601 * @param attrs The specific attributes to be retrieved. 1602 * @return Returns a TypedArray holding an array of the attribute values. 1603 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1604 * when done with it. 1605 * 1606 * @see Theme#obtainStyledAttributes(AttributeSet, int[], int, int) 1607 */ 1608 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { 1609 int len = attrs.length; 1610 TypedArray array = TypedArray.obtain(this, len); 1611 1612 // XXX note that for now we only work with compiled XML files. 1613 // To support generic XML files we will need to manually parse 1614 // out the attributes from the XML file (applying type information 1615 // contained in the resources and such). 1616 XmlBlock.Parser parser = (XmlBlock.Parser)set; 1617 mAssets.retrieveAttributes(parser.mParseState, attrs, 1618 array.mData, array.mIndices); 1619 1620 array.mXml = parser; 1621 1622 return array; 1623 } 1624 1625 /** 1626 * Store the newly updated configuration. 1627 */ 1628 public void updateConfiguration(Configuration config, 1629 DisplayMetrics metrics) { 1630 updateConfiguration(config, metrics, null); 1631 } 1632 1633 /** 1634 * @hide 1635 */ 1636 public void updateConfiguration(Configuration config, 1637 DisplayMetrics metrics, CompatibilityInfo compat) { 1638 synchronized (mAccessLock) { 1639 if (false) { 1640 Slog.i(TAG, "**** Updating config of " + this + ": old config is " 1641 + mConfiguration + " old compat is " + mCompatibilityInfo); 1642 Slog.i(TAG, "**** Updating config of " + this + ": new config is " 1643 + config + " new compat is " + compat); 1644 } 1645 if (compat != null) { 1646 mCompatibilityInfo = compat; 1647 } 1648 if (metrics != null) { 1649 mMetrics.setTo(metrics); 1650 } 1651 // NOTE: We should re-arrange this code to create a Display 1652 // with the CompatibilityInfo that is used everywhere we deal 1653 // with the display in relation to this app, rather than 1654 // doing the conversion here. This impl should be okay because 1655 // we make sure to return a compatible display in the places 1656 // where there are public APIs to retrieve the display... but 1657 // it would be cleaner and more maintainble to just be 1658 // consistently dealing with a compatible display everywhere in 1659 // the framework. 1660 mCompatibilityInfo.applyToDisplayMetrics(mMetrics); 1661 1662 int configChanges = 0xfffffff; 1663 if (config != null) { 1664 mTmpConfig.setTo(config); 1665 int density = config.densityDpi; 1666 if (density == Configuration.DENSITY_DPI_UNDEFINED) { 1667 density = mMetrics.noncompatDensityDpi; 1668 } 1669 1670 mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); 1671 1672 if (mTmpConfig.locale == null) { 1673 mTmpConfig.locale = Locale.getDefault(); 1674 mTmpConfig.setLayoutDirection(mTmpConfig.locale); 1675 } 1676 configChanges = mConfiguration.updateFrom(mTmpConfig); 1677 configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); 1678 } 1679 if (mConfiguration.locale == null) { 1680 mConfiguration.locale = Locale.getDefault(); 1681 mConfiguration.setLayoutDirection(mConfiguration.locale); 1682 } 1683 if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { 1684 mMetrics.densityDpi = mConfiguration.densityDpi; 1685 mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; 1686 } 1687 mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; 1688 1689 String locale = null; 1690 if (mConfiguration.locale != null) { 1691 locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale)); 1692 } 1693 int width, height; 1694 if (mMetrics.widthPixels >= mMetrics.heightPixels) { 1695 width = mMetrics.widthPixels; 1696 height = mMetrics.heightPixels; 1697 } else { 1698 //noinspection SuspiciousNameCombination 1699 width = mMetrics.heightPixels; 1700 //noinspection SuspiciousNameCombination 1701 height = mMetrics.widthPixels; 1702 } 1703 int keyboardHidden = mConfiguration.keyboardHidden; 1704 if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO 1705 && mConfiguration.hardKeyboardHidden 1706 == Configuration.HARDKEYBOARDHIDDEN_YES) { 1707 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; 1708 } 1709 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, 1710 locale, mConfiguration.orientation, 1711 mConfiguration.touchscreen, 1712 mConfiguration.densityDpi, mConfiguration.keyboard, 1713 keyboardHidden, mConfiguration.navigation, width, height, 1714 mConfiguration.smallestScreenWidthDp, 1715 mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, 1716 mConfiguration.screenLayout, mConfiguration.uiMode, 1717 Build.VERSION.RESOURCES_SDK_INT); 1718 1719 if (DEBUG_CONFIG) { 1720 Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration 1721 + " final compat is " + mCompatibilityInfo); 1722 } 1723 1724 clearDrawableCachesLocked(mDrawableCache, configChanges); 1725 clearDrawableCachesLocked(mColorDrawableCache, configChanges); 1726 1727 mColorStateListCache.clear(); 1728 1729 flushLayoutCache(); 1730 } 1731 synchronized (sSync) { 1732 if (mPluralRule != null) { 1733 mPluralRule = NativePluralRules.forLocale(config.locale); 1734 } 1735 } 1736 } 1737 1738 private void clearDrawableCachesLocked( 1739 ThemedCaches<ConstantState> caches, int configChanges) { 1740 final int N = caches.size(); 1741 for (int i = 0; i < N; i++) { 1742 clearDrawableCacheLocked(caches.valueAt(i), configChanges); 1743 } 1744 } 1745 1746 private void clearDrawableCacheLocked( 1747 LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) { 1748 if (DEBUG_CONFIG) { 1749 Log.d(TAG, "Cleaning up drawables config changes: 0x" 1750 + Integer.toHexString(configChanges)); 1751 } 1752 final int N = cache.size(); 1753 for (int i = 0; i < N; i++) { 1754 final WeakReference<ConstantState> ref = cache.valueAt(i); 1755 if (ref != null) { 1756 final ConstantState cs = ref.get(); 1757 if (cs != null) { 1758 if (Configuration.needNewResources( 1759 configChanges, cs.getChangingConfigurations())) { 1760 if (DEBUG_CONFIG) { 1761 Log.d(TAG, "FLUSHING #0x" 1762 + Long.toHexString(mDrawableCache.keyAt(i)) 1763 + " / " + cs + " with changes: 0x" 1764 + Integer.toHexString(cs.getChangingConfigurations())); 1765 } 1766 cache.setValueAt(i, null); 1767 } else if (DEBUG_CONFIG) { 1768 Log.d(TAG, "(Keeping #0x" 1769 + Long.toHexString(cache.keyAt(i)) 1770 + " / " + cs + " with changes: 0x" 1771 + Integer.toHexString(cs.getChangingConfigurations()) 1772 + ")"); 1773 } 1774 } 1775 } 1776 } 1777 } 1778 1779 // Locale.toLanguageTag() is not available in Java6. LayoutLib overrides 1780 // this method to enable users to use Java6. 1781 private String localeToLanguageTag(Locale locale) { 1782 return locale.toLanguageTag(); 1783 } 1784 1785 /** 1786 * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) 1787 * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. 1788 * 1789 * All released versions of android prior to "L" used the deprecated language 1790 * tags, so we will need to support them for backwards compatibility. 1791 * 1792 * Note that this conversion needs to take place *after* the call to 1793 * {@code toLanguageTag} because that will convert all the deprecated codes to 1794 * the new ones, even if they're set manually. 1795 */ 1796 private static String adjustLanguageTag(String languageTag) { 1797 final int separator = languageTag.indexOf('-'); 1798 final String language; 1799 final String remainder; 1800 1801 if (separator == -1) { 1802 language = languageTag; 1803 remainder = ""; 1804 } else { 1805 language = languageTag.substring(0, separator); 1806 remainder = languageTag.substring(separator); 1807 } 1808 1809 if ("id".equals(language)) { 1810 return "in" + remainder; 1811 } else if ("yi".equals(language)) { 1812 return "ji" + remainder; 1813 } else if ("he".equals(language)) { 1814 return "iw" + remainder; 1815 } else { 1816 return languageTag; 1817 } 1818 } 1819 1820 /** 1821 * Update the system resources configuration if they have previously 1822 * been initialized. 1823 * 1824 * @hide 1825 */ 1826 public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics, 1827 CompatibilityInfo compat) { 1828 if (mSystem != null) { 1829 mSystem.updateConfiguration(config, metrics, compat); 1830 //Log.i(TAG, "Updated system resources " + mSystem 1831 // + ": " + mSystem.getConfiguration()); 1832 } 1833 } 1834 1835 /** 1836 * Return the current display metrics that are in effect for this resource 1837 * object. The returned object should be treated as read-only. 1838 * 1839 * @return The resource's current display metrics. 1840 */ 1841 public DisplayMetrics getDisplayMetrics() { 1842 if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels 1843 + "x" + mMetrics.heightPixels + " " + mMetrics.density); 1844 return mMetrics; 1845 } 1846 1847 /** 1848 * Return the current configuration that is in effect for this resource 1849 * object. The returned object should be treated as read-only. 1850 * 1851 * @return The resource's current configuration. 1852 */ 1853 public Configuration getConfiguration() { 1854 return mConfiguration; 1855 } 1856 1857 /** 1858 * Return the compatibility mode information for the application. 1859 * The returned object should be treated as read-only. 1860 * 1861 * @return compatibility info. 1862 * @hide 1863 */ 1864 public CompatibilityInfo getCompatibilityInfo() { 1865 return mCompatibilityInfo; 1866 } 1867 1868 /** 1869 * This is just for testing. 1870 * @hide 1871 */ 1872 public void setCompatibilityInfo(CompatibilityInfo ci) { 1873 if (ci != null) { 1874 mCompatibilityInfo = ci; 1875 updateConfiguration(mConfiguration, mMetrics); 1876 } 1877 } 1878 1879 /** 1880 * Return a resource identifier for the given resource name. A fully 1881 * qualified resource name is of the form "package:type/entry". The first 1882 * two components (package and type) are optional if defType and 1883 * defPackage, respectively, are specified here. 1884 * 1885 * <p>Note: use of this function is discouraged. It is much more 1886 * efficient to retrieve resources by identifier than by name. 1887 * 1888 * @param name The name of the desired resource. 1889 * @param defType Optional default resource type to find, if "type/" is 1890 * not included in the name. Can be null to require an 1891 * explicit type. 1892 * @param defPackage Optional default package to find, if "package:" is 1893 * not included in the name. Can be null to require an 1894 * explicit package. 1895 * 1896 * @return int The associated resource identifier. Returns 0 if no such 1897 * resource was found. (0 is not a valid resource ID.) 1898 */ 1899 public int getIdentifier(String name, String defType, String defPackage) { 1900 if (name == null) { 1901 throw new NullPointerException("name is null"); 1902 } 1903 try { 1904 return Integer.parseInt(name); 1905 } catch (Exception e) { 1906 // Ignore 1907 } 1908 return mAssets.getResourceIdentifier(name, defType, defPackage); 1909 } 1910 1911 /** 1912 * Return true if given resource identifier includes a package. 1913 * 1914 * @hide 1915 */ 1916 public static boolean resourceHasPackage(int resid) { 1917 return (resid >>> 24) != 0; 1918 } 1919 1920 /** 1921 * Return the full name for a given resource identifier. This name is 1922 * a single string of the form "package:type/entry". 1923 * 1924 * @param resid The resource identifier whose name is to be retrieved. 1925 * 1926 * @return A string holding the name of the resource. 1927 * 1928 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1929 * 1930 * @see #getResourcePackageName 1931 * @see #getResourceTypeName 1932 * @see #getResourceEntryName 1933 */ 1934 public String getResourceName(int resid) throws NotFoundException { 1935 String str = mAssets.getResourceName(resid); 1936 if (str != null) return str; 1937 throw new NotFoundException("Unable to find resource ID #0x" 1938 + Integer.toHexString(resid)); 1939 } 1940 1941 /** 1942 * Return the package name for a given resource identifier. 1943 * 1944 * @param resid The resource identifier whose package name is to be 1945 * retrieved. 1946 * 1947 * @return A string holding the package name of the resource. 1948 * 1949 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1950 * 1951 * @see #getResourceName 1952 */ 1953 public String getResourcePackageName(int resid) throws NotFoundException { 1954 String str = mAssets.getResourcePackageName(resid); 1955 if (str != null) return str; 1956 throw new NotFoundException("Unable to find resource ID #0x" 1957 + Integer.toHexString(resid)); 1958 } 1959 1960 /** 1961 * Return the type name for a given resource identifier. 1962 * 1963 * @param resid The resource identifier whose type name is to be 1964 * retrieved. 1965 * 1966 * @return A string holding the type name of the resource. 1967 * 1968 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1969 * 1970 * @see #getResourceName 1971 */ 1972 public String getResourceTypeName(int resid) throws NotFoundException { 1973 String str = mAssets.getResourceTypeName(resid); 1974 if (str != null) return str; 1975 throw new NotFoundException("Unable to find resource ID #0x" 1976 + Integer.toHexString(resid)); 1977 } 1978 1979 /** 1980 * Return the entry name for a given resource identifier. 1981 * 1982 * @param resid The resource identifier whose entry name is to be 1983 * retrieved. 1984 * 1985 * @return A string holding the entry name of the resource. 1986 * 1987 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1988 * 1989 * @see #getResourceName 1990 */ 1991 public String getResourceEntryName(int resid) throws NotFoundException { 1992 String str = mAssets.getResourceEntryName(resid); 1993 if (str != null) return str; 1994 throw new NotFoundException("Unable to find resource ID #0x" 1995 + Integer.toHexString(resid)); 1996 } 1997 1998 /** 1999 * Parse a series of {@link android.R.styleable#Extra <extra>} tags from 2000 * an XML file. You call this when you are at the parent tag of the 2001 * extra tags, and it will return once all of the child tags have been parsed. 2002 * This will call {@link #parseBundleExtra} for each extra tag encountered. 2003 * 2004 * @param parser The parser from which to retrieve the extras. 2005 * @param outBundle A Bundle in which to place all parsed extras. 2006 * @throws XmlPullParserException 2007 * @throws IOException 2008 */ 2009 public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) 2010 throws XmlPullParserException, IOException { 2011 int outerDepth = parser.getDepth(); 2012 int type; 2013 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 2014 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 2015 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 2016 continue; 2017 } 2018 2019 String nodeName = parser.getName(); 2020 if (nodeName.equals("extra")) { 2021 parseBundleExtra("extra", parser, outBundle); 2022 XmlUtils.skipCurrentTag(parser); 2023 2024 } else { 2025 XmlUtils.skipCurrentTag(parser); 2026 } 2027 } 2028 } 2029 2030 /** 2031 * Parse a name/value pair out of an XML tag holding that data. The 2032 * AttributeSet must be holding the data defined by 2033 * {@link android.R.styleable#Extra}. The following value types are supported: 2034 * <ul> 2035 * <li> {@link TypedValue#TYPE_STRING}: 2036 * {@link Bundle#putCharSequence Bundle.putCharSequence()} 2037 * <li> {@link TypedValue#TYPE_INT_BOOLEAN}: 2038 * {@link Bundle#putCharSequence Bundle.putBoolean()} 2039 * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}: 2040 * {@link Bundle#putCharSequence Bundle.putBoolean()} 2041 * <li> {@link TypedValue#TYPE_FLOAT}: 2042 * {@link Bundle#putCharSequence Bundle.putFloat()} 2043 * </ul> 2044 * 2045 * @param tagName The name of the tag these attributes come from; this is 2046 * only used for reporting error messages. 2047 * @param attrs The attributes from which to retrieve the name/value pair. 2048 * @param outBundle The Bundle in which to place the parsed value. 2049 * @throws XmlPullParserException If the attributes are not valid. 2050 */ 2051 public void parseBundleExtra(String tagName, AttributeSet attrs, 2052 Bundle outBundle) throws XmlPullParserException { 2053 TypedArray sa = obtainAttributes(attrs, 2054 com.android.internal.R.styleable.Extra); 2055 2056 String name = sa.getString( 2057 com.android.internal.R.styleable.Extra_name); 2058 if (name == null) { 2059 sa.recycle(); 2060 throw new XmlPullParserException("<" + tagName 2061 + "> requires an android:name attribute at " 2062 + attrs.getPositionDescription()); 2063 } 2064 2065 TypedValue v = sa.peekValue( 2066 com.android.internal.R.styleable.Extra_value); 2067 if (v != null) { 2068 if (v.type == TypedValue.TYPE_STRING) { 2069 CharSequence cs = v.coerceToString(); 2070 outBundle.putCharSequence(name, cs); 2071 } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { 2072 outBundle.putBoolean(name, v.data != 0); 2073 } else if (v.type >= TypedValue.TYPE_FIRST_INT 2074 && v.type <= TypedValue.TYPE_LAST_INT) { 2075 outBundle.putInt(name, v.data); 2076 } else if (v.type == TypedValue.TYPE_FLOAT) { 2077 outBundle.putFloat(name, v.getFloat()); 2078 } else { 2079 sa.recycle(); 2080 throw new XmlPullParserException("<" + tagName 2081 + "> only supports string, integer, float, color, and boolean at " 2082 + attrs.getPositionDescription()); 2083 } 2084 } else { 2085 sa.recycle(); 2086 throw new XmlPullParserException("<" + tagName 2087 + "> requires an android:value or android:resource attribute at " 2088 + attrs.getPositionDescription()); 2089 } 2090 2091 sa.recycle(); 2092 } 2093 2094 /** 2095 * Retrieve underlying AssetManager storage for these resources. 2096 */ 2097 public final AssetManager getAssets() { 2098 return mAssets; 2099 } 2100 2101 /** 2102 * Call this to remove all cached loaded layout resources from the 2103 * Resources object. Only intended for use with performance testing 2104 * tools. 2105 */ 2106 public final void flushLayoutCache() { 2107 synchronized (mCachedXmlBlockIds) { 2108 // First see if this block is in our cache. 2109 final int num = mCachedXmlBlockIds.length; 2110 for (int i=0; i<num; i++) { 2111 mCachedXmlBlockIds[i] = -0; 2112 XmlBlock oldBlock = mCachedXmlBlocks[i]; 2113 if (oldBlock != null) { 2114 oldBlock.close(); 2115 } 2116 mCachedXmlBlocks[i] = null; 2117 } 2118 } 2119 } 2120 2121 /** 2122 * Start preloading of resource data using this Resources object. Only 2123 * for use by the zygote process for loading common system resources. 2124 * {@hide} 2125 */ 2126 public final void startPreloading() { 2127 synchronized (sSync) { 2128 if (sPreloaded) { 2129 throw new IllegalStateException("Resources already preloaded"); 2130 } 2131 sPreloaded = true; 2132 mPreloading = true; 2133 sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE; 2134 mConfiguration.densityDpi = sPreloadedDensity; 2135 updateConfiguration(null, null); 2136 } 2137 } 2138 2139 /** 2140 * Called by zygote when it is done preloading resources, to change back 2141 * to normal Resources operation. 2142 */ 2143 public final void finishPreloading() { 2144 if (mPreloading) { 2145 mPreloading = false; 2146 flushLayoutCache(); 2147 } 2148 } 2149 2150 /** 2151 * @hide 2152 */ 2153 public LongSparseArray<ConstantState> getPreloadedDrawables() { 2154 return sPreloadedDrawables[0]; 2155 } 2156 2157 private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying, 2158 int resourceId, String name) { 2159 // We allow preloading of resources even if they vary by font scale (which 2160 // doesn't impact resource selection) or density (which we handle specially by 2161 // simply turning off all preloading), as well as any other configs specified 2162 // by the caller. 2163 if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE | 2164 ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) { 2165 String resName; 2166 try { 2167 resName = getResourceName(resourceId); 2168 } catch (NotFoundException e) { 2169 resName = "?"; 2170 } 2171 // This should never happen in production, so we should log a 2172 // warning even if we're not debugging. 2173 Log.w(TAG, "Preloaded " + name + " resource #0x" 2174 + Integer.toHexString(resourceId) 2175 + " (" + resName + ") that varies with configuration!!"); 2176 return false; 2177 } 2178 if (TRACE_FOR_PRELOAD) { 2179 String resName; 2180 try { 2181 resName = getResourceName(resourceId); 2182 } catch (NotFoundException e) { 2183 resName = "?"; 2184 } 2185 Log.w(TAG, "Preloading " + name + " resource #0x" 2186 + Integer.toHexString(resourceId) 2187 + " (" + resName + ")"); 2188 } 2189 return true; 2190 } 2191 2192 /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { 2193 if (TRACE_FOR_PRELOAD) { 2194 // Log only framework resources 2195 if ((id >>> 24) == 0x1) { 2196 final String name = getResourceName(id); 2197 if (name != null) { 2198 Log.d("PreloadDrawable", name); 2199 } 2200 } 2201 } 2202 2203 final boolean isColorDrawable; 2204 final ThemedCaches<ConstantState> caches; 2205 final long key; 2206 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT 2207 && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 2208 isColorDrawable = true; 2209 caches = mColorDrawableCache; 2210 key = value.data; 2211 } else { 2212 isColorDrawable = false; 2213 caches = mDrawableCache; 2214 key = (((long) value.assetCookie) << 32) | value.data; 2215 } 2216 2217 // First, check whether we have a cached version of this drawable 2218 // that's valid for the specified theme. This may apply a theme to a 2219 // cached drawable that has themeable attributes but was not previously 2220 // themed. 2221 if (!mPreloading) { 2222 final Drawable cachedDrawable = getCachedDrawable(caches, key, theme); 2223 if (cachedDrawable != null) { 2224 return cachedDrawable; 2225 } 2226 } 2227 2228 // Next, check preloaded drawables. These are unthemed but may have 2229 // themeable attributes. 2230 final ConstantState cs; 2231 if (isColorDrawable) { 2232 cs = sPreloadedColorDrawables.get(key); 2233 } else { 2234 cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); 2235 } 2236 2237 final Drawable dr; 2238 if (cs != null) { 2239 dr = cs.newDrawable(this, theme); 2240 } else if (isColorDrawable) { 2241 dr = new ColorDrawable(value.data); 2242 } else { 2243 dr = loadDrawableForCookie(value, id, theme); 2244 } 2245 2246 // If we were able to obtain a drawable, attempt to place it in the 2247 // appropriate cache (e.g. no theme, themed, themeable). 2248 if (dr != null) { 2249 dr.setChangingConfigurations(value.changingConfigurations); 2250 cacheDrawable(value, theme, isColorDrawable, caches, key, dr); 2251 } 2252 2253 return dr; 2254 } 2255 2256 private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable, 2257 ThemedCaches<ConstantState> caches, long key, Drawable dr) { 2258 final ConstantState cs = dr.getConstantState(); 2259 if (cs == null) { 2260 return; 2261 } 2262 2263 // Abort if the drawable is themed, but the theme cannot be cached. 2264 if (dr.canApplyTheme() && theme != null && !theme.canCacheDrawables()) { 2265 return; 2266 } 2267 2268 if (mPreloading) { 2269 // Preloaded drawables never have a theme, but may be themeable. 2270 final int changingConfigs = cs.getChangingConfigurations(); 2271 if (isColorDrawable) { 2272 if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) { 2273 sPreloadedColorDrawables.put(key, cs); 2274 } 2275 } else { 2276 if (verifyPreloadConfig( 2277 changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) { 2278 if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) { 2279 // If this resource does not vary based on layout direction, 2280 // we can put it in all of the preload maps. 2281 sPreloadedDrawables[0].put(key, cs); 2282 sPreloadedDrawables[1].put(key, cs); 2283 } else { 2284 // Otherwise, only in the layout dir we loaded it for. 2285 sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs); 2286 } 2287 } 2288 } 2289 } else { 2290 synchronized (mAccessLock) { 2291 final LongSparseArray<WeakReference<ConstantState>> themedCache; 2292 if (!dr.canApplyTheme()) { 2293 themedCache = caches.getUnthemed(true); 2294 } else { 2295 themedCache = caches.getOrCreate(theme == null ? 0 : theme.mThemeResId); 2296 } 2297 themedCache.put(key, new WeakReference<ConstantState>(cs)); 2298 } 2299 } 2300 } 2301 2302 /** 2303 * Loads a drawable from XML or resources stream. 2304 */ 2305 private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { 2306 if (value.string == null) { 2307 throw new NotFoundException( 2308 "Resource is not a Drawable (color or path): " + value); 2309 } 2310 2311 final String file = value.string.toString(); 2312 2313 if (TRACE_FOR_MISS_PRELOAD) { 2314 // Log only framework resources 2315 if ((id >>> 24) == 0x1) { 2316 final String name = getResourceName(id); 2317 if (name != null) { 2318 Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id) 2319 + ": " + name + " at " + file); 2320 } 2321 } 2322 } 2323 2324 if (DEBUG_LOAD) { 2325 Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file); 2326 } 2327 2328 final Drawable dr; 2329 2330 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 2331 try { 2332 if (file.endsWith(".xml")) { 2333 final XmlResourceParser rp = loadXmlResourceParser( 2334 file, id, value.assetCookie, "drawable"); 2335 dr = Drawable.createFromXmlThemed(this, rp, theme); 2336 rp.close(); 2337 } else { 2338 final InputStream is = mAssets.openNonAsset( 2339 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 2340 dr = Drawable.createFromResourceStreamThemed(this, value, is, file, null, theme); 2341 is.close(); 2342 } 2343 } catch (Exception e) { 2344 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 2345 final NotFoundException rnf = new NotFoundException( 2346 "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); 2347 rnf.initCause(e); 2348 throw rnf; 2349 } 2350 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 2351 2352 return dr; 2353 } 2354 2355 private Drawable getCachedDrawable(ThemedCaches<ConstantState> caches, long key, Theme theme) { 2356 synchronized (mAccessLock) { 2357 // First, check for a matching unthemed drawable. 2358 final LongSparseArray<WeakReference<ConstantState>> unthemed = caches.getUnthemed(false); 2359 if (unthemed != null) { 2360 final Drawable unthemedDrawable = getCachedDrawableLocked(unthemed, key); 2361 if (unthemedDrawable != null) { 2362 return unthemedDrawable; 2363 } 2364 } 2365 2366 final boolean themeCannotCache = theme != null && !theme.canCacheDrawables(); 2367 if (themeCannotCache) { 2368 return null; 2369 } 2370 2371 // Next, check for a matching themed drawable. 2372 final int themeKey = theme != null ? theme.mThemeResId : 0; 2373 final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); 2374 if (themedCache != null) { 2375 final Drawable themedDrawable = getCachedDrawableLocked(themedCache, key); 2376 if (themedDrawable != null) { 2377 return themedDrawable; 2378 } 2379 } 2380 2381 // No cached drawable, we'll need to create a new one. 2382 return null; 2383 } 2384 } 2385 2386 private ConstantState getConstantStateLocked( 2387 LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { 2388 final WeakReference<ConstantState> wr = drawableCache.get(key); 2389 if (wr != null) { // we have the key 2390 final ConstantState entry = wr.get(); 2391 if (entry != null) { 2392 //Log.i(TAG, "Returning cached drawable @ #" + 2393 // Integer.toHexString(((Integer)key).intValue()) 2394 // + " in " + this + ": " + entry); 2395 return entry; 2396 } else { // our entry has been purged 2397 drawableCache.delete(key); 2398 } 2399 } 2400 return null; 2401 } 2402 2403 private Drawable getCachedDrawableLocked( 2404 LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { 2405 final ConstantState entry = getConstantStateLocked(drawableCache, key); 2406 if (entry != null) { 2407 return entry.newDrawable(this); 2408 } 2409 return null; 2410 } 2411 2412 /*package*/ ColorStateList loadColorStateList(TypedValue value, int id) 2413 throws NotFoundException { 2414 if (TRACE_FOR_PRELOAD) { 2415 // Log only framework resources 2416 if ((id >>> 24) == 0x1) { 2417 final String name = getResourceName(id); 2418 if (name != null) android.util.Log.d("PreloadColorStateList", name); 2419 } 2420 } 2421 2422 final long key = (((long) value.assetCookie) << 32) | value.data; 2423 2424 ColorStateList csl; 2425 2426 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 2427 value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 2428 2429 csl = sPreloadedColorStateLists.get(key); 2430 if (csl != null) { 2431 return csl; 2432 } 2433 2434 csl = ColorStateList.valueOf(value.data); 2435 if (mPreloading) { 2436 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 2437 "color")) { 2438 sPreloadedColorStateLists.put(key, csl); 2439 } 2440 } 2441 2442 return csl; 2443 } 2444 2445 csl = getCachedColorStateList(key); 2446 if (csl != null) { 2447 return csl; 2448 } 2449 2450 csl = sPreloadedColorStateLists.get(key); 2451 if (csl != null) { 2452 return csl; 2453 } 2454 2455 if (value.string == null) { 2456 throw new NotFoundException( 2457 "Resource is not a ColorStateList (color or path): " + value); 2458 } 2459 2460 final String file = value.string.toString(); 2461 2462 if (file.endsWith(".xml")) { 2463 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); 2464 try { 2465 final XmlResourceParser rp = loadXmlResourceParser( 2466 file, id, value.assetCookie, "colorstatelist"); 2467 csl = ColorStateList.createFromXml(this, rp); 2468 rp.close(); 2469 } catch (Exception e) { 2470 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 2471 NotFoundException rnf = new NotFoundException( 2472 "File " + file + " from color state list resource ID #0x" 2473 + Integer.toHexString(id)); 2474 rnf.initCause(e); 2475 throw rnf; 2476 } 2477 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 2478 } else { 2479 throw new NotFoundException( 2480 "File " + file + " from drawable resource ID #0x" 2481 + Integer.toHexString(id) + ": .xml extension required"); 2482 } 2483 2484 if (csl != null) { 2485 if (mPreloading) { 2486 if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, 2487 "color")) { 2488 sPreloadedColorStateLists.put(key, csl); 2489 } 2490 } else { 2491 synchronized (mAccessLock) { 2492 //Log.i(TAG, "Saving cached color state list @ #" + 2493 // Integer.toHexString(key.intValue()) 2494 // + " in " + this + ": " + csl); 2495 mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl)); 2496 } 2497 } 2498 } 2499 2500 return csl; 2501 } 2502 2503 private ColorStateList getCachedColorStateList(long key) { 2504 synchronized (mAccessLock) { 2505 WeakReference<ColorStateList> wr = mColorStateListCache.get(key); 2506 if (wr != null) { // we have the key 2507 ColorStateList entry = wr.get(); 2508 if (entry != null) { 2509 //Log.i(TAG, "Returning cached color state list @ #" + 2510 // Integer.toHexString(((Integer)key).intValue()) 2511 // + " in " + this + ": " + entry); 2512 return entry; 2513 } else { // our entry has been purged 2514 mColorStateListCache.delete(key); 2515 } 2516 } 2517 } 2518 return null; 2519 } 2520 2521 /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type) 2522 throws NotFoundException { 2523 synchronized (mAccessLock) { 2524 TypedValue value = mTmpValue; 2525 if (value == null) { 2526 mTmpValue = value = new TypedValue(); 2527 } 2528 getValue(id, value, true); 2529 if (value.type == TypedValue.TYPE_STRING) { 2530 return loadXmlResourceParser(value.string.toString(), id, 2531 value.assetCookie, type); 2532 } 2533 throw new NotFoundException( 2534 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 2535 + Integer.toHexString(value.type) + " is not valid"); 2536 } 2537 } 2538 2539 /*package*/ XmlResourceParser loadXmlResourceParser(String file, int id, 2540 int assetCookie, String type) throws NotFoundException { 2541 if (id != 0) { 2542 try { 2543 // These may be compiled... 2544 synchronized (mCachedXmlBlockIds) { 2545 // First see if this block is in our cache. 2546 final int num = mCachedXmlBlockIds.length; 2547 for (int i=0; i<num; i++) { 2548 if (mCachedXmlBlockIds[i] == id) { 2549 //System.out.println("**** REUSING XML BLOCK! id=" 2550 // + id + ", index=" + i); 2551 return mCachedXmlBlocks[i].newParser(); 2552 } 2553 } 2554 2555 // Not in the cache, create a new block and put it at 2556 // the next slot in the cache. 2557 XmlBlock block = mAssets.openXmlBlockAsset( 2558 assetCookie, file); 2559 if (block != null) { 2560 int pos = mLastCachedXmlBlockIndex+1; 2561 if (pos >= num) pos = 0; 2562 mLastCachedXmlBlockIndex = pos; 2563 XmlBlock oldBlock = mCachedXmlBlocks[pos]; 2564 if (oldBlock != null) { 2565 oldBlock.close(); 2566 } 2567 mCachedXmlBlockIds[pos] = id; 2568 mCachedXmlBlocks[pos] = block; 2569 //System.out.println("**** CACHING NEW XML BLOCK! id=" 2570 // + id + ", index=" + pos); 2571 return block.newParser(); 2572 } 2573 } 2574 } catch (Exception e) { 2575 NotFoundException rnf = new NotFoundException( 2576 "File " + file + " from xml type " + type + " resource ID #0x" 2577 + Integer.toHexString(id)); 2578 rnf.initCause(e); 2579 throw rnf; 2580 } 2581 } 2582 2583 throw new NotFoundException( 2584 "File " + file + " from xml type " + type + " resource ID #0x" 2585 + Integer.toHexString(id)); 2586 } 2587 2588 /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) { 2589 synchronized (mAccessLock) { 2590 final TypedArray cached = mCachedStyledAttributes; 2591 if (cached == null || cached.mData.length < attrs.mData.length) { 2592 mCachedStyledAttributes = attrs; 2593 } 2594 } 2595 } 2596 2597 private Resources() { 2598 mAssets = AssetManager.getSystem(); 2599 // NOTE: Intentionally leaving this uninitialized (all values set 2600 // to zero), so that anyone who tries to do something that requires 2601 // metrics will get a very wrong value. 2602 mConfiguration.setToDefaults(); 2603 mMetrics.setToDefaults(); 2604 updateConfiguration(null, null); 2605 mAssets.ensureStringBlocks(); 2606 } 2607 2608 static class ThemedCaches<T> extends SparseArray<LongSparseArray<WeakReference<T>>> { 2609 private LongSparseArray<WeakReference<T>> mUnthemed = null; 2610 2611 /** 2612 * Returns the cache of drawables with no themeable attributes. 2613 */ 2614 public LongSparseArray<WeakReference<T>> getUnthemed(boolean autoCreate) { 2615 if (mUnthemed == null && autoCreate) { 2616 mUnthemed = new LongSparseArray<WeakReference<T>>(1); 2617 } 2618 return mUnthemed; 2619 } 2620 2621 /** 2622 * Returns the cache of drawables styled for the specified theme. 2623 * <p> 2624 * Drawables that have themeable attributes but were loaded without 2625 * specifying a theme are cached at themeResId = 0. 2626 */ 2627 public LongSparseArray<WeakReference<T>> getOrCreate(int themeResId) { 2628 LongSparseArray<WeakReference<T>> result = get(themeResId); 2629 if (result == null) { 2630 result = new LongSparseArray<WeakReference<T>>(1); 2631 put(themeResId, result); 2632 } 2633 return result; 2634 } 2635 } 2636} 2637