1 /* 2 * Copyright (C) 2008 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 17 package android.content.res; 18 19 import android.annotation.AnyRes; 20 import android.annotation.ColorInt; 21 import android.annotation.Nullable; 22 import android.annotation.StyleableRes; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.ActivityInfo.Config; 25 import android.graphics.drawable.Drawable; 26 import android.os.StrictMode; 27 import android.util.AttributeSet; 28 import android.util.DisplayMetrics; 29 import android.util.TypedValue; 30 31 import com.android.internal.util.XmlUtils; 32 33 import java.util.Arrays; 34 35 /** 36 * Container for an array of values that were retrieved with 37 * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)} 38 * or {@link Resources#obtainAttributes}. Be 39 * sure to call {@link #recycle} when done with them. 40 * 41 * The indices used to retrieve values from this structure correspond to 42 * the positions of the attributes given to obtainStyledAttributes. 43 */ 44 public class TypedArray { 45 obtain(Resources res, int len)46 static TypedArray obtain(Resources res, int len) { 47 final TypedArray attrs = res.mTypedArrayPool.acquire(); 48 if (attrs != null) { 49 attrs.mLength = len; 50 attrs.mRecycled = false; 51 52 // Reset the assets, which may have changed due to configuration changes 53 // or further resource loading. 54 attrs.mAssets = res.getAssets(); 55 56 final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES; 57 if (attrs.mData.length >= fullLen) { 58 return attrs; 59 } 60 61 attrs.mData = new int[fullLen]; 62 attrs.mIndices = new int[1 + len]; 63 return attrs; 64 } 65 66 return new TypedArray(res, 67 new int[len*AssetManager.STYLE_NUM_ENTRIES], 68 new int[1+len], len); 69 } 70 71 private final Resources mResources; 72 private final DisplayMetrics mMetrics; 73 private AssetManager mAssets; 74 75 private boolean mRecycled; 76 77 /*package*/ XmlBlock.Parser mXml; 78 /*package*/ Resources.Theme mTheme; 79 /*package*/ int[] mData; 80 /*package*/ int[] mIndices; 81 /*package*/ int mLength; 82 /*package*/ TypedValue mValue = new TypedValue(); 83 84 /** 85 * Returns the number of values in this array. 86 * 87 * @throws RuntimeException if the TypedArray has already been recycled. 88 */ length()89 public int length() { 90 if (mRecycled) { 91 throw new RuntimeException("Cannot make calls to a recycled instance!"); 92 } 93 94 return mLength; 95 } 96 97 /** 98 * Return the number of indices in the array that actually have data. 99 * 100 * @throws RuntimeException if the TypedArray has already been recycled. 101 */ getIndexCount()102 public int getIndexCount() { 103 if (mRecycled) { 104 throw new RuntimeException("Cannot make calls to a recycled instance!"); 105 } 106 107 return mIndices[0]; 108 } 109 110 /** 111 * Returns an index in the array that has data. 112 * 113 * @param at The index you would like to returned, ranging from 0 to 114 * {@link #getIndexCount()}. 115 * 116 * @return The index at the given offset, which can be used with 117 * {@link #getValue} and related APIs. 118 * @throws RuntimeException if the TypedArray has already been recycled. 119 */ getIndex(int at)120 public int getIndex(int at) { 121 if (mRecycled) { 122 throw new RuntimeException("Cannot make calls to a recycled instance!"); 123 } 124 125 return mIndices[1+at]; 126 } 127 128 /** 129 * Returns the Resources object this array was loaded from. 130 * 131 * @throws RuntimeException if the TypedArray has already been recycled. 132 */ getResources()133 public Resources getResources() { 134 if (mRecycled) { 135 throw new RuntimeException("Cannot make calls to a recycled instance!"); 136 } 137 138 return mResources; 139 } 140 141 /** 142 * Retrieves the styled string value for the attribute at <var>index</var>. 143 * <p> 144 * If the attribute is not a string, this method will attempt to coerce 145 * it to a string. 146 * 147 * @param index Index of attribute to retrieve. 148 * 149 * @return CharSequence holding string data. May be styled. Returns 150 * {@code null} if the attribute is not defined or could not be 151 * coerced to a string. 152 * @throws RuntimeException if the TypedArray has already been recycled. 153 */ getText(@tyleableRes int index)154 public CharSequence getText(@StyleableRes int index) { 155 if (mRecycled) { 156 throw new RuntimeException("Cannot make calls to a recycled instance!"); 157 } 158 159 index *= AssetManager.STYLE_NUM_ENTRIES; 160 final int[] data = mData; 161 final int type = data[index+AssetManager.STYLE_TYPE]; 162 if (type == TypedValue.TYPE_NULL) { 163 return null; 164 } else if (type == TypedValue.TYPE_STRING) { 165 return loadStringValueAt(index); 166 } 167 168 final TypedValue v = mValue; 169 if (getValueAt(index, v)) { 170 return v.coerceToString(); 171 } 172 173 // We already checked for TYPE_NULL. This should never happen. 174 throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type)); 175 } 176 177 /** 178 * Retrieves the string value for the attribute at <var>index</var>. 179 * <p> 180 * If the attribute is not a string, this method will attempt to coerce 181 * it to a string. 182 * 183 * @param index Index of attribute to retrieve. 184 * 185 * @return String holding string data. Any styling information is removed. 186 * Returns {@code null} if the attribute is not defined or could 187 * not be coerced to a string. 188 * @throws RuntimeException if the TypedArray has already been recycled. 189 */ 190 @Nullable getString(@tyleableRes int index)191 public String getString(@StyleableRes int index) { 192 if (mRecycled) { 193 throw new RuntimeException("Cannot make calls to a recycled instance!"); 194 } 195 196 index *= AssetManager.STYLE_NUM_ENTRIES; 197 final int[] data = mData; 198 final int type = data[index+AssetManager.STYLE_TYPE]; 199 if (type == TypedValue.TYPE_NULL) { 200 return null; 201 } else if (type == TypedValue.TYPE_STRING) { 202 return loadStringValueAt(index).toString(); 203 } 204 205 final TypedValue v = mValue; 206 if (getValueAt(index, v)) { 207 final CharSequence cs = v.coerceToString(); 208 return cs != null ? cs.toString() : null; 209 } 210 211 // We already checked for TYPE_NULL. This should never happen. 212 throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type)); 213 } 214 215 /** 216 * Retrieves the string value for the attribute at <var>index</var>, but 217 * only if that string comes from an immediate value in an XML file. That 218 * is, this does not allow references to string resources, string 219 * attributes, or conversions from other types. As such, this method 220 * will only return strings for TypedArray objects that come from 221 * attributes in an XML file. 222 * 223 * @param index Index of attribute to retrieve. 224 * 225 * @return String holding string data. Any styling information is removed. 226 * Returns {@code null} if the attribute is not defined or is not 227 * an immediate string value. 228 * @throws RuntimeException if the TypedArray has already been recycled. 229 */ getNonResourceString(@tyleableRes int index)230 public String getNonResourceString(@StyleableRes int index) { 231 if (mRecycled) { 232 throw new RuntimeException("Cannot make calls to a recycled instance!"); 233 } 234 235 index *= AssetManager.STYLE_NUM_ENTRIES; 236 final int[] data = mData; 237 final int type = data[index+AssetManager.STYLE_TYPE]; 238 if (type == TypedValue.TYPE_STRING) { 239 final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; 240 if (cookie < 0) { 241 return mXml.getPooledString( 242 data[index+AssetManager.STYLE_DATA]).toString(); 243 } 244 } 245 return null; 246 } 247 248 /** 249 * Retrieves the string value for the attribute at <var>index</var> that is 250 * not allowed to change with the given configurations. 251 * 252 * @param index Index of attribute to retrieve. 253 * @param allowedChangingConfigs Bit mask of configurations from 254 * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change. 255 * 256 * @return String holding string data. Any styling information is removed. 257 * Returns {@code null} if the attribute is not defined. 258 * @throws RuntimeException if the TypedArray has already been recycled. 259 * @hide 260 */ getNonConfigurationString(@tyleableRes int index, @Config int allowedChangingConfigs)261 public String getNonConfigurationString(@StyleableRes int index, 262 @Config int allowedChangingConfigs) { 263 if (mRecycled) { 264 throw new RuntimeException("Cannot make calls to a recycled instance!"); 265 } 266 267 index *= AssetManager.STYLE_NUM_ENTRIES; 268 final int[] data = mData; 269 final int type = data[index+AssetManager.STYLE_TYPE]; 270 final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava( 271 data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); 272 if ((changingConfigs & ~allowedChangingConfigs) != 0) { 273 return null; 274 } 275 if (type == TypedValue.TYPE_NULL) { 276 return null; 277 } else if (type == TypedValue.TYPE_STRING) { 278 return loadStringValueAt(index).toString(); 279 } 280 281 final TypedValue v = mValue; 282 if (getValueAt(index, v)) { 283 final CharSequence cs = v.coerceToString(); 284 return cs != null ? cs.toString() : null; 285 } 286 287 // We already checked for TYPE_NULL. This should never happen. 288 throw new RuntimeException("getNonConfigurationString of bad type: 0x" 289 + Integer.toHexString(type)); 290 } 291 292 /** 293 * Retrieve the boolean value for the attribute at <var>index</var>. 294 * <p> 295 * If the attribute is an integer value, this method will return whether 296 * it is equal to zero. If the attribute is not a boolean or integer value, 297 * this method will attempt to coerce it to an integer using 298 * {@link Integer#decode(String)} and return whether it is equal to zero. 299 * 300 * @param index Index of attribute to retrieve. 301 * @param defValue Value to return if the attribute is not defined or 302 * cannot be coerced to an integer. 303 * 304 * @return Boolean value of the attribute, or defValue if the attribute was 305 * not defined or could not be coerced to an integer. 306 * @throws RuntimeException if the TypedArray has already been recycled. 307 */ getBoolean(@tyleableRes int index, boolean defValue)308 public boolean getBoolean(@StyleableRes int index, boolean defValue) { 309 if (mRecycled) { 310 throw new RuntimeException("Cannot make calls to a recycled instance!"); 311 } 312 313 index *= AssetManager.STYLE_NUM_ENTRIES; 314 final int[] data = mData; 315 final int type = data[index+AssetManager.STYLE_TYPE]; 316 if (type == TypedValue.TYPE_NULL) { 317 return defValue; 318 } else if (type >= TypedValue.TYPE_FIRST_INT 319 && type <= TypedValue.TYPE_LAST_INT) { 320 return data[index+AssetManager.STYLE_DATA] != 0; 321 } 322 323 final TypedValue v = mValue; 324 if (getValueAt(index, v)) { 325 StrictMode.noteResourceMismatch(v); 326 return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue); 327 } 328 329 // We already checked for TYPE_NULL. This should never happen. 330 throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type)); 331 } 332 333 /** 334 * Retrieve the integer value for the attribute at <var>index</var>. 335 * <p> 336 * If the attribute is not an integer, this method will attempt to coerce 337 * it to an integer using {@link Integer#decode(String)}. 338 * 339 * @param index Index of attribute to retrieve. 340 * @param defValue Value to return if the attribute is not defined or 341 * cannot be coerced to an integer. 342 * 343 * @return Integer value of the attribute, or defValue if the attribute was 344 * not defined or could not be coerced to an integer. 345 * @throws RuntimeException if the TypedArray has already been recycled. 346 */ getInt(@tyleableRes int index, int defValue)347 public int getInt(@StyleableRes int index, int defValue) { 348 if (mRecycled) { 349 throw new RuntimeException("Cannot make calls to a recycled instance!"); 350 } 351 352 index *= AssetManager.STYLE_NUM_ENTRIES; 353 final int[] data = mData; 354 final int type = data[index+AssetManager.STYLE_TYPE]; 355 if (type == TypedValue.TYPE_NULL) { 356 return defValue; 357 } else if (type >= TypedValue.TYPE_FIRST_INT 358 && type <= TypedValue.TYPE_LAST_INT) { 359 return data[index+AssetManager.STYLE_DATA]; 360 } 361 362 final TypedValue v = mValue; 363 if (getValueAt(index, v)) { 364 StrictMode.noteResourceMismatch(v); 365 return XmlUtils.convertValueToInt(v.coerceToString(), defValue); 366 } 367 368 // We already checked for TYPE_NULL. This should never happen. 369 throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type)); 370 } 371 372 /** 373 * Retrieve the float value for the attribute at <var>index</var>. 374 * <p> 375 * If the attribute is not a float or an integer, this method will attempt 376 * to coerce it to a float using {@link Float#parseFloat(String)}. 377 * 378 * @param index Index of attribute to retrieve. 379 * 380 * @return Attribute float value, or defValue if the attribute was 381 * not defined or could not be coerced to a float. 382 * @throws RuntimeException if the TypedArray has already been recycled. 383 */ getFloat(@tyleableRes int index, float defValue)384 public float getFloat(@StyleableRes int index, float defValue) { 385 if (mRecycled) { 386 throw new RuntimeException("Cannot make calls to a recycled instance!"); 387 } 388 389 index *= AssetManager.STYLE_NUM_ENTRIES; 390 final int[] data = mData; 391 final int type = data[index+AssetManager.STYLE_TYPE]; 392 if (type == TypedValue.TYPE_NULL) { 393 return defValue; 394 } else if (type == TypedValue.TYPE_FLOAT) { 395 return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); 396 } else if (type >= TypedValue.TYPE_FIRST_INT 397 && type <= TypedValue.TYPE_LAST_INT) { 398 return data[index+AssetManager.STYLE_DATA]; 399 } 400 401 final TypedValue v = mValue; 402 if (getValueAt(index, v)) { 403 final CharSequence str = v.coerceToString(); 404 if (str != null) { 405 StrictMode.noteResourceMismatch(v); 406 return Float.parseFloat(str.toString()); 407 } 408 } 409 410 // We already checked for TYPE_NULL. This should never happen. 411 throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type)); 412 } 413 414 /** 415 * Retrieve the color value for the attribute at <var>index</var>. If 416 * the attribute references a color resource holding a complex 417 * {@link android.content.res.ColorStateList}, then the default color from 418 * the set is returned. 419 * <p> 420 * This method will throw an exception if the attribute is defined but is 421 * not an integer color or color state list. 422 * 423 * @param index Index of attribute to retrieve. 424 * @param defValue Value to return if the attribute is not defined or 425 * not a resource. 426 * 427 * @return Attribute color value, or defValue if not defined. 428 * @throws RuntimeException if the TypedArray has already been recycled. 429 * @throws UnsupportedOperationException if the attribute is defined but is 430 * not an integer color or color state list. 431 */ 432 @ColorInt getColor(@tyleableRes int index, @ColorInt int defValue)433 public int getColor(@StyleableRes int index, @ColorInt int defValue) { 434 if (mRecycled) { 435 throw new RuntimeException("Cannot make calls to a recycled instance!"); 436 } 437 438 final int attrIndex = index; 439 index *= AssetManager.STYLE_NUM_ENTRIES; 440 441 final int[] data = mData; 442 final int type = data[index+AssetManager.STYLE_TYPE]; 443 if (type == TypedValue.TYPE_NULL) { 444 return defValue; 445 } else if (type >= TypedValue.TYPE_FIRST_INT 446 && type <= TypedValue.TYPE_LAST_INT) { 447 return data[index+AssetManager.STYLE_DATA]; 448 } else if (type == TypedValue.TYPE_STRING) { 449 final TypedValue value = mValue; 450 if (getValueAt(index, value)) { 451 final ColorStateList csl = mResources.loadColorStateList( 452 value, value.resourceId, mTheme); 453 return csl.getDefaultColor(); 454 } 455 return defValue; 456 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 457 final TypedValue value = mValue; 458 getValueAt(index, value); 459 throw new UnsupportedOperationException( 460 "Failed to resolve attribute at index " + attrIndex + ": " + value); 461 } 462 463 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 464 + " to color: type=0x" + Integer.toHexString(type)); 465 } 466 467 /** 468 * Retrieve the ComplexColor for the attribute at <var>index</var>. 469 * The value may be either a {@link android.content.res.ColorStateList} which can wrap a simple 470 * color value or a {@link android.content.res.GradientColor} 471 * <p> 472 * This method will return {@code null} if the attribute is not defined or 473 * is not an integer color, color state list or GradientColor. 474 * 475 * @param index Index of attribute to retrieve. 476 * 477 * @return ComplexColor for the attribute, or {@code null} if not defined. 478 * @throws RuntimeException if the attribute if the TypedArray has already 479 * been recycled. 480 * @throws UnsupportedOperationException if the attribute is defined but is 481 * not an integer color, color state list or GradientColor. 482 * @hide 483 */ 484 @Nullable getComplexColor(@tyleableRes int index)485 public ComplexColor getComplexColor(@StyleableRes int index) { 486 if (mRecycled) { 487 throw new RuntimeException("Cannot make calls to a recycled instance!"); 488 } 489 490 final TypedValue value = mValue; 491 if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { 492 if (value.type == TypedValue.TYPE_ATTRIBUTE) { 493 throw new UnsupportedOperationException( 494 "Failed to resolve attribute at index " + index + ": " + value); 495 } 496 return mResources.loadComplexColor(value, value.resourceId, mTheme); 497 } 498 return null; 499 } 500 501 /** 502 * Retrieve the ColorStateList for the attribute at <var>index</var>. 503 * The value may be either a single solid color or a reference to 504 * a color or complex {@link android.content.res.ColorStateList} 505 * description. 506 * <p> 507 * This method will return {@code null} if the attribute is not defined or 508 * is not an integer color or color state list. 509 * 510 * @param index Index of attribute to retrieve. 511 * 512 * @return ColorStateList for the attribute, or {@code null} if not 513 * defined. 514 * @throws RuntimeException if the attribute if the TypedArray has already 515 * been recycled. 516 * @throws UnsupportedOperationException if the attribute is defined but is 517 * not an integer color or color state list. 518 */ 519 @Nullable getColorStateList(@tyleableRes int index)520 public ColorStateList getColorStateList(@StyleableRes int index) { 521 if (mRecycled) { 522 throw new RuntimeException("Cannot make calls to a recycled instance!"); 523 } 524 525 final TypedValue value = mValue; 526 if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { 527 if (value.type == TypedValue.TYPE_ATTRIBUTE) { 528 throw new UnsupportedOperationException( 529 "Failed to resolve attribute at index " + index + ": " + value); 530 } 531 return mResources.loadColorStateList(value, value.resourceId, mTheme); 532 } 533 return null; 534 } 535 536 /** 537 * Retrieve the integer value for the attribute at <var>index</var>. 538 * <p> 539 * Unlike {@link #getInt(int, int)}, this method will throw an exception if 540 * the attribute is defined but is not an integer. 541 * 542 * @param index Index of attribute to retrieve. 543 * @param defValue Value to return if the attribute is not defined or 544 * not a resource. 545 * 546 * @return Attribute integer value, or defValue if not defined. 547 * @throws RuntimeException if the TypedArray has already been recycled. 548 * @throws UnsupportedOperationException if the attribute is defined but is 549 * not an integer. 550 */ getInteger(@tyleableRes int index, int defValue)551 public int getInteger(@StyleableRes int index, int defValue) { 552 if (mRecycled) { 553 throw new RuntimeException("Cannot make calls to a recycled instance!"); 554 } 555 556 final int attrIndex = index; 557 index *= AssetManager.STYLE_NUM_ENTRIES; 558 559 final int[] data = mData; 560 final int type = data[index+AssetManager.STYLE_TYPE]; 561 if (type == TypedValue.TYPE_NULL) { 562 return defValue; 563 } else if (type >= TypedValue.TYPE_FIRST_INT 564 && type <= TypedValue.TYPE_LAST_INT) { 565 return data[index+AssetManager.STYLE_DATA]; 566 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 567 final TypedValue value = mValue; 568 getValueAt(index, value); 569 throw new UnsupportedOperationException( 570 "Failed to resolve attribute at index " + attrIndex + ": " + value); 571 } 572 573 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 574 + " to integer: type=0x" + Integer.toHexString(type)); 575 } 576 577 /** 578 * Retrieve a dimensional unit attribute at <var>index</var>. Unit 579 * conversions are based on the current {@link DisplayMetrics} 580 * associated with the resources this {@link TypedArray} object 581 * came from. 582 * <p> 583 * This method will throw an exception if the attribute is defined but is 584 * not a dimension. 585 * 586 * @param index Index of attribute to retrieve. 587 * @param defValue Value to return if the attribute is not defined or 588 * not a resource. 589 * 590 * @return Attribute dimension value multiplied by the appropriate 591 * metric, or defValue if not defined. 592 * @throws RuntimeException if the TypedArray has already been recycled. 593 * @throws UnsupportedOperationException if the attribute is defined but is 594 * not an integer. 595 * 596 * @see #getDimensionPixelOffset 597 * @see #getDimensionPixelSize 598 */ getDimension(@tyleableRes int index, float defValue)599 public float getDimension(@StyleableRes int index, float defValue) { 600 if (mRecycled) { 601 throw new RuntimeException("Cannot make calls to a recycled instance!"); 602 } 603 604 final int attrIndex = index; 605 index *= AssetManager.STYLE_NUM_ENTRIES; 606 607 final int[] data = mData; 608 final int type = data[index+AssetManager.STYLE_TYPE]; 609 if (type == TypedValue.TYPE_NULL) { 610 return defValue; 611 } else if (type == TypedValue.TYPE_DIMENSION) { 612 return TypedValue.complexToDimension( 613 data[index + AssetManager.STYLE_DATA], mMetrics); 614 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 615 final TypedValue value = mValue; 616 getValueAt(index, value); 617 throw new UnsupportedOperationException( 618 "Failed to resolve attribute at index " + attrIndex + ": " + value); 619 } 620 621 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 622 + " to dimension: type=0x" + Integer.toHexString(type)); 623 } 624 625 /** 626 * Retrieve a dimensional unit attribute at <var>index</var> for use 627 * as an offset in raw pixels. This is the same as 628 * {@link #getDimension}, except the returned value is converted to 629 * integer pixels for you. An offset conversion involves simply 630 * truncating the base value to an integer. 631 * <p> 632 * This method will throw an exception if the attribute is defined but is 633 * not a dimension. 634 * 635 * @param index Index of attribute to retrieve. 636 * @param defValue Value to return if the attribute is not defined or 637 * not a resource. 638 * 639 * @return Attribute dimension value multiplied by the appropriate 640 * metric and truncated to integer pixels, or defValue if not defined. 641 * @throws RuntimeException if the TypedArray has already been recycled. 642 * @throws UnsupportedOperationException if the attribute is defined but is 643 * not an integer. 644 * 645 * @see #getDimension 646 * @see #getDimensionPixelSize 647 */ getDimensionPixelOffset(@tyleableRes int index, int defValue)648 public int getDimensionPixelOffset(@StyleableRes int index, int defValue) { 649 if (mRecycled) { 650 throw new RuntimeException("Cannot make calls to a recycled instance!"); 651 } 652 653 final int attrIndex = index; 654 index *= AssetManager.STYLE_NUM_ENTRIES; 655 656 final int[] data = mData; 657 final int type = data[index+AssetManager.STYLE_TYPE]; 658 if (type == TypedValue.TYPE_NULL) { 659 return defValue; 660 } else if (type == TypedValue.TYPE_DIMENSION) { 661 return TypedValue.complexToDimensionPixelOffset( 662 data[index + AssetManager.STYLE_DATA], mMetrics); 663 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 664 final TypedValue value = mValue; 665 getValueAt(index, value); 666 throw new UnsupportedOperationException( 667 "Failed to resolve attribute at index " + attrIndex + ": " + value); 668 } 669 670 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 671 + " to dimension: type=0x" + Integer.toHexString(type)); 672 } 673 674 /** 675 * Retrieve a dimensional unit attribute at <var>index</var> for use 676 * as a size in raw pixels. This is the same as 677 * {@link #getDimension}, except the returned value is converted to 678 * integer pixels for use as a size. A size conversion involves 679 * rounding the base value, and ensuring that a non-zero base value 680 * is at least one pixel in size. 681 * <p> 682 * This method will throw an exception if the attribute is defined but is 683 * not a dimension. 684 * 685 * @param index Index of attribute to retrieve. 686 * @param defValue Value to return if the attribute is not defined or 687 * not a resource. 688 * 689 * @return Attribute dimension value multiplied by the appropriate 690 * metric and truncated to integer pixels, or defValue if not defined. 691 * @throws RuntimeException if the TypedArray has already been recycled. 692 * @throws UnsupportedOperationException if the attribute is defined but is 693 * not a dimension. 694 * 695 * @see #getDimension 696 * @see #getDimensionPixelOffset 697 */ getDimensionPixelSize(@tyleableRes int index, int defValue)698 public int getDimensionPixelSize(@StyleableRes int index, int defValue) { 699 if (mRecycled) { 700 throw new RuntimeException("Cannot make calls to a recycled instance!"); 701 } 702 703 final int attrIndex = index; 704 index *= AssetManager.STYLE_NUM_ENTRIES; 705 706 final int[] data = mData; 707 final int type = data[index+AssetManager.STYLE_TYPE]; 708 if (type == TypedValue.TYPE_NULL) { 709 return defValue; 710 } else if (type == TypedValue.TYPE_DIMENSION) { 711 return TypedValue.complexToDimensionPixelSize( 712 data[index+AssetManager.STYLE_DATA], mMetrics); 713 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 714 final TypedValue value = mValue; 715 getValueAt(index, value); 716 throw new UnsupportedOperationException( 717 "Failed to resolve attribute at index " + attrIndex + ": " + value); 718 } 719 720 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 721 + " to dimension: type=0x" + Integer.toHexString(type)); 722 } 723 724 /** 725 * Special version of {@link #getDimensionPixelSize} for retrieving 726 * {@link android.view.ViewGroup}'s layout_width and layout_height 727 * attributes. This is only here for performance reasons; applications 728 * should use {@link #getDimensionPixelSize}. 729 * <p> 730 * This method will throw an exception if the attribute is defined but is 731 * not a dimension or integer (enum). 732 * 733 * @param index Index of the attribute to retrieve. 734 * @param name Textual name of attribute for error reporting. 735 * 736 * @return Attribute dimension value multiplied by the appropriate 737 * metric and truncated to integer pixels. 738 * @throws RuntimeException if the TypedArray has already been recycled. 739 * @throws UnsupportedOperationException if the attribute is defined but is 740 * not a dimension or integer (enum). 741 */ getLayoutDimension(@tyleableRes int index, String name)742 public int getLayoutDimension(@StyleableRes int index, String name) { 743 if (mRecycled) { 744 throw new RuntimeException("Cannot make calls to a recycled instance!"); 745 } 746 747 final int attrIndex = index; 748 index *= AssetManager.STYLE_NUM_ENTRIES; 749 750 final int[] data = mData; 751 final int type = data[index+AssetManager.STYLE_TYPE]; 752 if (type >= TypedValue.TYPE_FIRST_INT 753 && type <= TypedValue.TYPE_LAST_INT) { 754 return data[index+AssetManager.STYLE_DATA]; 755 } else if (type == TypedValue.TYPE_DIMENSION) { 756 return TypedValue.complexToDimensionPixelSize( 757 data[index+AssetManager.STYLE_DATA], mMetrics); 758 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 759 final TypedValue value = mValue; 760 getValueAt(index, value); 761 throw new UnsupportedOperationException( 762 "Failed to resolve attribute at index " + attrIndex + ": " + value); 763 } 764 765 throw new UnsupportedOperationException(getPositionDescription() 766 + ": You must supply a " + name + " attribute."); 767 } 768 769 /** 770 * Special version of {@link #getDimensionPixelSize} for retrieving 771 * {@link android.view.ViewGroup}'s layout_width and layout_height 772 * attributes. This is only here for performance reasons; applications 773 * should use {@link #getDimensionPixelSize}. 774 * 775 * @param index Index of the attribute to retrieve. 776 * @param defValue The default value to return if this attribute is not 777 * default or contains the wrong type of data. 778 * 779 * @return Attribute dimension value multiplied by the appropriate 780 * metric and truncated to integer pixels. 781 * @throws RuntimeException if the TypedArray has already been recycled. 782 */ getLayoutDimension(@tyleableRes int index, int defValue)783 public int getLayoutDimension(@StyleableRes int index, int defValue) { 784 if (mRecycled) { 785 throw new RuntimeException("Cannot make calls to a recycled instance!"); 786 } 787 788 index *= AssetManager.STYLE_NUM_ENTRIES; 789 final int[] data = mData; 790 final int type = data[index+AssetManager.STYLE_TYPE]; 791 if (type >= TypedValue.TYPE_FIRST_INT 792 && type <= TypedValue.TYPE_LAST_INT) { 793 return data[index+AssetManager.STYLE_DATA]; 794 } else if (type == TypedValue.TYPE_DIMENSION) { 795 return TypedValue.complexToDimensionPixelSize( 796 data[index + AssetManager.STYLE_DATA], mMetrics); 797 } 798 799 return defValue; 800 } 801 802 /** 803 * Retrieves a fractional unit attribute at <var>index</var>. 804 * 805 * @param index Index of attribute to retrieve. 806 * @param base The base value of this fraction. In other words, a 807 * standard fraction is multiplied by this value. 808 * @param pbase The parent base value of this fraction. In other 809 * words, a parent fraction (nn%p) is multiplied by this 810 * value. 811 * @param defValue Value to return if the attribute is not defined or 812 * not a resource. 813 * 814 * @return Attribute fractional value multiplied by the appropriate 815 * base value, or defValue if not defined. 816 * @throws RuntimeException if the TypedArray has already been recycled. 817 * @throws UnsupportedOperationException if the attribute is defined but is 818 * not a fraction. 819 */ getFraction(@tyleableRes int index, int base, int pbase, float defValue)820 public float getFraction(@StyleableRes int index, int base, int pbase, float defValue) { 821 if (mRecycled) { 822 throw new RuntimeException("Cannot make calls to a recycled instance!"); 823 } 824 825 final int attrIndex = index; 826 index *= AssetManager.STYLE_NUM_ENTRIES; 827 828 final int[] data = mData; 829 final int type = data[index+AssetManager.STYLE_TYPE]; 830 if (type == TypedValue.TYPE_NULL) { 831 return defValue; 832 } else if (type == TypedValue.TYPE_FRACTION) { 833 return TypedValue.complexToFraction( 834 data[index+AssetManager.STYLE_DATA], base, pbase); 835 } else if (type == TypedValue.TYPE_ATTRIBUTE) { 836 final TypedValue value = mValue; 837 getValueAt(index, value); 838 throw new UnsupportedOperationException( 839 "Failed to resolve attribute at index " + attrIndex + ": " + value); 840 } 841 842 throw new UnsupportedOperationException("Can't convert value at index " + attrIndex 843 + " to fraction: type=0x" + Integer.toHexString(type)); 844 } 845 846 /** 847 * Retrieves the resource identifier for the attribute at 848 * <var>index</var>. Note that attribute resource as resolved when 849 * the overall {@link TypedArray} object is retrieved. As a 850 * result, this function will return the resource identifier of the 851 * final resource value that was found, <em>not</em> necessarily the 852 * original resource that was specified by the attribute. 853 * 854 * @param index Index of attribute to retrieve. 855 * @param defValue Value to return if the attribute is not defined or 856 * not a resource. 857 * 858 * @return Attribute resource identifier, or defValue if not defined. 859 * @throws RuntimeException if the TypedArray has already been recycled. 860 */ 861 @AnyRes getResourceId(@tyleableRes int index, int defValue)862 public int getResourceId(@StyleableRes int index, int defValue) { 863 if (mRecycled) { 864 throw new RuntimeException("Cannot make calls to a recycled instance!"); 865 } 866 867 index *= AssetManager.STYLE_NUM_ENTRIES; 868 final int[] data = mData; 869 if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { 870 final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; 871 if (resid != 0) { 872 return resid; 873 } 874 } 875 return defValue; 876 } 877 878 /** 879 * Retrieves the theme attribute resource identifier for the attribute at 880 * <var>index</var>. 881 * 882 * @param index Index of attribute to retrieve. 883 * @param defValue Value to return if the attribute is not defined or not a 884 * resource. 885 * 886 * @return Theme attribute resource identifier, or defValue if not defined. 887 * @throws RuntimeException if the TypedArray has already been recycled. 888 * @hide 889 */ getThemeAttributeId(@tyleableRes int index, int defValue)890 public int getThemeAttributeId(@StyleableRes int index, int defValue) { 891 if (mRecycled) { 892 throw new RuntimeException("Cannot make calls to a recycled instance!"); 893 } 894 895 index *= AssetManager.STYLE_NUM_ENTRIES; 896 final int[] data = mData; 897 if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { 898 return data[index + AssetManager.STYLE_DATA]; 899 } 900 return defValue; 901 } 902 903 /** 904 * Retrieve the Drawable for the attribute at <var>index</var>. 905 * <p> 906 * This method will throw an exception if the attribute is defined but is 907 * not a color or drawable resource. 908 * 909 * @param index Index of attribute to retrieve. 910 * 911 * @return Drawable for the attribute, or {@code null} if not defined. 912 * @throws RuntimeException if the TypedArray has already been recycled. 913 * @throws UnsupportedOperationException if the attribute is defined but is 914 * not a color or drawable resource. 915 */ 916 @Nullable getDrawable(@tyleableRes int index)917 public Drawable getDrawable(@StyleableRes int index) { 918 if (mRecycled) { 919 throw new RuntimeException("Cannot make calls to a recycled instance!"); 920 } 921 922 final TypedValue value = mValue; 923 if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { 924 if (value.type == TypedValue.TYPE_ATTRIBUTE) { 925 throw new UnsupportedOperationException( 926 "Failed to resolve attribute at index " + index + ": " + value); 927 } 928 return mResources.loadDrawable(value, value.resourceId, mTheme); 929 } 930 return null; 931 } 932 933 /** 934 * Retrieve the CharSequence[] for the attribute at <var>index</var>. 935 * This gets the resource ID of the selected attribute, and uses 936 * {@link Resources#getTextArray Resources.getTextArray} of the owning 937 * Resources object to retrieve its String[]. 938 * <p> 939 * This method will throw an exception if the attribute is defined but is 940 * not a text array resource. 941 * 942 * @param index Index of attribute to retrieve. 943 * 944 * @return CharSequence[] for the attribute, or {@code null} if not 945 * defined. 946 * @throws RuntimeException if the TypedArray has already been recycled. 947 */ getTextArray(@tyleableRes int index)948 public CharSequence[] getTextArray(@StyleableRes int index) { 949 if (mRecycled) { 950 throw new RuntimeException("Cannot make calls to a recycled instance!"); 951 } 952 953 final TypedValue value = mValue; 954 if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { 955 return mResources.getTextArray(value.resourceId); 956 } 957 return null; 958 } 959 960 /** 961 * Retrieve the raw TypedValue for the attribute at <var>index</var>. 962 * 963 * @param index Index of attribute to retrieve. 964 * @param outValue TypedValue object in which to place the attribute's 965 * data. 966 * 967 * @return {@code true} if the value was retrieved, false otherwise. 968 * @throws RuntimeException if the TypedArray has already been recycled. 969 */ getValue(@tyleableRes int index, TypedValue outValue)970 public boolean getValue(@StyleableRes int index, TypedValue outValue) { 971 if (mRecycled) { 972 throw new RuntimeException("Cannot make calls to a recycled instance!"); 973 } 974 975 return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue); 976 } 977 978 /** 979 * Returns the type of attribute at the specified index. 980 * 981 * @param index Index of attribute whose type to retrieve. 982 * 983 * @return Attribute type. 984 * @throws RuntimeException if the TypedArray has already been recycled. 985 */ getType(@tyleableRes int index)986 public int getType(@StyleableRes int index) { 987 if (mRecycled) { 988 throw new RuntimeException("Cannot make calls to a recycled instance!"); 989 } 990 991 index *= AssetManager.STYLE_NUM_ENTRIES; 992 return mData[index + AssetManager.STYLE_TYPE]; 993 } 994 995 /** 996 * Determines whether there is an attribute at <var>index</var>. 997 * <p> 998 * <strong>Note:</strong> If the attribute was set to {@code @empty} or 999 * {@code @undefined}, this method returns {@code false}. 1000 * 1001 * @param index Index of attribute to retrieve. 1002 * 1003 * @return True if the attribute has a value, false otherwise. 1004 * @throws RuntimeException if the TypedArray has already been recycled. 1005 */ hasValue(@tyleableRes int index)1006 public boolean hasValue(@StyleableRes int index) { 1007 if (mRecycled) { 1008 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1009 } 1010 1011 index *= AssetManager.STYLE_NUM_ENTRIES; 1012 final int[] data = mData; 1013 final int type = data[index+AssetManager.STYLE_TYPE]; 1014 return type != TypedValue.TYPE_NULL; 1015 } 1016 1017 /** 1018 * Determines whether there is an attribute at <var>index</var>, returning 1019 * {@code true} if the attribute was explicitly set to {@code @empty} and 1020 * {@code false} only if the attribute was undefined. 1021 * 1022 * @param index Index of attribute to retrieve. 1023 * 1024 * @return True if the attribute has a value or is empty, false otherwise. 1025 * @throws RuntimeException if the TypedArray has already been recycled. 1026 */ hasValueOrEmpty(@tyleableRes int index)1027 public boolean hasValueOrEmpty(@StyleableRes int index) { 1028 if (mRecycled) { 1029 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1030 } 1031 1032 index *= AssetManager.STYLE_NUM_ENTRIES; 1033 final int[] data = mData; 1034 final int type = data[index+AssetManager.STYLE_TYPE]; 1035 return type != TypedValue.TYPE_NULL 1036 || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; 1037 } 1038 1039 /** 1040 * Retrieve the raw TypedValue for the attribute at <var>index</var> 1041 * and return a temporary object holding its data. This object is only 1042 * valid until the next call on to {@link TypedArray}. 1043 * 1044 * @param index Index of attribute to retrieve. 1045 * 1046 * @return Returns a TypedValue object if the attribute is defined, 1047 * containing its data; otherwise returns null. (You will not 1048 * receive a TypedValue whose type is TYPE_NULL.) 1049 * @throws RuntimeException if the TypedArray has already been recycled. 1050 */ peekValue(@tyleableRes int index)1051 public TypedValue peekValue(@StyleableRes int index) { 1052 if (mRecycled) { 1053 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1054 } 1055 1056 final TypedValue value = mValue; 1057 if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { 1058 return value; 1059 } 1060 return null; 1061 } 1062 1063 /** 1064 * Returns a message about the parser state suitable for printing error messages. 1065 * 1066 * @return Human-readable description of current parser state. 1067 * @throws RuntimeException if the TypedArray has already been recycled. 1068 */ getPositionDescription()1069 public String getPositionDescription() { 1070 if (mRecycled) { 1071 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1072 } 1073 1074 return mXml != null ? mXml.getPositionDescription() : "<internal>"; 1075 } 1076 1077 /** 1078 * Recycles the TypedArray, to be re-used by a later caller. After calling 1079 * this function you must not ever touch the typed array again. 1080 * 1081 * @throws RuntimeException if the TypedArray has already been recycled. 1082 */ recycle()1083 public void recycle() { 1084 if (mRecycled) { 1085 throw new RuntimeException(toString() + " recycled twice!"); 1086 } 1087 1088 mRecycled = true; 1089 1090 // These may have been set by the client. 1091 mXml = null; 1092 mTheme = null; 1093 mAssets = null; 1094 1095 mResources.mTypedArrayPool.release(this); 1096 } 1097 1098 /** 1099 * Extracts theme attributes from a typed array for later resolution using 1100 * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}. 1101 * Removes the entries from the typed array so that subsequent calls to typed 1102 * getters will return the default value without crashing. 1103 * 1104 * @return an array of length {@link #getIndexCount()} populated with theme 1105 * attributes, or null if there are no theme attributes in the typed 1106 * array 1107 * @throws RuntimeException if the TypedArray has already been recycled. 1108 * @hide 1109 */ 1110 @Nullable extractThemeAttrs()1111 public int[] extractThemeAttrs() { 1112 return extractThemeAttrs(null); 1113 } 1114 1115 /** 1116 * @hide 1117 */ 1118 @Nullable extractThemeAttrs(@ullable int[] scrap)1119 public int[] extractThemeAttrs(@Nullable int[] scrap) { 1120 if (mRecycled) { 1121 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1122 } 1123 1124 int[] attrs = null; 1125 1126 final int[] data = mData; 1127 final int N = length(); 1128 for (int i = 0; i < N; i++) { 1129 final int index = i * AssetManager.STYLE_NUM_ENTRIES; 1130 if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { 1131 // Not an attribute, ignore. 1132 continue; 1133 } 1134 1135 // Null the entry so that we can safely call getZzz(). 1136 data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL; 1137 1138 final int attr = data[index + AssetManager.STYLE_DATA]; 1139 if (attr == 0) { 1140 // Useless data, ignore. 1141 continue; 1142 } 1143 1144 // Ensure we have a usable attribute array. 1145 if (attrs == null) { 1146 if (scrap != null && scrap.length == N) { 1147 attrs = scrap; 1148 Arrays.fill(attrs, 0); 1149 } else { 1150 attrs = new int[N]; 1151 } 1152 } 1153 1154 attrs[i] = attr; 1155 } 1156 1157 return attrs; 1158 } 1159 1160 /** 1161 * Return a mask of the configuration parameters for which the values in 1162 * this typed array may change. 1163 * 1164 * @return Returns a mask of the changing configuration parameters, as 1165 * defined by {@link android.content.pm.ActivityInfo}. 1166 * @throws RuntimeException if the TypedArray has already been recycled. 1167 * @see android.content.pm.ActivityInfo 1168 */ getChangingConfigurations()1169 public @Config int getChangingConfigurations() { 1170 if (mRecycled) { 1171 throw new RuntimeException("Cannot make calls to a recycled instance!"); 1172 } 1173 1174 @Config int changingConfig = 0; 1175 1176 final int[] data = mData; 1177 final int N = length(); 1178 for (int i = 0; i < N; i++) { 1179 final int index = i * AssetManager.STYLE_NUM_ENTRIES; 1180 final int type = data[index + AssetManager.STYLE_TYPE]; 1181 if (type == TypedValue.TYPE_NULL) { 1182 continue; 1183 } 1184 changingConfig |= ActivityInfo.activityInfoConfigNativeToJava( 1185 data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); 1186 } 1187 return changingConfig; 1188 } 1189 getValueAt(int index, TypedValue outValue)1190 private boolean getValueAt(int index, TypedValue outValue) { 1191 final int[] data = mData; 1192 final int type = data[index+AssetManager.STYLE_TYPE]; 1193 if (type == TypedValue.TYPE_NULL) { 1194 return false; 1195 } 1196 outValue.type = type; 1197 outValue.data = data[index+AssetManager.STYLE_DATA]; 1198 outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; 1199 outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; 1200 outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( 1201 data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); 1202 outValue.density = data[index+AssetManager.STYLE_DENSITY]; 1203 outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; 1204 return true; 1205 } 1206 loadStringValueAt(int index)1207 private CharSequence loadStringValueAt(int index) { 1208 final int[] data = mData; 1209 final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; 1210 if (cookie < 0) { 1211 if (mXml != null) { 1212 return mXml.getPooledString( 1213 data[index+AssetManager.STYLE_DATA]); 1214 } 1215 return null; 1216 } 1217 return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); 1218 } 1219 TypedArray(Resources resources, int[] data, int[] indices, int len)1220 /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) { 1221 mResources = resources; 1222 mMetrics = mResources.getDisplayMetrics(); 1223 mAssets = mResources.getAssets(); 1224 mData = data; 1225 mIndices = indices; 1226 mLength = len; 1227 } 1228 1229 @Override toString()1230 public String toString() { 1231 return Arrays.toString(mData); 1232 } 1233 } 1234