Resources.java revision c7d9d2790f0b7a036d70bac2f100b42790ff0979
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 19 20import com.android.internal.util.XmlUtils; 21 22import org.xmlpull.v1.XmlPullParser; 23import org.xmlpull.v1.XmlPullParserException; 24 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.SystemProperties; 32import android.util.AttributeSet; 33import android.util.DisplayMetrics; 34import android.util.Log; 35import android.util.SparseArray; 36import android.util.TypedValue; 37import android.util.LongSparseArray; 38import android.view.Display; 39 40import java.io.IOException; 41import java.io.InputStream; 42import java.lang.ref.WeakReference; 43import java.util.Locale; 44 45/** 46 * Class for accessing an application's resources. This sits on top of the 47 * asset manager of the application (accessible through getAssets()) and 48 * provides a higher-level API for getting typed data from the assets. 49 */ 50public class Resources { 51 static final String TAG = "Resources"; 52 private static final boolean DEBUG_LOAD = false; 53 private static final boolean DEBUG_CONFIG = false; 54 private static final boolean TRACE_FOR_PRELOAD = false; 55 56 // Use the current SDK version code. If we are a development build, 57 // also allow the previous SDK version + 1. 58 private static final int sSdkVersion = Build.VERSION.SDK_INT 59 + ("REL".equals(Build.VERSION.CODENAME) ? 0 : 1); 60 private static final Object mSync = new Object(); 61 private static Resources mSystem = null; 62 63 // Information about preloaded resources. Note that they are not 64 // protected by a lock, because while preloading in zygote we are all 65 // single-threaded, and after that these are immutable. 66 private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables 67 = new LongSparseArray<Drawable.ConstantState>(); 68 private static final SparseArray<ColorStateList> mPreloadedColorStateLists 69 = new SparseArray<ColorStateList>(); 70 private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables 71 = new LongSparseArray<Drawable.ConstantState>(); 72 private static boolean mPreloaded; 73 74 /*package*/ final TypedValue mTmpValue = new TypedValue(); 75 76 // These are protected by the mTmpValue lock. 77 private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache 78 = new LongSparseArray<WeakReference<Drawable.ConstantState> >(); 79 private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache 80 = new SparseArray<WeakReference<ColorStateList> >(); 81 private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache 82 = new LongSparseArray<WeakReference<Drawable.ConstantState> >(); 83 private boolean mPreloading; 84 85 /*package*/ TypedArray mCachedStyledAttributes = null; 86 87 private int mLastCachedXmlBlockIndex = -1; 88 private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; 89 private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; 90 91 /*package*/ final AssetManager mAssets; 92 private final Configuration mConfiguration = new Configuration(); 93 /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); 94 PluralRules mPluralRule; 95 96 private CompatibilityInfo mCompatibilityInfo; 97 private Display mDefaultDisplay; 98 99 private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>() { 100 @Override 101 public void put(long k, Object o) { 102 throw new UnsupportedOperationException(); 103 } 104 @Override 105 public void append(long k, Object o) { 106 throw new UnsupportedOperationException(); 107 } 108 }; 109 110 @SuppressWarnings("unchecked") 111 private static <T> LongSparseArray<T> emptySparseArray() { 112 return (LongSparseArray<T>) EMPTY_ARRAY; 113 } 114 115 /** 116 * This exception is thrown by the resource APIs when a requested resource 117 * can not be found. 118 */ 119 public static class NotFoundException extends RuntimeException { 120 public NotFoundException() { 121 } 122 123 public NotFoundException(String name) { 124 super(name); 125 } 126 } 127 128 /** 129 * Create a new Resources object on top of an existing set of assets in an 130 * AssetManager. 131 * 132 * @param assets Previously created AssetManager. 133 * @param metrics Current display metrics to consider when 134 * selecting/computing resource values. 135 * @param config Desired device configuration to consider when 136 * selecting/computing resource values (optional). 137 */ 138 public Resources(AssetManager assets, DisplayMetrics metrics, 139 Configuration config) { 140 this(assets, metrics, config, (CompatibilityInfo) null); 141 } 142 143 /** 144 * Creates a new Resources object with CompatibilityInfo. 145 * 146 * @param assets Previously created AssetManager. 147 * @param metrics Current display metrics to consider when 148 * selecting/computing resource values. 149 * @param config Desired device configuration to consider when 150 * selecting/computing resource values (optional). 151 * @param compInfo this resource's compatibility info. It will use the default compatibility 152 * info when it's null. 153 * @hide 154 */ 155 public Resources(AssetManager assets, DisplayMetrics metrics, 156 Configuration config, CompatibilityInfo compInfo) { 157 mAssets = assets; 158 mMetrics.setToDefaults(); 159 if (compInfo == null) { 160 mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; 161 } else { 162 mCompatibilityInfo = compInfo; 163 } 164 updateConfiguration(config, metrics); 165 assets.ensureStringBlocks(); 166 } 167 168 /** 169 * Return a global shared Resources object that provides access to only 170 * system resources (no application resources), and is not configured for 171 * the current screen (can not use dimension units, does not change based 172 * on orientation, etc). 173 */ 174 public static Resources getSystem() { 175 synchronized (mSync) { 176 Resources ret = mSystem; 177 if (ret == null) { 178 ret = new Resources(); 179 mSystem = ret; 180 } 181 182 return ret; 183 } 184 } 185 186 /** 187 * Return the string value associated with a particular resource ID. The 188 * returned object will be a String if this is a plain string; it will be 189 * some other type of CharSequence if it is styled. 190 * {@more} 191 * 192 * @param id The desired resource identifier, as generated by the aapt 193 * tool. This integer encodes the package, type, and resource 194 * entry. The value 0 is an invalid identifier. 195 * 196 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 197 * 198 * @return CharSequence The string data associated with the resource, plus 199 * possibly styled text information. 200 */ 201 public CharSequence getText(int id) throws NotFoundException { 202 CharSequence res = mAssets.getResourceText(id); 203 if (res != null) { 204 return res; 205 } 206 throw new NotFoundException("String resource ID #0x" 207 + Integer.toHexString(id)); 208 } 209 210 /** 211 * @param id The desired resource identifier, as generated by the aapt 212 * tool. This integer encodes the package, type, and resource 213 * entry. The value 0 is an invalid identifier. 214 * 215 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 216 * 217 * @return CharSequence The string data associated with the resource, plus 218 * possibly styled text information. 219 */ 220 public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { 221 PluralRules rule = getPluralRule(); 222 CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity)); 223 if (res != null) { 224 return res; 225 } 226 res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER); 227 if (res != null) { 228 return res; 229 } 230 throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id) 231 + " quantity=" + quantity 232 + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity))); 233 } 234 235 private PluralRules getPluralRule() { 236 synchronized (mSync) { 237 if (mPluralRule == null) { 238 mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale); 239 } 240 return mPluralRule; 241 } 242 } 243 244 /** 245 * Return the string value associated with a particular resource ID. It 246 * will be stripped of any styled text information. 247 * {@more} 248 * 249 * @param id The desired resource identifier, as generated by the aapt 250 * tool. This integer encodes the package, type, and resource 251 * entry. The value 0 is an invalid identifier. 252 * 253 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 254 * 255 * @return String The string data associated with the resource, 256 * stripped of styled text information. 257 */ 258 public String getString(int id) throws NotFoundException { 259 CharSequence res = getText(id); 260 if (res != null) { 261 return res.toString(); 262 } 263 throw new NotFoundException("String resource ID #0x" 264 + Integer.toHexString(id)); 265 } 266 267 268 /** 269 * Return the string value associated with a particular resource ID, 270 * substituting the format arguments as defined in {@link java.util.Formatter} 271 * and {@link java.lang.String#format}. It will be stripped of any styled text 272 * information. 273 * {@more} 274 * 275 * @param id The desired resource identifier, as generated by the aapt 276 * tool. This integer encodes the package, type, and resource 277 * entry. The value 0 is an invalid identifier. 278 * 279 * @param formatArgs The format arguments that will be used for substitution. 280 * 281 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 282 * 283 * @return String The string data associated with the resource, 284 * stripped of styled text information. 285 */ 286 public String getString(int id, Object... formatArgs) throws NotFoundException { 287 String raw = getString(id); 288 return String.format(mConfiguration.locale, raw, formatArgs); 289 } 290 291 /** 292 * Return the string value associated with a particular resource ID for a particular 293 * numerical quantity, substituting the format arguments as defined in 294 * {@link java.util.Formatter} and {@link java.lang.String#format}. It will be 295 * stripped of any styled text information. 296 * {@more} 297 * 298 * @param id The desired resource identifier, as generated by the aapt 299 * tool. This integer encodes the package, type, and resource 300 * entry. The value 0 is an invalid identifier. 301 * @param quantity The number used to get the correct string for the current language's 302 * plural rules. 303 * @param formatArgs The format arguments that will be used for substitution. 304 * 305 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 306 * 307 * @return String The string data associated with the resource, 308 * stripped of styled text information. 309 */ 310 public String getQuantityString(int id, int quantity, Object... formatArgs) 311 throws NotFoundException { 312 String raw = getQuantityText(id, quantity).toString(); 313 return String.format(mConfiguration.locale, raw, formatArgs); 314 } 315 316 /** 317 * Return the string value associated with a particular resource ID for a particular 318 * numerical quantity. 319 * 320 * @param id The desired resource identifier, as generated by the aapt 321 * tool. This integer encodes the package, type, and resource 322 * entry. The value 0 is an invalid identifier. 323 * @param quantity The number used to get the correct string for the current language's 324 * plural rules. 325 * 326 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 327 * 328 * @return String The string data associated with the resource, 329 * stripped of styled text information. 330 */ 331 public String getQuantityString(int id, int quantity) throws NotFoundException { 332 return getQuantityText(id, quantity).toString(); 333 } 334 335 /** 336 * Return the string value associated with a particular resource ID. The 337 * returned object will be a String if this is a plain string; it will be 338 * some other type of CharSequence if it is styled. 339 * 340 * @param id The desired resource identifier, as generated by the aapt 341 * tool. This integer encodes the package, type, and resource 342 * entry. The value 0 is an invalid identifier. 343 * 344 * @param def The default CharSequence to return. 345 * 346 * @return CharSequence The string data associated with the resource, plus 347 * possibly styled text information, or def if id is 0 or not found. 348 */ 349 public CharSequence getText(int id, CharSequence def) { 350 CharSequence res = id != 0 ? mAssets.getResourceText(id) : null; 351 return res != null ? res : def; 352 } 353 354 /** 355 * Return the styled text array associated with a particular resource ID. 356 * 357 * @param id The desired resource identifier, as generated by the aapt 358 * tool. This integer encodes the package, type, and resource 359 * entry. The value 0 is an invalid identifier. 360 * 361 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 362 * 363 * @return The styled text array associated with the resource. 364 */ 365 public CharSequence[] getTextArray(int id) throws NotFoundException { 366 CharSequence[] res = mAssets.getResourceTextArray(id); 367 if (res != null) { 368 return res; 369 } 370 throw new NotFoundException("Text array resource ID #0x" 371 + Integer.toHexString(id)); 372 } 373 374 /** 375 * Return the string array associated with a particular resource ID. 376 * 377 * @param id The desired resource identifier, as generated by the aapt 378 * tool. This integer encodes the package, type, and resource 379 * entry. The value 0 is an invalid identifier. 380 * 381 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 382 * 383 * @return The string array associated with the resource. 384 */ 385 public String[] getStringArray(int id) throws NotFoundException { 386 String[] res = mAssets.getResourceStringArray(id); 387 if (res != null) { 388 return res; 389 } 390 throw new NotFoundException("String array resource ID #0x" 391 + Integer.toHexString(id)); 392 } 393 394 /** 395 * Return the int array associated with a particular resource ID. 396 * 397 * @param id The desired resource identifier, as generated by the aapt 398 * tool. This integer encodes the package, type, and resource 399 * entry. The value 0 is an invalid identifier. 400 * 401 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 402 * 403 * @return The int array associated with the resource. 404 */ 405 public int[] getIntArray(int id) throws NotFoundException { 406 int[] res = mAssets.getArrayIntResource(id); 407 if (res != null) { 408 return res; 409 } 410 throw new NotFoundException("Int array resource ID #0x" 411 + Integer.toHexString(id)); 412 } 413 414 /** 415 * Return an array of heterogeneous values. 416 * 417 * @param id The desired resource identifier, as generated by the aapt 418 * tool. This integer encodes the package, type, and resource 419 * entry. The value 0 is an invalid identifier. 420 * 421 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 422 * 423 * @return Returns a TypedArray holding an array of the array values. 424 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 425 * when done with it. 426 */ 427 public TypedArray obtainTypedArray(int id) throws NotFoundException { 428 int len = mAssets.getArraySize(id); 429 if (len < 0) { 430 throw new NotFoundException("Array resource ID #0x" 431 + Integer.toHexString(id)); 432 } 433 434 TypedArray array = getCachedStyledAttributes(len); 435 array.mLength = mAssets.retrieveArray(id, array.mData); 436 array.mIndices[0] = 0; 437 438 return array; 439 } 440 441 /** 442 * Retrieve a dimensional for a particular resource ID. Unit 443 * conversions are based on the current {@link DisplayMetrics} associated 444 * with the resources. 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 * @return Resource dimension value multiplied by the appropriate 451 * metric. 452 * 453 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 454 * 455 * @see #getDimensionPixelOffset 456 * @see #getDimensionPixelSize 457 */ 458 public float getDimension(int id) throws NotFoundException { 459 synchronized (mTmpValue) { 460 TypedValue value = mTmpValue; 461 getValue(id, value, true); 462 if (value.type == TypedValue.TYPE_DIMENSION) { 463 return TypedValue.complexToDimension(value.data, mMetrics); 464 } 465 throw new NotFoundException( 466 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 467 + Integer.toHexString(value.type) + " is not valid"); 468 } 469 } 470 471 /** 472 * Retrieve a dimensional for a particular resource ID for use 473 * as an offset in raw pixels. This is the same as 474 * {@link #getDimension}, except the returned value is converted to 475 * integer pixels for you. An offset conversion involves simply 476 * truncating the base value to an integer. 477 * 478 * @param id The desired resource identifier, as generated by the aapt 479 * tool. This integer encodes the package, type, and resource 480 * entry. The value 0 is an invalid identifier. 481 * 482 * @return Resource dimension value multiplied by the appropriate 483 * metric and truncated to integer pixels. 484 * 485 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 486 * 487 * @see #getDimension 488 * @see #getDimensionPixelSize 489 */ 490 public int getDimensionPixelOffset(int id) throws NotFoundException { 491 synchronized (mTmpValue) { 492 TypedValue value = mTmpValue; 493 getValue(id, value, true); 494 if (value.type == TypedValue.TYPE_DIMENSION) { 495 return TypedValue.complexToDimensionPixelOffset( 496 value.data, mMetrics); 497 } 498 throw new NotFoundException( 499 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 500 + Integer.toHexString(value.type) + " is not valid"); 501 } 502 } 503 504 /** 505 * Retrieve a dimensional for a particular resource ID for use 506 * as a size in raw pixels. This is the same as 507 * {@link #getDimension}, except the returned value is converted to 508 * integer pixels for use as a size. A size conversion involves 509 * rounding the base value, and ensuring that a non-zero base value 510 * is at least one pixel in size. 511 * 512 * @param id The desired resource identifier, as generated by the aapt 513 * tool. This integer encodes the package, type, and resource 514 * entry. The value 0 is an invalid identifier. 515 * 516 * @return Resource dimension value multiplied by the appropriate 517 * metric and truncated to integer pixels. 518 * 519 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 520 * 521 * @see #getDimension 522 * @see #getDimensionPixelOffset 523 */ 524 public int getDimensionPixelSize(int id) throws NotFoundException { 525 synchronized (mTmpValue) { 526 TypedValue value = mTmpValue; 527 getValue(id, value, true); 528 if (value.type == TypedValue.TYPE_DIMENSION) { 529 return TypedValue.complexToDimensionPixelSize( 530 value.data, mMetrics); 531 } 532 throw new NotFoundException( 533 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 534 + Integer.toHexString(value.type) + " is not valid"); 535 } 536 } 537 538 /** 539 * Retrieve a fractional unit for a particular resource ID. 540 * 541 * @param id The desired resource identifier, as generated by the aapt 542 * tool. This integer encodes the package, type, and resource 543 * entry. The value 0 is an invalid identifier. 544 * @param base The base value of this fraction. In other words, a 545 * standard fraction is multiplied by this value. 546 * @param pbase The parent base value of this fraction. In other 547 * words, a parent fraction (nn%p) is multiplied by this 548 * value. 549 * 550 * @return Attribute fractional value multiplied by the appropriate 551 * base value. 552 * 553 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 554 */ 555 public float getFraction(int id, int base, int pbase) { 556 synchronized (mTmpValue) { 557 TypedValue value = mTmpValue; 558 getValue(id, value, true); 559 if (value.type == TypedValue.TYPE_FRACTION) { 560 return TypedValue.complexToFraction(value.data, base, pbase); 561 } 562 throw new NotFoundException( 563 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 564 + Integer.toHexString(value.type) + " is not valid"); 565 } 566 } 567 568 /** 569 * Return a drawable object associated with a particular resource ID. 570 * Various types of objects will be returned depending on the underlying 571 * resource -- for example, a solid color, PNG image, scalable image, etc. 572 * The Drawable API hides these implementation details. 573 * 574 * @param id The desired resource identifier, as generated by the aapt 575 * tool. This integer encodes the package, type, and resource 576 * entry. The value 0 is an invalid identifier. 577 * 578 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 579 * 580 * @return Drawable An object that can be used to draw this resource. 581 */ 582 public Drawable getDrawable(int id) throws NotFoundException { 583 synchronized (mTmpValue) { 584 TypedValue value = mTmpValue; 585 getValue(id, value, true); 586 return loadDrawable(value, id); 587 } 588 } 589 590 /** 591 * Return a movie object associated with the particular resource ID. 592 * @param id The desired resource identifier, as generated by the aapt 593 * tool. This integer encodes the package, type, and resource 594 * entry. The value 0 is an invalid identifier. 595 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 596 * 597 */ 598 public Movie getMovie(int id) throws NotFoundException { 599 InputStream is = openRawResource(id); 600 Movie movie = Movie.decodeStream(is); 601 try { 602 is.close(); 603 } 604 catch (java.io.IOException e) { 605 // don't care, since the return value is valid 606 } 607 return movie; 608 } 609 610 /** 611 * Return a color integer associated with a particular resource ID. 612 * If the resource holds a complex 613 * {@link android.content.res.ColorStateList}, then the default color from 614 * the set is returned. 615 * 616 * @param id The desired resource identifier, as generated by the aapt 617 * tool. This integer encodes the package, type, and resource 618 * entry. The value 0 is an invalid identifier. 619 * 620 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 621 * 622 * @return Returns a single color value in the form 0xAARRGGBB. 623 */ 624 public int getColor(int id) throws NotFoundException { 625 synchronized (mTmpValue) { 626 TypedValue value = mTmpValue; 627 getValue(id, value, true); 628 if (value.type >= TypedValue.TYPE_FIRST_INT 629 && value.type <= TypedValue.TYPE_LAST_INT) { 630 return value.data; 631 } else if (value.type == TypedValue.TYPE_STRING) { 632 ColorStateList csl = loadColorStateList(mTmpValue, id); 633 return csl.getDefaultColor(); 634 } 635 throw new NotFoundException( 636 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 637 + Integer.toHexString(value.type) + " is not valid"); 638 } 639 } 640 641 /** 642 * Return a color state list associated with a particular resource ID. The 643 * resource may contain either a single raw color value, or a complex 644 * {@link android.content.res.ColorStateList} holding multiple possible colors. 645 * 646 * @param id The desired resource identifier of a {@link ColorStateList}, 647 * as generated by the aapt tool. This integer encodes the package, type, and resource 648 * entry. The value 0 is an invalid identifier. 649 * 650 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 651 * 652 * @return Returns a ColorStateList object containing either a single 653 * solid color or multiple colors that can be selected based on a state. 654 */ 655 public ColorStateList getColorStateList(int id) throws NotFoundException { 656 synchronized (mTmpValue) { 657 TypedValue value = mTmpValue; 658 getValue(id, value, true); 659 return loadColorStateList(value, id); 660 } 661 } 662 663 /** 664 * Return a boolean associated with a particular resource ID. This can be 665 * used with any integral resource value, and will return true if it is 666 * non-zero. 667 * 668 * @param id The desired resource identifier, as generated by the aapt 669 * tool. This integer encodes the package, type, and resource 670 * entry. The value 0 is an invalid identifier. 671 * 672 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 673 * 674 * @return Returns the boolean value contained in the resource. 675 */ 676 public boolean getBoolean(int id) throws NotFoundException { 677 synchronized (mTmpValue) { 678 TypedValue value = mTmpValue; 679 getValue(id, value, true); 680 if (value.type >= TypedValue.TYPE_FIRST_INT 681 && value.type <= TypedValue.TYPE_LAST_INT) { 682 return value.data != 0; 683 } 684 throw new NotFoundException( 685 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 686 + Integer.toHexString(value.type) + " is not valid"); 687 } 688 } 689 690 /** 691 * Return an integer associated with a particular resource ID. 692 * 693 * @param id The desired resource identifier, as generated by the aapt 694 * tool. This integer encodes the package, type, and resource 695 * entry. The value 0 is an invalid identifier. 696 * 697 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 698 * 699 * @return Returns the integer value contained in the resource. 700 */ 701 public int getInteger(int id) throws NotFoundException { 702 synchronized (mTmpValue) { 703 TypedValue value = mTmpValue; 704 getValue(id, value, true); 705 if (value.type >= TypedValue.TYPE_FIRST_INT 706 && value.type <= TypedValue.TYPE_LAST_INT) { 707 return value.data; 708 } 709 throw new NotFoundException( 710 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 711 + Integer.toHexString(value.type) + " is not valid"); 712 } 713 } 714 715 /** 716 * Return an XmlResourceParser through which you can read a view layout 717 * description for the given resource ID. This parser has limited 718 * functionality -- in particular, you can't change its input, and only 719 * the high-level events are available. 720 * 721 * <p>This function is really a simple wrapper for calling 722 * {@link #getXml} with a layout resource. 723 * 724 * @param id The desired resource identifier, as generated by the aapt 725 * tool. This integer encodes the package, type, and resource 726 * entry. The value 0 is an invalid identifier. 727 * 728 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 729 * 730 * @return A new parser object through which you can read 731 * the XML data. 732 * 733 * @see #getXml 734 */ 735 public XmlResourceParser getLayout(int id) throws NotFoundException { 736 return loadXmlResourceParser(id, "layout"); 737 } 738 739 /** 740 * Return an XmlResourceParser through which you can read an animation 741 * description for the given resource ID. This parser has limited 742 * functionality -- in particular, you can't change its input, and only 743 * the high-level events are available. 744 * 745 * <p>This function is really a simple wrapper for calling 746 * {@link #getXml} with an animation resource. 747 * 748 * @param id The desired resource identifier, as generated by the aapt 749 * tool. This integer encodes the package, type, and resource 750 * entry. The value 0 is an invalid identifier. 751 * 752 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 753 * 754 * @return A new parser object through which you can read 755 * the XML data. 756 * 757 * @see #getXml 758 */ 759 public XmlResourceParser getAnimation(int id) throws NotFoundException { 760 return loadXmlResourceParser(id, "anim"); 761 } 762 763 /** 764 * Return an XmlResourceParser through which you can read a generic XML 765 * resource for the given resource ID. 766 * 767 * <p>The XmlPullParser implementation returned here has some limited 768 * functionality. In particular, you can't change its input, and only 769 * high-level parsing events are available (since the document was 770 * pre-parsed for you at build time, which involved merging text and 771 * stripping comments). 772 * 773 * @param id The desired resource identifier, as generated by the aapt 774 * tool. This integer encodes the package, type, and resource 775 * entry. The value 0 is an invalid identifier. 776 * 777 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 778 * 779 * @return A new parser object through which you can read 780 * the XML data. 781 * 782 * @see android.util.AttributeSet 783 */ 784 public XmlResourceParser getXml(int id) throws NotFoundException { 785 return loadXmlResourceParser(id, "xml"); 786 } 787 788 /** 789 * Open a data stream for reading a raw resource. This can only be used 790 * with resources whose value is the name of an asset files -- that is, it can be 791 * used to open drawable, sound, and raw resources; it will fail on string 792 * and color resources. 793 * 794 * @param id The resource identifier to open, as generated by the appt 795 * tool. 796 * 797 * @return InputStream Access to the resource data. 798 * 799 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 800 * 801 */ 802 public InputStream openRawResource(int id) throws NotFoundException { 803 synchronized (mTmpValue) { 804 return openRawResource(id, mTmpValue); 805 } 806 } 807 808 /** 809 * Open a data stream for reading a raw resource. This can only be used 810 * with resources whose value is the name of an asset file -- that is, it can be 811 * used to open drawable, sound, and raw resources; it will fail on string 812 * and color resources. 813 * 814 * @param id The resource identifier to open, as generated by the appt tool. 815 * @param value The TypedValue object to hold the resource information. 816 * 817 * @return InputStream Access to the resource data. 818 * 819 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 820 */ 821 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 822 getValue(id, value, true); 823 824 try { 825 return mAssets.openNonAsset(value.assetCookie, value.string.toString(), 826 AssetManager.ACCESS_STREAMING); 827 } catch (Exception e) { 828 NotFoundException rnf = new NotFoundException("File " + value.string.toString() + 829 " from drawable resource ID #0x" + Integer.toHexString(id)); 830 rnf.initCause(e); 831 throw rnf; 832 } 833 } 834 835 /** 836 * Open a file descriptor for reading a raw resource. This can only be used 837 * with resources whose value is the name of an asset files -- that is, it can be 838 * used to open drawable, sound, and raw resources; it will fail on string 839 * and color resources. 840 * 841 * <p>This function only works for resources that are stored in the package 842 * as uncompressed data, which typically includes things like mp3 files 843 * and png images. 844 * 845 * @param id The resource identifier to open, as generated by the appt 846 * tool. 847 * 848 * @return AssetFileDescriptor A new file descriptor you can use to read 849 * the resource. This includes the file descriptor itself, as well as the 850 * offset and length of data where the resource appears in the file. A 851 * null is returned if the file exists but is compressed. 852 * 853 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 854 * 855 */ 856 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 857 synchronized (mTmpValue) { 858 TypedValue value = mTmpValue; 859 getValue(id, value, true); 860 861 try { 862 return mAssets.openNonAssetFd( 863 value.assetCookie, value.string.toString()); 864 } catch (Exception e) { 865 NotFoundException rnf = new NotFoundException( 866 "File " + value.string.toString() 867 + " from drawable resource ID #0x" 868 + Integer.toHexString(id)); 869 rnf.initCause(e); 870 throw rnf; 871 } 872 873 } 874 } 875 876 /** 877 * Return the raw data associated with a particular resource ID. 878 * 879 * @param id The desired resource identifier, as generated by the aapt 880 * tool. This integer encodes the package, type, and resource 881 * entry. The value 0 is an invalid identifier. 882 * @param outValue Object in which to place the resource data. 883 * @param resolveRefs If true, a resource that is a reference to another 884 * resource will be followed so that you receive the 885 * actual final resource data. If false, the TypedValue 886 * will be filled in with the reference itself. 887 * 888 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 889 * 890 */ 891 public void getValue(int id, TypedValue outValue, boolean resolveRefs) 892 throws NotFoundException { 893 boolean found = mAssets.getResourceValue(id, outValue, resolveRefs); 894 if (found) { 895 return; 896 } 897 throw new NotFoundException("Resource ID #0x" 898 + Integer.toHexString(id)); 899 } 900 901 /** 902 * Return the raw data associated with a particular resource ID. 903 * See getIdentifier() for information on how names are mapped to resource 904 * IDs, and getString(int) for information on how string resources are 905 * retrieved. 906 * 907 * <p>Note: use of this function is discouraged. It is much more 908 * efficient to retrieve resources by identifier than by name. 909 * 910 * @param name The name of the desired resource. This is passed to 911 * getIdentifier() with a default type of "string". 912 * @param outValue Object in which to place the resource data. 913 * @param resolveRefs If true, a resource that is a reference to another 914 * resource will be followed so that you receive the 915 * actual final resource data. If false, the TypedValue 916 * will be filled in with the reference itself. 917 * 918 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 919 * 920 */ 921 public void getValue(String name, TypedValue outValue, boolean resolveRefs) 922 throws NotFoundException { 923 int id = getIdentifier(name, "string", null); 924 if (id != 0) { 925 getValue(id, outValue, resolveRefs); 926 return; 927 } 928 throw new NotFoundException("String resource name " + name); 929 } 930 931 /** 932 * This class holds the current attribute values for a particular theme. 933 * In other words, a Theme is a set of values for resource attributes; 934 * these are used in conjunction with {@link TypedArray} 935 * to resolve the final value for an attribute. 936 * 937 * <p>The Theme's attributes come into play in two ways: (1) a styled 938 * attribute can explicit reference a value in the theme through the 939 * "?themeAttribute" syntax; (2) if no value has been defined for a 940 * particular styled attribute, as a last resort we will try to find that 941 * attribute's value in the Theme. 942 * 943 * <p>You will normally use the {@link #obtainStyledAttributes} APIs to 944 * retrieve XML attributes with style and theme information applied. 945 */ 946 public final class Theme { 947 /** 948 * Place new attribute values into the theme. The style resource 949 * specified by <var>resid</var> will be retrieved from this Theme's 950 * resources, its values placed into the Theme object. 951 * 952 * <p>The semantics of this function depends on the <var>force</var> 953 * argument: If false, only values that are not already defined in 954 * the theme will be copied from the system resource; otherwise, if 955 * any of the style's attributes are already defined in the theme, the 956 * current values in the theme will be overwritten. 957 * 958 * @param resid The resource ID of a style resource from which to 959 * obtain attribute values. 960 * @param force If true, values in the style resource will always be 961 * used in the theme; otherwise, they will only be used 962 * if not already defined in the theme. 963 */ 964 public void applyStyle(int resid, boolean force) { 965 AssetManager.applyThemeStyle(mTheme, resid, force); 966 } 967 968 /** 969 * Set this theme to hold the same contents as the theme 970 * <var>other</var>. If both of these themes are from the same 971 * Resources object, they will be identical after this function 972 * returns. If they are from different Resources, only the resources 973 * they have in common will be set in this theme. 974 * 975 * @param other The existing Theme to copy from. 976 */ 977 public void setTo(Theme other) { 978 AssetManager.copyTheme(mTheme, other.mTheme); 979 } 980 981 /** 982 * Return a StyledAttributes holding the values defined by 983 * <var>Theme</var> which are listed in <var>attrs</var>. 984 * 985 * <p>Be sure to call StyledAttributes.recycle() when you are done with 986 * the array. 987 * 988 * @param attrs The desired attributes. 989 * 990 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 991 * 992 * @return Returns a TypedArray holding an array of the attribute values. 993 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 994 * when done with it. 995 * 996 * @see Resources#obtainAttributes 997 * @see #obtainStyledAttributes(int, int[]) 998 * @see #obtainStyledAttributes(AttributeSet, int[], int, int) 999 */ 1000 public TypedArray obtainStyledAttributes(int[] attrs) { 1001 int len = attrs.length; 1002 TypedArray array = getCachedStyledAttributes(len); 1003 array.mRsrcs = attrs; 1004 AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, 1005 array.mData, array.mIndices); 1006 return array; 1007 } 1008 1009 /** 1010 * Return a StyledAttributes holding the values defined by the style 1011 * resource <var>resid</var> which are listed in <var>attrs</var>. 1012 * 1013 * <p>Be sure to call StyledAttributes.recycle() when you are done with 1014 * the array. 1015 * 1016 * @param resid The desired style resource. 1017 * @param attrs The desired attributes in the style. 1018 * 1019 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1020 * 1021 * @return Returns a TypedArray holding an array of the attribute values. 1022 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1023 * when done with it. 1024 * 1025 * @see Resources#obtainAttributes 1026 * @see #obtainStyledAttributes(int[]) 1027 * @see #obtainStyledAttributes(AttributeSet, int[], int, int) 1028 */ 1029 public TypedArray obtainStyledAttributes(int resid, int[] attrs) 1030 throws NotFoundException { 1031 int len = attrs.length; 1032 TypedArray array = getCachedStyledAttributes(len); 1033 array.mRsrcs = attrs; 1034 1035 AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, 1036 array.mData, array.mIndices); 1037 if (false) { 1038 int[] data = array.mData; 1039 1040 System.out.println("**********************************************************"); 1041 System.out.println("**********************************************************"); 1042 System.out.println("**********************************************************"); 1043 System.out.println("Attributes:"); 1044 String s = " Attrs:"; 1045 int i; 1046 for (i=0; i<attrs.length; i++) { 1047 s = s + " 0x" + Integer.toHexString(attrs[i]); 1048 } 1049 System.out.println(s); 1050 s = " Found:"; 1051 TypedValue value = new TypedValue(); 1052 for (i=0; i<attrs.length; i++) { 1053 int d = i*AssetManager.STYLE_NUM_ENTRIES; 1054 value.type = data[d+AssetManager.STYLE_TYPE]; 1055 value.data = data[d+AssetManager.STYLE_DATA]; 1056 value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; 1057 value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; 1058 s = s + " 0x" + Integer.toHexString(attrs[i]) 1059 + "=" + value; 1060 } 1061 System.out.println(s); 1062 } 1063 return array; 1064 } 1065 1066 /** 1067 * Return a StyledAttributes holding the attribute values in 1068 * <var>set</var> 1069 * that are listed in <var>attrs</var>. In addition, if the given 1070 * AttributeSet specifies a style class (through the "style" attribute), 1071 * that style will be applied on top of the base attributes it defines. 1072 * 1073 * <p>Be sure to call StyledAttributes.recycle() when you are done with 1074 * the array. 1075 * 1076 * <p>When determining the final value of a particular attribute, there 1077 * are four inputs that come into play:</p> 1078 * 1079 * <ol> 1080 * <li> Any attribute values in the given AttributeSet. 1081 * <li> The style resource specified in the AttributeSet (named 1082 * "style"). 1083 * <li> The default style specified by <var>defStyleAttr</var> and 1084 * <var>defStyleRes</var> 1085 * <li> The base values in this theme. 1086 * </ol> 1087 * 1088 * <p>Each of these inputs is considered in-order, with the first listed 1089 * taking precedence over the following ones. In other words, if in the 1090 * AttributeSet you have supplied <code><Button 1091 * textColor="#ff000000"></code>, then the button's text will 1092 * <em>always</em> be black, regardless of what is specified in any of 1093 * the styles. 1094 * 1095 * @param set The base set of attribute values. May be null. 1096 * @param attrs The desired attributes to be retrieved. 1097 * @param defStyleAttr An attribute in the current theme that contains a 1098 * reference to a style resource that supplies 1099 * defaults values for the StyledAttributes. Can be 1100 * 0 to not look for defaults. 1101 * @param defStyleRes A resource identifier of a style resource that 1102 * supplies default values for the StyledAttributes, 1103 * used only if defStyleAttr is 0 or can not be found 1104 * in the theme. Can be 0 to not look for defaults. 1105 * 1106 * @return Returns a TypedArray holding an array of the attribute values. 1107 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1108 * when done with it. 1109 * 1110 * @see Resources#obtainAttributes 1111 * @see #obtainStyledAttributes(int[]) 1112 * @see #obtainStyledAttributes(int, int[]) 1113 */ 1114 public TypedArray obtainStyledAttributes(AttributeSet set, 1115 int[] attrs, int defStyleAttr, int defStyleRes) { 1116 int len = attrs.length; 1117 TypedArray array = getCachedStyledAttributes(len); 1118 1119 // XXX note that for now we only work with compiled XML files. 1120 // To support generic XML files we will need to manually parse 1121 // out the attributes from the XML file (applying type information 1122 // contained in the resources and such). 1123 XmlBlock.Parser parser = (XmlBlock.Parser)set; 1124 AssetManager.applyStyle( 1125 mTheme, defStyleAttr, defStyleRes, 1126 parser != null ? parser.mParseState : 0, attrs, 1127 array.mData, array.mIndices); 1128 1129 array.mRsrcs = attrs; 1130 array.mXml = parser; 1131 1132 if (false) { 1133 int[] data = array.mData; 1134 1135 System.out.println("Attributes:"); 1136 String s = " Attrs:"; 1137 int i; 1138 for (i=0; i<set.getAttributeCount(); i++) { 1139 s = s + " " + set.getAttributeName(i); 1140 int id = set.getAttributeNameResource(i); 1141 if (id != 0) { 1142 s = s + "(0x" + Integer.toHexString(id) + ")"; 1143 } 1144 s = s + "=" + set.getAttributeValue(i); 1145 } 1146 System.out.println(s); 1147 s = " Found:"; 1148 TypedValue value = new TypedValue(); 1149 for (i=0; i<attrs.length; i++) { 1150 int d = i*AssetManager.STYLE_NUM_ENTRIES; 1151 value.type = data[d+AssetManager.STYLE_TYPE]; 1152 value.data = data[d+AssetManager.STYLE_DATA]; 1153 value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; 1154 value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; 1155 s = s + " 0x" + Integer.toHexString(attrs[i]) 1156 + "=" + value; 1157 } 1158 System.out.println(s); 1159 } 1160 1161 return array; 1162 } 1163 1164 /** 1165 * Retrieve the value of an attribute in the Theme. The contents of 1166 * <var>outValue</var> are ultimately filled in by 1167 * {@link Resources#getValue}. 1168 * 1169 * @param resid The resource identifier of the desired theme 1170 * attribute. 1171 * @param outValue Filled in with the ultimate resource value supplied 1172 * by the attribute. 1173 * @param resolveRefs If true, resource references will be walked; if 1174 * false, <var>outValue</var> may be a 1175 * TYPE_REFERENCE. In either case, it will never 1176 * be a TYPE_ATTRIBUTE. 1177 * 1178 * @return boolean Returns true if the attribute was found and 1179 * <var>outValue</var> is valid, else false. 1180 */ 1181 public boolean resolveAttribute(int resid, TypedValue outValue, 1182 boolean resolveRefs) { 1183 boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); 1184 if (false) { 1185 System.out.println( 1186 "resolveAttribute #" + Integer.toHexString(resid) 1187 + " got=" + got + ", type=0x" + Integer.toHexString(outValue.type) 1188 + ", data=0x" + Integer.toHexString(outValue.data)); 1189 } 1190 return got; 1191 } 1192 1193 /** 1194 * Print contents of this theme out to the log. For debugging only. 1195 * 1196 * @param priority The log priority to use. 1197 * @param tag The log tag to use. 1198 * @param prefix Text to prefix each line printed. 1199 */ 1200 public void dump(int priority, String tag, String prefix) { 1201 AssetManager.dumpTheme(mTheme, priority, tag, prefix); 1202 } 1203 1204 protected void finalize() throws Throwable { 1205 super.finalize(); 1206 mAssets.releaseTheme(mTheme); 1207 } 1208 1209 /*package*/ Theme() { 1210 mAssets = Resources.this.mAssets; 1211 mTheme = mAssets.createTheme(); 1212 } 1213 1214 private final AssetManager mAssets; 1215 private final int mTheme; 1216 } 1217 1218 /** 1219 * Generate a new Theme object for this set of Resources. It initially 1220 * starts out empty. 1221 * 1222 * @return Theme The newly created Theme container. 1223 */ 1224 public final Theme newTheme() { 1225 return new Theme(); 1226 } 1227 1228 /** 1229 * Retrieve a set of basic attribute values from an AttributeSet, not 1230 * performing styling of them using a theme and/or style resources. 1231 * 1232 * @param set The current attribute values to retrieve. 1233 * @param attrs The specific attributes to be retrieved. 1234 * @return Returns a TypedArray holding an array of the attribute values. 1235 * Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} 1236 * when done with it. 1237 * 1238 * @see Theme#obtainStyledAttributes(AttributeSet, int[], int, int) 1239 */ 1240 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { 1241 int len = attrs.length; 1242 TypedArray array = getCachedStyledAttributes(len); 1243 1244 // XXX note that for now we only work with compiled XML files. 1245 // To support generic XML files we will need to manually parse 1246 // out the attributes from the XML file (applying type information 1247 // contained in the resources and such). 1248 XmlBlock.Parser parser = (XmlBlock.Parser)set; 1249 mAssets.retrieveAttributes(parser.mParseState, attrs, 1250 array.mData, array.mIndices); 1251 1252 array.mRsrcs = attrs; 1253 array.mXml = parser; 1254 1255 return array; 1256 } 1257 1258 /** 1259 * Store the newly updated configuration. 1260 */ 1261 public void updateConfiguration(Configuration config, 1262 DisplayMetrics metrics) { 1263 synchronized (mTmpValue) { 1264 int configChanges = 0xfffffff; 1265 if (config != null) { 1266 configChanges = mConfiguration.updateFrom(config); 1267 } 1268 if (mConfiguration.locale == null) { 1269 mConfiguration.locale = Locale.getDefault(); 1270 } 1271 if (metrics != null) { 1272 mMetrics.setTo(metrics); 1273 mMetrics.updateMetrics(mCompatibilityInfo, 1274 mConfiguration.orientation, mConfiguration.screenLayout); 1275 } 1276 mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; 1277 1278 String locale = null; 1279 if (mConfiguration.locale != null) { 1280 locale = mConfiguration.locale.getLanguage(); 1281 if (mConfiguration.locale.getCountry() != null) { 1282 locale += "-" + mConfiguration.locale.getCountry(); 1283 } 1284 } 1285 int width, height; 1286 if (mMetrics.widthPixels >= mMetrics.heightPixels) { 1287 width = mMetrics.widthPixels; 1288 height = mMetrics.heightPixels; 1289 } else { 1290 //noinspection SuspiciousNameCombination 1291 width = mMetrics.heightPixels; 1292 //noinspection SuspiciousNameCombination 1293 height = mMetrics.widthPixels; 1294 } 1295 int keyboardHidden = mConfiguration.keyboardHidden; 1296 if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO 1297 && mConfiguration.hardKeyboardHidden 1298 == Configuration.HARDKEYBOARDHIDDEN_YES) { 1299 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; 1300 } 1301 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, 1302 locale, mConfiguration.orientation, 1303 mConfiguration.touchscreen, 1304 (int)(mMetrics.density*160), mConfiguration.keyboard, 1305 keyboardHidden, mConfiguration.navigation, width, height, 1306 mConfiguration.screenLayout, mConfiguration.uiMode, sSdkVersion); 1307 1308 drawableCacheClear(mDrawableCache, configChanges); 1309 drawableCacheClear(mColorDrawableCache, configChanges); 1310 1311 mColorStateListCache.clear(); 1312 1313 1314 flushLayoutCache(); 1315 } 1316 synchronized (mSync) { 1317 if (mPluralRule != null) { 1318 mPluralRule = PluralRules.ruleForLocale(config.locale); 1319 } 1320 } 1321 } 1322 1323 private void drawableCacheClear( 1324 LongSparseArray<WeakReference<ConstantState>> cache, 1325 int configChanges) { 1326 int N = cache.size(); 1327 if (DEBUG_CONFIG) { 1328 Log.d(TAG, "Cleaning up drawables config changes: 0x" 1329 + Integer.toHexString(configChanges)); 1330 } 1331 for (int i=0; i<N; i++) { 1332 WeakReference<Drawable.ConstantState> ref = cache.valueAt(i); 1333 if (ref != null) { 1334 Drawable.ConstantState cs = ref.get(); 1335 if (cs != null) { 1336 if (Configuration.needNewResources( 1337 configChanges, cs.getChangingConfigurations())) { 1338 if (DEBUG_CONFIG) { 1339 Log.d(TAG, "FLUSHING #0x" 1340 + Long.toHexString(mDrawableCache.keyAt(i)) 1341 + " / " + cs + " with changes: 0x" 1342 + Integer.toHexString(cs.getChangingConfigurations())); 1343 } 1344 cache.setValueAt(i, null); 1345 } else if (DEBUG_CONFIG) { 1346 Log.d(TAG, "(Keeping #0x" 1347 + Long.toHexString(cache.keyAt(i)) 1348 + " / " + cs + " with changes: 0x" 1349 + Integer.toHexString(cs.getChangingConfigurations()) 1350 + ")"); 1351 } 1352 } 1353 } 1354 } 1355 cache.clear(); 1356 } 1357 1358 /** 1359 * Update the system resources configuration if they have previously 1360 * been initialized. 1361 * 1362 * @hide 1363 */ 1364 public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) { 1365 if (mSystem != null) { 1366 mSystem.updateConfiguration(config, metrics); 1367 //Log.i(TAG, "Updated system resources " + mSystem 1368 // + ": " + mSystem.getConfiguration()); 1369 } 1370 } 1371 1372 /** 1373 * Return the current display metrics that are in effect for this resource 1374 * object. The returned object should be treated as read-only. 1375 * 1376 * @return The resource's current display metrics. 1377 */ 1378 public DisplayMetrics getDisplayMetrics() { 1379 return mMetrics; 1380 } 1381 1382 /** 1383 * Return the current configuration that is in effect for this resource 1384 * object. The returned object should be treated as read-only. 1385 * 1386 * @return The resource's current configuration. 1387 */ 1388 public Configuration getConfiguration() { 1389 return mConfiguration; 1390 } 1391 1392 /** 1393 * Return the compatibility mode information for the application. 1394 * The returned object should be treated as read-only. 1395 * 1396 * @return compatibility info. null if the app does not require compatibility mode. 1397 * @hide 1398 */ 1399 public CompatibilityInfo getCompatibilityInfo() { 1400 return mCompatibilityInfo; 1401 } 1402 1403 /** 1404 * This is just for testing. 1405 * @hide 1406 */ 1407 public void setCompatibilityInfo(CompatibilityInfo ci) { 1408 mCompatibilityInfo = ci; 1409 updateConfiguration(mConfiguration, mMetrics); 1410 } 1411 1412 /** 1413 * Return a resource identifier for the given resource name. A fully 1414 * qualified resource name is of the form "package:type/entry". The first 1415 * two components (package and type) are optional if defType and 1416 * defPackage, respectively, are specified here. 1417 * 1418 * <p>Note: use of this function is discouraged. It is much more 1419 * efficient to retrieve resources by identifier than by name. 1420 * 1421 * @param name The name of the desired resource. 1422 * @param defType Optional default resource type to find, if "type/" is 1423 * not included in the name. Can be null to require an 1424 * explicit type. 1425 * @param defPackage Optional default package to find, if "package:" is 1426 * not included in the name. Can be null to require an 1427 * explicit package. 1428 * 1429 * @return int The associated resource identifier. Returns 0 if no such 1430 * resource was found. (0 is not a valid resource ID.) 1431 */ 1432 public int getIdentifier(String name, String defType, String defPackage) { 1433 try { 1434 return Integer.parseInt(name); 1435 } catch (Exception e) { 1436 // Ignore 1437 } 1438 return mAssets.getResourceIdentifier(name, defType, defPackage); 1439 } 1440 1441 /** 1442 * Return the full name for a given resource identifier. This name is 1443 * a single string of the form "package:type/entry". 1444 * 1445 * @param resid The resource identifier whose name is to be retrieved. 1446 * 1447 * @return A string holding the name of the resource. 1448 * 1449 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1450 * 1451 * @see #getResourcePackageName 1452 * @see #getResourceTypeName 1453 * @see #getResourceEntryName 1454 */ 1455 public String getResourceName(int resid) throws NotFoundException { 1456 String str = mAssets.getResourceName(resid); 1457 if (str != null) return str; 1458 throw new NotFoundException("Unable to find resource ID #0x" 1459 + Integer.toHexString(resid)); 1460 } 1461 1462 /** 1463 * Return the package name for a given resource identifier. 1464 * 1465 * @param resid The resource identifier whose package name is to be 1466 * retrieved. 1467 * 1468 * @return A string holding the package name of the resource. 1469 * 1470 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1471 * 1472 * @see #getResourceName 1473 */ 1474 public String getResourcePackageName(int resid) throws NotFoundException { 1475 String str = mAssets.getResourcePackageName(resid); 1476 if (str != null) return str; 1477 throw new NotFoundException("Unable to find resource ID #0x" 1478 + Integer.toHexString(resid)); 1479 } 1480 1481 /** 1482 * Return the type name for a given resource identifier. 1483 * 1484 * @param resid The resource identifier whose type name is to be 1485 * retrieved. 1486 * 1487 * @return A string holding the type name of the resource. 1488 * 1489 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1490 * 1491 * @see #getResourceName 1492 */ 1493 public String getResourceTypeName(int resid) throws NotFoundException { 1494 String str = mAssets.getResourceTypeName(resid); 1495 if (str != null) return str; 1496 throw new NotFoundException("Unable to find resource ID #0x" 1497 + Integer.toHexString(resid)); 1498 } 1499 1500 /** 1501 * Return the entry name for a given resource identifier. 1502 * 1503 * @param resid The resource identifier whose entry name is to be 1504 * retrieved. 1505 * 1506 * @return A string holding the entry name of the resource. 1507 * 1508 * @throws NotFoundException Throws NotFoundException if the given ID does not exist. 1509 * 1510 * @see #getResourceName 1511 */ 1512 public String getResourceEntryName(int resid) throws NotFoundException { 1513 String str = mAssets.getResourceEntryName(resid); 1514 if (str != null) return str; 1515 throw new NotFoundException("Unable to find resource ID #0x" 1516 + Integer.toHexString(resid)); 1517 } 1518 1519 /** 1520 * Parse a series of {@link android.R.styleable#Extra <extra>} tags from 1521 * an XML file. You call this when you are at the parent tag of the 1522 * extra tags, and it return once all of the child tags have been parsed. 1523 * This will call {@link #parseBundleExtra} for each extra tag encountered. 1524 * 1525 * @param parser The parser from which to retrieve the extras. 1526 * @param outBundle A Bundle in which to place all parsed extras. 1527 * @throws XmlPullParserException 1528 * @throws IOException 1529 */ 1530 public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) 1531 throws XmlPullParserException, IOException { 1532 int outerDepth = parser.getDepth(); 1533 int type; 1534 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1535 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 1536 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 1537 continue; 1538 } 1539 1540 String nodeName = parser.getName(); 1541 if (nodeName.equals("extra")) { 1542 parseBundleExtra("extra", parser, outBundle); 1543 XmlUtils.skipCurrentTag(parser); 1544 1545 } else { 1546 XmlUtils.skipCurrentTag(parser); 1547 } 1548 } 1549 } 1550 1551 /** 1552 * Parse a name/value pair out of an XML tag holding that data. The 1553 * AttributeSet must be holding the data defined by 1554 * {@link android.R.styleable#Extra}. The following value types are supported: 1555 * <ul> 1556 * <li> {@link TypedValue#TYPE_STRING}: 1557 * {@link Bundle#putCharSequence Bundle.putCharSequence()} 1558 * <li> {@link TypedValue#TYPE_INT_BOOLEAN}: 1559 * {@link Bundle#putCharSequence Bundle.putBoolean()} 1560 * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}: 1561 * {@link Bundle#putCharSequence Bundle.putBoolean()} 1562 * <li> {@link TypedValue#TYPE_FLOAT}: 1563 * {@link Bundle#putCharSequence Bundle.putFloat()} 1564 * </ul> 1565 * 1566 * @param tagName The name of the tag these attributes come from; this is 1567 * only used for reporting error messages. 1568 * @param attrs The attributes from which to retrieve the name/value pair. 1569 * @param outBundle The Bundle in which to place the parsed value. 1570 * @throws XmlPullParserException If the attributes are not valid. 1571 */ 1572 public void parseBundleExtra(String tagName, AttributeSet attrs, 1573 Bundle outBundle) throws XmlPullParserException { 1574 TypedArray sa = obtainAttributes(attrs, 1575 com.android.internal.R.styleable.Extra); 1576 1577 String name = sa.getString( 1578 com.android.internal.R.styleable.Extra_name); 1579 if (name == null) { 1580 sa.recycle(); 1581 throw new XmlPullParserException("<" + tagName 1582 + "> requires an android:name attribute at " 1583 + attrs.getPositionDescription()); 1584 } 1585 1586 TypedValue v = sa.peekValue( 1587 com.android.internal.R.styleable.Extra_value); 1588 if (v != null) { 1589 if (v.type == TypedValue.TYPE_STRING) { 1590 CharSequence cs = v.coerceToString(); 1591 outBundle.putCharSequence(name, cs); 1592 } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { 1593 outBundle.putBoolean(name, v.data != 0); 1594 } else if (v.type >= TypedValue.TYPE_FIRST_INT 1595 && v.type <= TypedValue.TYPE_LAST_INT) { 1596 outBundle.putInt(name, v.data); 1597 } else if (v.type == TypedValue.TYPE_FLOAT) { 1598 outBundle.putFloat(name, v.getFloat()); 1599 } else { 1600 sa.recycle(); 1601 throw new XmlPullParserException("<" + tagName 1602 + "> only supports string, integer, float, color, and boolean at " 1603 + attrs.getPositionDescription()); 1604 } 1605 } else { 1606 sa.recycle(); 1607 throw new XmlPullParserException("<" + tagName 1608 + "> requires an android:value or android:resource attribute at " 1609 + attrs.getPositionDescription()); 1610 } 1611 1612 sa.recycle(); 1613 } 1614 1615 /** 1616 * Retrieve underlying AssetManager storage for these resources. 1617 */ 1618 public final AssetManager getAssets() { 1619 return mAssets; 1620 } 1621 1622 /** 1623 * Call this to remove all cached loaded layout resources from the 1624 * Resources object. Only intended for use with performance testing 1625 * tools. 1626 */ 1627 public final void flushLayoutCache() { 1628 synchronized (mCachedXmlBlockIds) { 1629 // First see if this block is in our cache. 1630 final int num = mCachedXmlBlockIds.length; 1631 for (int i=0; i<num; i++) { 1632 mCachedXmlBlockIds[i] = -0; 1633 XmlBlock oldBlock = mCachedXmlBlocks[i]; 1634 if (oldBlock != null) { 1635 oldBlock.close(); 1636 } 1637 mCachedXmlBlocks[i] = null; 1638 } 1639 } 1640 } 1641 1642 /** 1643 * Start preloading of resource data using this Resources object. Only 1644 * for use by the zygote process for loading common system resources. 1645 * {@hide} 1646 */ 1647 public final void startPreloading() { 1648 synchronized (mSync) { 1649 if (mPreloaded) { 1650 throw new IllegalStateException("Resources already preloaded"); 1651 } 1652 mPreloaded = true; 1653 mPreloading = true; 1654 } 1655 } 1656 1657 /** 1658 * Called by zygote when it is done preloading resources, to change back 1659 * to normal Resources operation. 1660 */ 1661 public final void finishPreloading() { 1662 if (mPreloading) { 1663 mPreloading = false; 1664 flushLayoutCache(); 1665 } 1666 } 1667 1668 /*package*/ Drawable loadDrawable(TypedValue value, int id) 1669 throws NotFoundException { 1670 1671 if (TRACE_FOR_PRELOAD) { 1672 // Log only framework resources 1673 if ((id >>> 24) == 0x1) { 1674 final String name = getResourceName(id); 1675 if (name != null) android.util.Log.d("PreloadDrawable", name); 1676 } 1677 } 1678 1679 final long key = (((long) value.assetCookie) << 32) | value.data; 1680 boolean isColorDrawable = false; 1681 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 1682 value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 1683 isColorDrawable = true; 1684 } 1685 Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key); 1686 1687 if (dr != null) { 1688 return dr; 1689 } 1690 1691 Drawable.ConstantState cs = isColorDrawable ? sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key); 1692 if (cs != null) { 1693 dr = cs.newDrawable(this); 1694 } else { 1695 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 1696 value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 1697 dr = new ColorDrawable(value.data); 1698 } 1699 1700 if (dr == null) { 1701 if (value.string == null) { 1702 throw new NotFoundException( 1703 "Resource is not a Drawable (color or path): " + value); 1704 } 1705 1706 String file = value.string.toString(); 1707 1708 if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie " 1709 + value.assetCookie + ": " + file); 1710 1711 if (file.endsWith(".xml")) { 1712 try { 1713 XmlResourceParser rp = loadXmlResourceParser( 1714 file, id, value.assetCookie, "drawable"); 1715 dr = Drawable.createFromXml(this, rp); 1716 rp.close(); 1717 } catch (Exception e) { 1718 NotFoundException rnf = new NotFoundException( 1719 "File " + file + " from drawable resource ID #0x" 1720 + Integer.toHexString(id)); 1721 rnf.initCause(e); 1722 throw rnf; 1723 } 1724 1725 } else { 1726 try { 1727 InputStream is = mAssets.openNonAsset( 1728 value.assetCookie, file, AssetManager.ACCESS_STREAMING); 1729 // System.out.println("Opened file " + file + ": " + is); 1730 dr = Drawable.createFromResourceStream(this, value, is, 1731 file, null); 1732 is.close(); 1733 // System.out.println("Created stream: " + dr); 1734 } catch (Exception e) { 1735 NotFoundException rnf = new NotFoundException( 1736 "File " + file + " from drawable resource ID #0x" 1737 + Integer.toHexString(id)); 1738 rnf.initCause(e); 1739 throw rnf; 1740 } 1741 } 1742 } 1743 } 1744 1745 if (dr != null) { 1746 dr.setChangingConfigurations(value.changingConfigurations); 1747 cs = dr.getConstantState(); 1748 if (cs != null) { 1749 if (mPreloading) { 1750 if (isColorDrawable) { 1751 sPreloadedColorDrawables.put(key, cs); 1752 } else { 1753 sPreloadedDrawables.put(key, cs); 1754 } 1755 } else { 1756 synchronized (mTmpValue) { 1757 //Log.i(TAG, "Saving cached drawable @ #" + 1758 // Integer.toHexString(key.intValue()) 1759 // + " in " + this + ": " + cs); 1760 if (isColorDrawable) { 1761 mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); 1762 } else { 1763 mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); 1764 } 1765 } 1766 } 1767 } 1768 } 1769 1770 return dr; 1771 } 1772 1773 private Drawable getCachedDrawable( 1774 LongSparseArray<WeakReference<ConstantState>> drawableCache, 1775 long key) { 1776 synchronized (mTmpValue) { 1777 WeakReference<Drawable.ConstantState> wr = drawableCache.get(key); 1778 if (wr != null) { // we have the key 1779 Drawable.ConstantState entry = wr.get(); 1780 if (entry != null) { 1781 //Log.i(TAG, "Returning cached drawable @ #" + 1782 // Integer.toHexString(((Integer)key).intValue()) 1783 // + " in " + this + ": " + entry); 1784 return entry.newDrawable(); 1785 } 1786 else { // our entry has been purged 1787 drawableCache.delete(key); 1788 } 1789 } 1790 } 1791 return null; 1792 } 1793 1794 /*package*/ ColorStateList loadColorStateList(TypedValue value, int id) 1795 throws NotFoundException { 1796 if (TRACE_FOR_PRELOAD) { 1797 // Log only framework resources 1798 if ((id >>> 24) == 0x1) { 1799 final String name = getResourceName(id); 1800 if (name != null) android.util.Log.d("PreloadColorStateList", name); 1801 } 1802 } 1803 1804 final int key = (value.assetCookie << 24) | value.data; 1805 1806 ColorStateList csl; 1807 1808 if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && 1809 value.type <= TypedValue.TYPE_LAST_COLOR_INT) { 1810 1811 csl = mPreloadedColorStateLists.get(key); 1812 if (csl != null) { 1813 return csl; 1814 } 1815 1816 csl = ColorStateList.valueOf(value.data); 1817 if (mPreloading) { 1818 mPreloadedColorStateLists.put(key, csl); 1819 } 1820 1821 return csl; 1822 } 1823 1824 csl = getCachedColorStateList(key); 1825 if (csl != null) { 1826 return csl; 1827 } 1828 1829 csl = mPreloadedColorStateLists.get(key); 1830 if (csl != null) { 1831 return csl; 1832 } 1833 1834 if (value.string == null) { 1835 throw new NotFoundException( 1836 "Resource is not a ColorStateList (color or path): " + value); 1837 } 1838 1839 String file = value.string.toString(); 1840 1841 if (file.endsWith(".xml")) { 1842 try { 1843 XmlResourceParser rp = loadXmlResourceParser( 1844 file, id, value.assetCookie, "colorstatelist"); 1845 csl = ColorStateList.createFromXml(this, rp); 1846 rp.close(); 1847 } catch (Exception e) { 1848 NotFoundException rnf = new NotFoundException( 1849 "File " + file + " from color state list resource ID #0x" 1850 + Integer.toHexString(id)); 1851 rnf.initCause(e); 1852 throw rnf; 1853 } 1854 } else { 1855 throw new NotFoundException( 1856 "File " + file + " from drawable resource ID #0x" 1857 + Integer.toHexString(id) + ": .xml extension required"); 1858 } 1859 1860 if (csl != null) { 1861 if (mPreloading) { 1862 mPreloadedColorStateLists.put(key, csl); 1863 } else { 1864 synchronized (mTmpValue) { 1865 //Log.i(TAG, "Saving cached color state list @ #" + 1866 // Integer.toHexString(key.intValue()) 1867 // + " in " + this + ": " + csl); 1868 mColorStateListCache.put( 1869 key, new WeakReference<ColorStateList>(csl)); 1870 } 1871 } 1872 } 1873 1874 return csl; 1875 } 1876 1877 private ColorStateList getCachedColorStateList(int key) { 1878 synchronized (mTmpValue) { 1879 WeakReference<ColorStateList> wr = mColorStateListCache.get(key); 1880 if (wr != null) { // we have the key 1881 ColorStateList entry = wr.get(); 1882 if (entry != null) { 1883 //Log.i(TAG, "Returning cached color state list @ #" + 1884 // Integer.toHexString(((Integer)key).intValue()) 1885 // + " in " + this + ": " + entry); 1886 return entry; 1887 } 1888 else { // our entry has been purged 1889 mColorStateListCache.delete(key); 1890 } 1891 } 1892 } 1893 return null; 1894 } 1895 1896 /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type) 1897 throws NotFoundException { 1898 synchronized (mTmpValue) { 1899 TypedValue value = mTmpValue; 1900 getValue(id, value, true); 1901 if (value.type == TypedValue.TYPE_STRING) { 1902 return loadXmlResourceParser(value.string.toString(), id, 1903 value.assetCookie, type); 1904 } 1905 throw new NotFoundException( 1906 "Resource ID #0x" + Integer.toHexString(id) + " type #0x" 1907 + Integer.toHexString(value.type) + " is not valid"); 1908 } 1909 } 1910 1911 /*package*/ XmlResourceParser loadXmlResourceParser(String file, int id, 1912 int assetCookie, String type) throws NotFoundException { 1913 if (id != 0) { 1914 try { 1915 // These may be compiled... 1916 synchronized (mCachedXmlBlockIds) { 1917 // First see if this block is in our cache. 1918 final int num = mCachedXmlBlockIds.length; 1919 for (int i=0; i<num; i++) { 1920 if (mCachedXmlBlockIds[i] == id) { 1921 //System.out.println("**** REUSING XML BLOCK! id=" 1922 // + id + ", index=" + i); 1923 return mCachedXmlBlocks[i].newParser(); 1924 } 1925 } 1926 1927 // Not in the cache, create a new block and put it at 1928 // the next slot in the cache. 1929 XmlBlock block = mAssets.openXmlBlockAsset( 1930 assetCookie, file); 1931 if (block != null) { 1932 int pos = mLastCachedXmlBlockIndex+1; 1933 if (pos >= num) pos = 0; 1934 mLastCachedXmlBlockIndex = pos; 1935 XmlBlock oldBlock = mCachedXmlBlocks[pos]; 1936 if (oldBlock != null) { 1937 oldBlock.close(); 1938 } 1939 mCachedXmlBlockIds[pos] = id; 1940 mCachedXmlBlocks[pos] = block; 1941 //System.out.println("**** CACHING NEW XML BLOCK! id=" 1942 // + id + ", index=" + pos); 1943 return block.newParser(); 1944 } 1945 } 1946 } catch (Exception e) { 1947 NotFoundException rnf = new NotFoundException( 1948 "File " + file + " from xml type " + type + " resource ID #0x" 1949 + Integer.toHexString(id)); 1950 rnf.initCause(e); 1951 throw rnf; 1952 } 1953 } 1954 1955 throw new NotFoundException( 1956 "File " + file + " from xml type " + type + " resource ID #0x" 1957 + Integer.toHexString(id)); 1958 } 1959 1960 /** 1961 * Returns the display adjusted for the Resources' metrics. 1962 * @hide 1963 */ 1964 public Display getDefaultDisplay(Display defaultDisplay) { 1965 if (mDefaultDisplay == null) { 1966 if (!mCompatibilityInfo.isScalingRequired() && mCompatibilityInfo.supportsScreen()) { 1967 // the app supports the display. just use the default one. 1968 mDefaultDisplay = defaultDisplay; 1969 } else { 1970 // display needs adjustment. 1971 mDefaultDisplay = Display.createMetricsBasedDisplay( 1972 defaultDisplay.getDisplayId(), mMetrics); 1973 } 1974 } 1975 return mDefaultDisplay; 1976 } 1977 1978 private TypedArray getCachedStyledAttributes(int len) { 1979 synchronized (mTmpValue) { 1980 TypedArray attrs = mCachedStyledAttributes; 1981 if (attrs != null) { 1982 mCachedStyledAttributes = null; 1983 1984 attrs.mLength = len; 1985 int fullLen = len * AssetManager.STYLE_NUM_ENTRIES; 1986 if (attrs.mData.length >= fullLen) { 1987 return attrs; 1988 } 1989 attrs.mData = new int[fullLen]; 1990 attrs.mIndices = new int[1+len]; 1991 return attrs; 1992 } 1993 return new TypedArray(this, 1994 new int[len*AssetManager.STYLE_NUM_ENTRIES], 1995 new int[1+len], len); 1996 } 1997 } 1998 1999 private Resources() { 2000 mAssets = AssetManager.getSystem(); 2001 // NOTE: Intentionally leaving this uninitialized (all values set 2002 // to zero), so that anyone who tries to do something that requires 2003 // metrics will get a very wrong value. 2004 mConfiguration.setToDefaults(); 2005 mMetrics.setToDefaults(); 2006 updateConfiguration(null, null); 2007 mAssets.ensureStringBlocks(); 2008 mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; 2009 } 2010} 2011