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 com.android.ide.common.rendering.api.ArrayResourceValue; 20 import com.android.ide.common.rendering.api.AttrResourceValue; 21 import com.android.ide.common.rendering.api.LayoutLog; 22 import com.android.ide.common.rendering.api.RenderResources; 23 import com.android.ide.common.rendering.api.ResourceValue; 24 import com.android.ide.common.rendering.api.StyleResourceValue; 25 import com.android.internal.util.XmlUtils; 26 import com.android.layoutlib.bridge.Bridge; 27 import com.android.layoutlib.bridge.android.BridgeContext; 28 import com.android.layoutlib.bridge.impl.ResourceHelper; 29 import com.android.resources.ResourceType; 30 31 import android.annotation.Nullable; 32 import android.content.res.Resources.Theme; 33 import android.graphics.Typeface; 34 import android.graphics.Typeface_Accessor; 35 import android.graphics.drawable.Drawable; 36 import android.util.DisplayMetrics; 37 import android.util.TypedValue; 38 import android.view.LayoutInflater_Delegate; 39 import android.view.ViewGroup.LayoutParams; 40 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Map; 44 45 import static android.util.TypedValue.TYPE_ATTRIBUTE; 46 import static android.util.TypedValue.TYPE_DIMENSION; 47 import static android.util.TypedValue.TYPE_FLOAT; 48 import static android.util.TypedValue.TYPE_INT_BOOLEAN; 49 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4; 50 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; 51 import static android.util.TypedValue.TYPE_INT_COLOR_RGB4; 52 import static android.util.TypedValue.TYPE_INT_COLOR_RGB8; 53 import static android.util.TypedValue.TYPE_INT_DEC; 54 import static android.util.TypedValue.TYPE_INT_HEX; 55 import static android.util.TypedValue.TYPE_NULL; 56 import static android.util.TypedValue.TYPE_REFERENCE; 57 import static android.util.TypedValue.TYPE_STRING; 58 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 59 import static com.android.SdkConstants.PREFIX_THEME_REF; 60 import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY; 61 import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL; 62 import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED; 63 64 /** 65 * Custom implementation of TypedArray to handle non compiled resources. 66 */ 67 public final class BridgeTypedArray extends TypedArray { 68 69 private final Resources mBridgeResources; 70 private final BridgeContext mContext; 71 private final boolean mPlatformFile; 72 73 private final int[] mResourceId; 74 private final ResourceValue[] mResourceData; 75 private final String[] mNames; 76 private final boolean[] mIsFramework; 77 78 // Contains ids that are @empty. We still store null in mResourceData for that index, since we 79 // want to save on the check against empty, each time a resource value is requested. 80 @Nullable 81 private int[] mEmptyIds; 82 BridgeTypedArray(Resources resources, BridgeContext context, int len, boolean platformFile)83 public BridgeTypedArray(Resources resources, BridgeContext context, int len, 84 boolean platformFile) { 85 super(resources); 86 mBridgeResources = resources; 87 mContext = context; 88 mPlatformFile = platformFile; 89 mResourceId = new int[len]; 90 mResourceData = new ResourceValue[len]; 91 mNames = new String[len]; 92 mIsFramework = new boolean[len]; 93 } 94 95 /** 96 * A bridge-specific method that sets a value in the type array 97 * @param index the index of the value in the TypedArray 98 * @param name the name of the attribute 99 * @param isFramework whether the attribute is in the android namespace. 100 * @param resourceId the reference id of this resource 101 * @param value the value of the attribute 102 */ bridgeSetValue(int index, String name, boolean isFramework, int resourceId, ResourceValue value)103 public void bridgeSetValue(int index, String name, boolean isFramework, int resourceId, 104 ResourceValue value) { 105 mResourceId[index] = resourceId; 106 mResourceData[index] = value; 107 mNames[index] = name; 108 mIsFramework[index] = isFramework; 109 } 110 111 /** 112 * Seals the array after all calls to 113 * {@link #bridgeSetValue(int, String, boolean, int, ResourceValue)} have been done. 114 * <p/>This allows to compute the list of non default values, permitting 115 * {@link #getIndexCount()} to return the proper value. 116 */ sealArray()117 public void sealArray() { 118 // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt 119 // first count the array size 120 int count = 0; 121 ArrayList<Integer> emptyIds = null; 122 for (int i = 0; i < mResourceData.length; i++) { 123 ResourceValue data = mResourceData[i]; 124 if (data != null) { 125 String dataValue = data.getValue(); 126 if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) { 127 mResourceData[i] = null; 128 } else if (REFERENCE_EMPTY.equals(dataValue)) { 129 mResourceData[i] = null; 130 if (emptyIds == null) { 131 emptyIds = new ArrayList<Integer>(4); 132 } 133 emptyIds.add(i); 134 } else { 135 count++; 136 } 137 } 138 } 139 140 if (emptyIds != null) { 141 mEmptyIds = new int[emptyIds.size()]; 142 for (int i = 0; i < emptyIds.size(); i++) { 143 mEmptyIds[i] = emptyIds.get(i); 144 } 145 } 146 147 // allocate the table with an extra to store the size 148 mIndices = new int[count+1]; 149 mIndices[0] = count; 150 151 // fill the array with the indices. 152 int index = 1; 153 for (int i = 0 ; i < mResourceData.length ; i++) { 154 if (mResourceData[i] != null) { 155 mIndices[index++] = i; 156 } 157 } 158 } 159 160 /** 161 * Set the theme to be used for inflating drawables. 162 */ setTheme(Theme theme)163 public void setTheme(Theme theme) { 164 mTheme = theme; 165 } 166 167 /** 168 * Return the number of values in this array. 169 */ 170 @Override length()171 public int length() { 172 return mResourceData.length; 173 } 174 175 /** 176 * Return the Resources object this array was loaded from. 177 */ 178 @Override getResources()179 public Resources getResources() { 180 return mBridgeResources; 181 } 182 183 /** 184 * Retrieve the styled string value for the attribute at <var>index</var>. 185 * 186 * @param index Index of attribute to retrieve. 187 * 188 * @return CharSequence holding string data. May be styled. Returns 189 * null if the attribute is not defined. 190 */ 191 @Override getText(int index)192 public CharSequence getText(int index) { 193 // FIXME: handle styled strings! 194 return getString(index); 195 } 196 197 /** 198 * Retrieve the string value for the attribute at <var>index</var>. 199 * 200 * @param index Index of attribute to retrieve. 201 * 202 * @return String holding string data. Any styling information is 203 * removed. Returns null if the attribute is not defined. 204 */ 205 @Override getString(int index)206 public String getString(int index) { 207 if (!hasValue(index)) { 208 return null; 209 } 210 // As unfortunate as it is, it's possible to use enums with all attribute formats, 211 // not just integers/enums. So, we need to search the enums always. In case 212 // enums are used, the returned value is an integer. 213 Integer v = resolveEnumAttribute(index); 214 return v == null ? mResourceData[index].getValue() : String.valueOf((int) v); 215 } 216 217 /** 218 * Retrieve the boolean value for the attribute at <var>index</var>. 219 * 220 * @param index Index of attribute to retrieve. 221 * @param defValue Value to return if the attribute is not defined. 222 * 223 * @return Attribute boolean value, or defValue if not defined. 224 */ 225 @Override getBoolean(int index, boolean defValue)226 public boolean getBoolean(int index, boolean defValue) { 227 String s = getString(index); 228 return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue); 229 230 } 231 232 /** 233 * Retrieve the integer value for the attribute at <var>index</var>. 234 * 235 * @param index Index of attribute to retrieve. 236 * @param defValue Value to return if the attribute is not defined. 237 * 238 * @return Attribute int value, or defValue if not defined. 239 */ 240 @Override getInt(int index, int defValue)241 public int getInt(int index, int defValue) { 242 String s = getString(index); 243 try { 244 return convertValueToInt(s, defValue); 245 } catch (NumberFormatException e) { 246 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 247 String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer", 248 s, mNames[index]), 249 null); 250 } 251 return defValue; 252 } 253 254 /** 255 * Retrieve the float value for the attribute at <var>index</var>. 256 * 257 * @param index Index of attribute to retrieve. 258 * 259 * @return Attribute float value, or defValue if not defined.. 260 */ 261 @Override getFloat(int index, float defValue)262 public float getFloat(int index, float defValue) { 263 String s = getString(index); 264 try { 265 if (s != null) { 266 return Float.parseFloat(s); 267 } 268 } catch (NumberFormatException e) { 269 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 270 String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.", 271 s, mNames[index]), 272 null); 273 } 274 return defValue; 275 } 276 277 /** 278 * Retrieve the color value for the attribute at <var>index</var>. If 279 * the attribute references a color resource holding a complex 280 * {@link android.content.res.ColorStateList}, then the default color from 281 * the set is returned. 282 * 283 * @param index Index of attribute to retrieve. 284 * @param defValue Value to return if the attribute is not defined or 285 * not a resource. 286 * 287 * @return Attribute color value, or defValue if not defined. 288 */ 289 @Override getColor(int index, int defValue)290 public int getColor(int index, int defValue) { 291 if (index < 0 || index >= mResourceData.length) { 292 return defValue; 293 } 294 295 if (mResourceData[index] == null) { 296 return defValue; 297 } 298 299 ColorStateList colorStateList = ResourceHelper.getColorStateList( 300 mResourceData[index], mContext, mTheme); 301 if (colorStateList != null) { 302 return colorStateList.getDefaultColor(); 303 } 304 305 return defValue; 306 } 307 308 @Override getColorStateList(int index)309 public ColorStateList getColorStateList(int index) { 310 if (!hasValue(index)) { 311 return null; 312 } 313 314 return ResourceHelper.getColorStateList(mResourceData[index], mContext, mTheme); 315 } 316 317 @Override getComplexColor(int index)318 public ComplexColor getComplexColor(int index) { 319 if (!hasValue(index)) { 320 return null; 321 } 322 323 return ResourceHelper.getComplexColor(mResourceData[index], mContext, mTheme); 324 } 325 326 /** 327 * Retrieve the integer value for the attribute at <var>index</var>. 328 * 329 * @param index Index of attribute to retrieve. 330 * @param defValue Value to return if the attribute is not defined or 331 * not a resource. 332 * 333 * @return Attribute integer value, or defValue if not defined. 334 */ 335 @Override getInteger(int index, int defValue)336 public int getInteger(int index, int defValue) { 337 return getInt(index, defValue); 338 } 339 340 /** 341 * Retrieve a dimensional unit attribute at <var>index</var>. Unit 342 * conversions are based on the current {@link DisplayMetrics} 343 * associated with the resources this {@link TypedArray} object 344 * came from. 345 * 346 * @param index Index of attribute to retrieve. 347 * @param defValue Value to return if the attribute is not defined or 348 * not a resource. 349 * 350 * @return Attribute dimension value multiplied by the appropriate 351 * metric, or defValue if not defined. 352 * 353 * @see #getDimensionPixelOffset 354 * @see #getDimensionPixelSize 355 */ 356 @Override getDimension(int index, float defValue)357 public float getDimension(int index, float defValue) { 358 String s = getString(index); 359 if (s == null) { 360 return defValue; 361 } 362 // Check if the value is a magic constant that doesn't require a unit. 363 try { 364 int i = Integer.parseInt(s); 365 if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { 366 return i; 367 } 368 } catch (NumberFormatException ignored) { 369 // pass 370 } 371 372 if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { 373 return mValue.getDimension(mBridgeResources.getDisplayMetrics()); 374 } 375 376 return defValue; 377 } 378 379 /** 380 * Retrieve a dimensional unit attribute at <var>index</var> for use 381 * as an offset in raw pixels. This is the same as 382 * {@link #getDimension}, except the returned value is converted to 383 * integer pixels for you. An offset conversion involves simply 384 * truncating the base value to an integer. 385 * 386 * @param index Index of attribute to retrieve. 387 * @param defValue Value to return if the attribute is not defined or 388 * not a resource. 389 * 390 * @return Attribute dimension value multiplied by the appropriate 391 * metric and truncated to integer pixels, or defValue if not defined. 392 * 393 * @see #getDimension 394 * @see #getDimensionPixelSize 395 */ 396 @Override getDimensionPixelOffset(int index, int defValue)397 public int getDimensionPixelOffset(int index, int defValue) { 398 return (int) getDimension(index, defValue); 399 } 400 401 /** 402 * Retrieve a dimensional unit attribute at <var>index</var> for use 403 * as a size in raw pixels. This is the same as 404 * {@link #getDimension}, except the returned value is converted to 405 * integer pixels for use as a size. A size conversion involves 406 * rounding the base value, and ensuring that a non-zero base value 407 * is at least one pixel in size. 408 * 409 * @param index Index of attribute to retrieve. 410 * @param defValue Value to return if the attribute is not defined or 411 * not a resource. 412 * 413 * @return Attribute dimension value multiplied by the appropriate 414 * metric and truncated to integer pixels, or defValue if not defined. 415 * 416 * @see #getDimension 417 * @see #getDimensionPixelOffset 418 */ 419 @Override getDimensionPixelSize(int index, int defValue)420 public int getDimensionPixelSize(int index, int defValue) { 421 try { 422 return getDimension(index, null); 423 } catch (RuntimeException e) { 424 String s = getString(index); 425 426 if (s != null) { 427 // looks like we were unable to resolve the dimension value 428 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 429 String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.", 430 s, mNames[index]), null); 431 } 432 433 return defValue; 434 } 435 } 436 437 /** 438 * Special version of {@link #getDimensionPixelSize} for retrieving 439 * {@link android.view.ViewGroup}'s layout_width and layout_height 440 * attributes. This is only here for performance reasons; applications 441 * should use {@link #getDimensionPixelSize}. 442 * 443 * @param index Index of the attribute to retrieve. 444 * @param name Textual name of attribute for error reporting. 445 * 446 * @return Attribute dimension value multiplied by the appropriate 447 * metric and truncated to integer pixels. 448 */ 449 @Override getLayoutDimension(int index, String name)450 public int getLayoutDimension(int index, String name) { 451 try { 452 // this will throw an exception if not found. 453 return getDimension(index, name); 454 } catch (RuntimeException e) { 455 456 if (LayoutInflater_Delegate.sIsInInclude) { 457 throw new RuntimeException("Layout Dimension '" + name + "' not found."); 458 } 459 460 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 461 "You must supply a " + name + " attribute.", null); 462 463 return 0; 464 } 465 } 466 467 @Override getLayoutDimension(int index, int defValue)468 public int getLayoutDimension(int index, int defValue) { 469 return getDimensionPixelSize(index, defValue); 470 } 471 472 /** @param name attribute name, used for error reporting. */ getDimension(int index, @Nullable String name)473 private int getDimension(int index, @Nullable String name) { 474 String s = getString(index); 475 if (s == null) { 476 if (name != null) { 477 throw new RuntimeException("Attribute '" + name + "' not found"); 478 } 479 throw new RuntimeException(); 480 } 481 // Check if the value is a magic constant that doesn't require a unit. 482 try { 483 int i = Integer.parseInt(s); 484 if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { 485 return i; 486 } 487 } catch (NumberFormatException ignored) { 488 // pass 489 } 490 if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { 491 float f = mValue.getDimension(mBridgeResources.getDisplayMetrics()); 492 493 final int res = (int)(f+0.5f); 494 if (res != 0) return res; 495 if (f == 0) return 0; 496 if (f > 0) return 1; 497 } 498 499 throw new RuntimeException(); 500 } 501 502 /** 503 * Retrieve a fractional unit attribute at <var>index</var>. 504 * 505 * @param index Index of attribute to retrieve. 506 * @param base The base value of this fraction. In other words, a 507 * standard fraction is multiplied by this value. 508 * @param pbase The parent base value of this fraction. In other 509 * words, a parent fraction (nn%p) is multiplied by this 510 * value. 511 * @param defValue Value to return if the attribute is not defined or 512 * not a resource. 513 * 514 * @return Attribute fractional value multiplied by the appropriate 515 * base value, or defValue if not defined. 516 */ 517 @Override getFraction(int index, int base, int pbase, float defValue)518 public float getFraction(int index, int base, int pbase, float defValue) { 519 String value = getString(index); 520 if (value == null) { 521 return defValue; 522 } 523 524 if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) { 525 return mValue.getFraction(base, pbase); 526 } 527 528 // looks like we were unable to resolve the fraction value 529 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 530 String.format( 531 "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", 532 value, mNames[index]), null); 533 534 return defValue; 535 } 536 537 /** 538 * Retrieve the resource identifier for the attribute at 539 * <var>index</var>. Note that attribute resource as resolved when 540 * the overall {@link TypedArray} object is retrieved. As a 541 * result, this function will return the resource identifier of the 542 * final resource value that was found, <em>not</em> necessarily the 543 * original resource that was specified by the attribute. 544 * 545 * @param index Index of attribute to retrieve. 546 * @param defValue Value to return if the attribute is not defined or 547 * not a resource. 548 * 549 * @return Attribute resource identifier, or defValue if not defined. 550 */ 551 @Override getResourceId(int index, int defValue)552 public int getResourceId(int index, int defValue) { 553 if (index < 0 || index >= mResourceData.length) { 554 return defValue; 555 } 556 557 // get the Resource for this index 558 ResourceValue resValue = mResourceData[index]; 559 560 // no data, return the default value. 561 if (resValue == null) { 562 return defValue; 563 } 564 565 // check if this is a style resource 566 if (resValue instanceof StyleResourceValue) { 567 // get the id that will represent this style. 568 return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); 569 } 570 571 // if the attribute was a reference to a resource, and not a declaration of an id (@+id), 572 // then the xml attribute value was "resolved" which leads us to a ResourceValue with a 573 // valid getType() and getName() returning a resource name. 574 // (and getValue() returning null!). We need to handle this! 575 if (resValue.getResourceType() != null) { 576 // if this is a framework id 577 if (mPlatformFile || resValue.isFramework()) { 578 // look for idName in the android R classes 579 return mContext.getFrameworkResourceValue( 580 resValue.getResourceType(), resValue.getName(), defValue); 581 } 582 583 // look for idName in the project R class. 584 return mContext.getProjectResourceValue( 585 resValue.getResourceType(), resValue.getName(), defValue); 586 } 587 588 // else, try to get the value, and resolve it somehow. 589 String value = resValue.getValue(); 590 if (value == null) { 591 return defValue; 592 } 593 value = value.trim(); 594 595 // if the value is just an integer, return it. 596 try { 597 int i = Integer.parseInt(value); 598 if (Integer.toString(i).equals(value)) { 599 return i; 600 } 601 } catch (NumberFormatException e) { 602 // pass 603 } 604 605 if (value.startsWith("#")) { 606 // this looks like a color, do not try to parse it 607 return defValue; 608 } 609 610 if (Typeface_Accessor.isSystemFont(value)) { 611 // A system font family value, do not try to parse 612 return defValue; 613 } 614 615 // Handle the @id/<name>, @+id/<name> and @android:id/<name> 616 // We need to return the exact value that was compiled (from the various R classes), 617 // as these values can be reused internally with calls to findViewById(). 618 // There's a trick with platform layouts that not use "android:" but their IDs are in 619 // fact in the android.R and com.android.internal.R classes. 620 // The field mPlatformFile will indicate that all IDs are to be looked up in the android R 621 // classes exclusively. 622 623 // if this is a reference to an id, find it. 624 if (value.startsWith("@id/") || value.startsWith("@+") || 625 value.startsWith("@android:id/")) { 626 627 int pos = value.indexOf('/'); 628 String idName = value.substring(pos + 1); 629 boolean create = value.startsWith("@+"); 630 boolean isFrameworkId = 631 mPlatformFile || value.startsWith("@android") || value.startsWith("@+android"); 632 633 // Look for the idName in project or android R class depending on isPlatform. 634 if (create) { 635 Integer idValue; 636 if (isFrameworkId) { 637 idValue = Bridge.getResourceId(ResourceType.ID, idName); 638 } else { 639 idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName); 640 } 641 return idValue == null ? defValue : idValue; 642 } 643 // This calls the same method as in if(create), but doesn't create a dynamic id, if 644 // one is not found. 645 if (isFrameworkId) { 646 return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); 647 } else { 648 return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); 649 } 650 } 651 else if (value.startsWith("@aapt:_aapt")) { 652 return mContext.getLayoutlibCallback().getResourceId(ResourceType.AAPT, value); 653 } 654 655 // not a direct id valid reference. First check if it's an enum (this is a corner case 656 // for attributes that have a reference|enum type), then fallback to resolve 657 // as an ID without prefix. 658 Integer enumValue = resolveEnumAttribute(index); 659 if (enumValue != null) { 660 return enumValue; 661 } 662 663 // Ok, not an enum, resolve as an ID 664 Integer idValue; 665 666 if (resValue.isFramework()) { 667 idValue = Bridge.getResourceId(resValue.getResourceType(), 668 resValue.getName()); 669 } else { 670 idValue = mContext.getLayoutlibCallback().getResourceId( 671 resValue.getResourceType(), resValue.getName()); 672 } 673 674 if (idValue != null) { 675 return idValue; 676 } 677 678 if ("text".equals(mNames[index])) { 679 // In a TextView, if the text is set from the attribute android:text, the correct 680 // behaviour is not to find a resourceId for the text, and to return the default value. 681 // So in this case, do not log a warning. 682 return defValue; 683 } 684 685 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, 686 String.format( 687 "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]), 688 resValue); 689 690 return defValue; 691 } 692 693 @Override getThemeAttributeId(int index, int defValue)694 public int getThemeAttributeId(int index, int defValue) { 695 // TODO: Get the right Theme Attribute ID to enable caching of the drawables. 696 return defValue; 697 } 698 699 /** 700 * Retrieve the Drawable for the attribute at <var>index</var>. This 701 * gets the resource ID of the selected attribute, and uses 702 * {@link Resources#getDrawable Resources.getDrawable} of the owning 703 * Resources object to retrieve its Drawable. 704 * 705 * @param index Index of attribute to retrieve. 706 * 707 * @return Drawable for the attribute, or null if not defined. 708 */ 709 @Override getDrawable(int index)710 public Drawable getDrawable(int index) { 711 if (!hasValue(index)) { 712 return null; 713 } 714 715 ResourceValue value = mResourceData[index]; 716 return ResourceHelper.getDrawable(value, mContext, mTheme); 717 } 718 719 /** 720 * Version of {@link #getDrawable(int)} that accepts an override density. 721 * @hide 722 */ 723 @Override getDrawableForDensity(int index, int density)724 public Drawable getDrawableForDensity(int index, int density) { 725 return getDrawable(index); 726 } 727 728 /** 729 * Retrieve the Typeface for the attribute at <var>index</var>. 730 * @param index Index of attribute to retrieve. 731 * 732 * @return Typeface for the attribute, or null if not defined. 733 */ 734 @Override getFont(int index)735 public Typeface getFont(int index) { 736 if (!hasValue(index)) { 737 return null; 738 } 739 740 ResourceValue value = mResourceData[index]; 741 return ResourceHelper.getFont(value, mContext, mTheme); 742 } 743 744 /** 745 * Retrieve the CharSequence[] for the attribute at <var>index</var>. 746 * This gets the resource ID of the selected attribute, and uses 747 * {@link Resources#getTextArray Resources.getTextArray} of the owning 748 * Resources object to retrieve its String[]. 749 * 750 * @param index Index of attribute to retrieve. 751 * 752 * @return CharSequence[] for the attribute, or null if not defined. 753 */ 754 @Override getTextArray(int index)755 public CharSequence[] getTextArray(int index) { 756 if (!hasValue(index)) { 757 return null; 758 } 759 ResourceValue resVal = mResourceData[index]; 760 if (resVal instanceof ArrayResourceValue) { 761 ArrayResourceValue array = (ArrayResourceValue) resVal; 762 int count = array.getElementCount(); 763 return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) : 764 null; 765 } 766 int id = getResourceId(index, 0); 767 String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : ""; 768 assert false : 769 String.format("%1$s in %2$s%3$s is not a valid array resource.", resVal.getValue(), 770 mNames[index], resIdMessage); 771 772 return new CharSequence[0]; 773 } 774 775 @Override extractThemeAttrs()776 public int[] extractThemeAttrs() { 777 // The drawables are always inflated with a Theme and we don't care about caching. So, 778 // just return. 779 return null; 780 } 781 782 @Override getChangingConfigurations()783 public int getChangingConfigurations() { 784 // We don't care about caching. Any change in configuration is a fresh render. So, 785 // just return. 786 return 0; 787 } 788 789 /** 790 * Retrieve the raw TypedValue for the attribute at <var>index</var>. 791 * 792 * @param index Index of attribute to retrieve. 793 * @param outValue TypedValue object in which to place the attribute's 794 * data. 795 * 796 * @return Returns true if the value was retrieved, else false. 797 */ 798 @Override getValue(int index, TypedValue outValue)799 public boolean getValue(int index, TypedValue outValue) { 800 // TODO: more switch cases for other types. 801 outValue.type = getType(index); 802 switch (outValue.type) { 803 case TYPE_NULL: 804 return false; 805 case TYPE_STRING: 806 outValue.string = getString(index); 807 return true; 808 case TYPE_REFERENCE: 809 outValue.resourceId = mResourceId[index]; 810 return true; 811 default: 812 // For back-compatibility, parse as float. 813 String s = getString(index); 814 return s != null && 815 ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false); 816 } 817 } 818 819 @Override 820 @SuppressWarnings("ResultOfMethodCallIgnored") getType(int index)821 public int getType(int index) { 822 String value = getString(index); 823 if (value == null) { 824 return TYPE_NULL; 825 } 826 if (value.startsWith(PREFIX_RESOURCE_REF)) { 827 return TYPE_REFERENCE; 828 } 829 if (value.startsWith(PREFIX_THEME_REF)) { 830 return TYPE_ATTRIBUTE; 831 } 832 try { 833 // Don't care about the value. Only called to check if an exception is thrown. 834 convertValueToInt(value, 0); 835 if (value.startsWith("0x") || value.startsWith("0X")) { 836 return TYPE_INT_HEX; 837 } 838 // is it a color? 839 if (value.startsWith("#")) { 840 int length = value.length() - 1; 841 if (length == 3) { // rgb 842 return TYPE_INT_COLOR_RGB4; 843 } 844 if (length == 4) { // argb 845 return TYPE_INT_COLOR_ARGB4; 846 } 847 if (length == 6) { // rrggbb 848 return TYPE_INT_COLOR_RGB8; 849 } 850 if (length == 8) { // aarrggbb 851 return TYPE_INT_COLOR_ARGB8; 852 } 853 } 854 if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { 855 return TYPE_INT_BOOLEAN; 856 } 857 return TYPE_INT_DEC; 858 } catch (NumberFormatException ignored) { 859 try { 860 Float.parseFloat(value); 861 return TYPE_FLOAT; 862 } catch (NumberFormatException ignore) { 863 } 864 // Might be a dimension. 865 if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) { 866 return TYPE_DIMENSION; 867 } 868 } 869 // TODO: handle fractions. 870 return TYPE_STRING; 871 } 872 873 /** 874 * Determines whether there is an attribute at <var>index</var>. 875 * 876 * @param index Index of attribute to retrieve. 877 * 878 * @return True if the attribute has a value, false otherwise. 879 */ 880 @Override hasValue(int index)881 public boolean hasValue(int index) { 882 return index >= 0 && index < mResourceData.length && mResourceData[index] != null; 883 } 884 885 @Override hasValueOrEmpty(int index)886 public boolean hasValueOrEmpty(int index) { 887 return hasValue(index) || index >= 0 && index < mResourceData.length && 888 mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0; 889 } 890 891 /** 892 * Retrieve the raw TypedValue for the attribute at <var>index</var> 893 * and return a temporary object holding its data. This object is only 894 * valid until the next call on to {@link TypedArray}. 895 * 896 * @param index Index of attribute to retrieve. 897 * 898 * @return Returns a TypedValue object if the attribute is defined, 899 * containing its data; otherwise returns null. (You will not 900 * receive a TypedValue whose type is TYPE_NULL.) 901 */ 902 @Override peekValue(int index)903 public TypedValue peekValue(int index) { 904 if (index < 0 || index >= mResourceData.length) { 905 return null; 906 } 907 908 if (getValue(index, mValue)) { 909 return mValue; 910 } 911 912 return null; 913 } 914 915 /** 916 * Returns a message about the parser state suitable for printing error messages. 917 */ 918 @Override getPositionDescription()919 public String getPositionDescription() { 920 return "<internal -- stub if needed>"; 921 } 922 923 /** 924 * Give back a previously retrieved TypedArray, for later re-use. 925 */ 926 @Override recycle()927 public void recycle() { 928 // pass 929 } 930 931 @Override toString()932 public String toString() { 933 return Arrays.toString(mResourceData); 934 } 935 936 /** 937 * Searches for the string in the attributes (flag or enums) and returns the integer. 938 * If found, it will return an integer matching the value. 939 * 940 * @param index Index of attribute to retrieve. 941 * 942 * @return Attribute int value, or null if not defined. 943 */ resolveEnumAttribute(int index)944 private Integer resolveEnumAttribute(int index) { 945 // Get the map of attribute-constant -> IntegerValue 946 Map<String, Integer> map = null; 947 if (mIsFramework[index]) { 948 map = Bridge.getEnumValues(mNames[index]); 949 } else { 950 // get the styleable matching the resolved name 951 RenderResources res = mContext.getRenderResources(); 952 ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); 953 if (attr instanceof AttrResourceValue) { 954 map = ((AttrResourceValue) attr).getAttributeValues(); 955 } 956 } 957 958 if (map != null) { 959 // accumulator to store the value of the 1+ constants. 960 int result = 0; 961 boolean found = false; 962 963 // split the value in case this is a mix of several flags. 964 String[] keywords = mResourceData[index].getValue().split("\\|"); 965 for (String keyword : keywords) { 966 Integer i = map.get(keyword.trim()); 967 if (i != null) { 968 result |= i; 969 found = true; 970 } 971 // TODO: We should act smartly and log a warning for incorrect keywords. However, 972 // this method is currently called even if the resourceValue is not an enum. 973 } 974 if (found) { 975 return result; 976 } 977 } 978 979 return null; 980 } 981 982 /** 983 * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account 984 * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle 985 * "XXXXXXXX" > 80000000. 986 */ convertValueToInt(@ullable String charSeq, int defValue)987 private static int convertValueToInt(@Nullable String charSeq, int defValue) { 988 if (null == charSeq || charSeq.isEmpty()) 989 return defValue; 990 991 int sign = 1; 992 int index = 0; 993 int len = charSeq.length(); 994 int base = 10; 995 996 if ('-' == charSeq.charAt(0)) { 997 sign = -1; 998 index++; 999 } 1000 1001 if ('0' == charSeq.charAt(index)) { 1002 // Quick check for a zero by itself 1003 if (index == (len - 1)) 1004 return 0; 1005 1006 char c = charSeq.charAt(index + 1); 1007 1008 if ('x' == c || 'X' == c) { 1009 index += 2; 1010 base = 16; 1011 } else { 1012 index++; 1013 // Leave the base as 10. aapt removes the preceding zero, and thus when framework 1014 // sees the value, it only gets the decimal value. 1015 } 1016 } else if ('#' == charSeq.charAt(index)) { 1017 return ResourceHelper.getColor(charSeq) * sign; 1018 } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) { 1019 return -1; 1020 } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) { 1021 return 0; 1022 } 1023 1024 // Use Long, since we want to handle hex ints > 80000000. 1025 return ((int)Long.parseLong(charSeq.substring(index), base)) * sign; 1026 } 1027 obtain(Resources res, int len)1028 static TypedArray obtain(Resources res, int len) { 1029 return new BridgeTypedArray(res, null, len, true); 1030 } 1031 } 1032