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.AttrResourceValue; 20 import com.android.ide.common.rendering.api.LayoutLog; 21 import com.android.ide.common.rendering.api.RenderResources; 22 import com.android.ide.common.rendering.api.ResourceValue; 23 import com.android.ide.common.rendering.api.StyleResourceValue; 24 import com.android.internal.util.XmlUtils; 25 import com.android.layoutlib.bridge.Bridge; 26 import com.android.layoutlib.bridge.android.BridgeContext; 27 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 28 import com.android.layoutlib.bridge.impl.ParserFactory; 29 import com.android.layoutlib.bridge.impl.ResourceHelper; 30 import com.android.resources.ResourceType; 31 32 import org.xmlpull.v1.XmlPullParser; 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import android.content.res.Resources.Theme; 36 import android.graphics.drawable.Drawable; 37 import android.util.DisplayMetrics; 38 import android.util.TypedValue; 39 import android.view.LayoutInflater_Delegate; 40 import android.view.ViewGroup.LayoutParams; 41 42 import java.io.File; 43 import java.util.Arrays; 44 import java.util.Map; 45 46 /** 47 * Custom implementation of TypedArray to handle non compiled resources. 48 */ 49 public final class BridgeTypedArray extends TypedArray { 50 51 private final BridgeResources mBridgeResources; 52 private final BridgeContext mContext; 53 private final boolean mPlatformFile; 54 55 private final ResourceValue[] mResourceData; 56 private final String[] mNames; 57 private final boolean[] mIsFramework; 58 BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile)59 public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, 60 boolean platformFile) { 61 super(resources, null, null, 0); 62 mBridgeResources = resources; 63 mContext = context; 64 mPlatformFile = platformFile; 65 mResourceData = new ResourceValue[len]; 66 mNames = new String[len]; 67 mIsFramework = new boolean[len]; 68 } 69 70 /** 71 * A bridge-specific method that sets a value in the type array 72 * @param index the index of the value in the TypedArray 73 * @param name the name of the attribute 74 * @param isFramework whether the attribute is in the android namespace. 75 * @param value the value of the attribute 76 */ bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value)77 public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) { 78 mResourceData[index] = value; 79 mNames[index] = name; 80 mIsFramework[index] = isFramework; 81 } 82 83 /** 84 * Seals the array after all calls to 85 * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done. 86 * <p/>This allows to compute the list of non default values, permitting 87 * {@link #getIndexCount()} to return the proper value. 88 */ sealArray()89 public void sealArray() { 90 // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt 91 // first count the array size 92 int count = 0; 93 for (int i = 0; i < mResourceData.length; i++) { 94 ResourceValue data = mResourceData[i]; 95 if (data != null) { 96 if (RenderResources.REFERENCE_NULL.equals(data.getValue())) { 97 // No need to store this resource value. This saves needless checking for 98 // "@null" every time an attribute is requested. 99 mResourceData[i] = null; 100 } else { 101 count++; 102 } 103 } 104 } 105 106 // allocate the table with an extra to store the size 107 mIndices = new int[count+1]; 108 mIndices[0] = count; 109 110 // fill the array with the indices. 111 int index = 1; 112 for (int i = 0 ; i < mResourceData.length ; i++) { 113 if (mResourceData[i] != null) { 114 mIndices[index++] = i; 115 } 116 } 117 } 118 119 /** 120 * Set the theme to be used for inflating drawables. 121 */ setTheme(Theme theme)122 public void setTheme(Theme theme) { 123 mTheme = theme; 124 } 125 126 /** 127 * Return the number of values in this array. 128 */ 129 @Override length()130 public int length() { 131 return mResourceData.length; 132 } 133 134 /** 135 * Return the Resources object this array was loaded from. 136 */ 137 @Override getResources()138 public Resources getResources() { 139 return mBridgeResources; 140 } 141 142 /** 143 * Retrieve the styled string value for the attribute at <var>index</var>. 144 * 145 * @param index Index of attribute to retrieve. 146 * 147 * @return CharSequence holding string data. May be styled. Returns 148 * null if the attribute is not defined. 149 */ 150 @Override getText(int index)151 public CharSequence getText(int index) { 152 // FIXME: handle styled strings! 153 return getString(index); 154 } 155 156 /** 157 * Retrieve the string value for the attribute at <var>index</var>. 158 * 159 * @param index Index of attribute to retrieve. 160 * 161 * @return String holding string data. Any styling information is 162 * removed. Returns null if the attribute is not defined. 163 */ 164 @Override getString(int index)165 public String getString(int index) { 166 if (!hasValue(index)) { 167 return null; 168 } 169 // As unfortunate as it is, it's possible to use enums with all attribute formats, 170 // not just integers/enums. So, we need to search the enums always. In case 171 // enums are used, the returned value is an integer. 172 Integer v = resolveEnumAttribute(index); 173 return v == null ? mResourceData[index].getValue() : String.valueOf((int) v); 174 } 175 176 /** 177 * Retrieve the boolean value for the attribute at <var>index</var>. 178 * 179 * @param index Index of attribute to retrieve. 180 * @param defValue Value to return if the attribute is not defined. 181 * 182 * @return Attribute boolean value, or defValue if not defined. 183 */ 184 @Override getBoolean(int index, boolean defValue)185 public boolean getBoolean(int index, boolean defValue) { 186 String s = getString(index); 187 return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue); 188 189 } 190 191 /** 192 * Retrieve the integer value for the attribute at <var>index</var>. 193 * 194 * @param index Index of attribute to retrieve. 195 * @param defValue Value to return if the attribute is not defined. 196 * 197 * @return Attribute int value, or defValue if not defined. 198 */ 199 @Override getInt(int index, int defValue)200 public int getInt(int index, int defValue) { 201 String s = getString(index); 202 try { 203 if (s != null) { 204 return XmlUtils.convertValueToInt(s, defValue); 205 } 206 } catch (NumberFormatException e) { 207 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 208 String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer", 209 s, mNames[index]), 210 null); 211 return defValue; 212 } 213 return defValue; 214 } 215 216 /** 217 * Retrieve the float value for the attribute at <var>index</var>. 218 * 219 * @param index Index of attribute to retrieve. 220 * 221 * @return Attribute float value, or defValue if not defined.. 222 */ 223 @Override getFloat(int index, float defValue)224 public float getFloat(int index, float defValue) { 225 String s = getString(index); 226 try { 227 if (s != null) { 228 return Float.parseFloat(s); 229 } 230 } catch (NumberFormatException e) { 231 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 232 String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.", 233 s, mNames[index]), 234 null); 235 } 236 return defValue; 237 } 238 239 /** 240 * Retrieve the color value for the attribute at <var>index</var>. If 241 * the attribute references a color resource holding a complex 242 * {@link android.content.res.ColorStateList}, then the default color from 243 * the set is returned. 244 * 245 * @param index Index of attribute to retrieve. 246 * @param defValue Value to return if the attribute is not defined or 247 * not a resource. 248 * 249 * @return Attribute color value, or defValue if not defined. 250 */ 251 @Override getColor(int index, int defValue)252 public int getColor(int index, int defValue) { 253 if (index < 0 || index >= mResourceData.length) { 254 return defValue; 255 } 256 257 if (mResourceData[index] == null) { 258 return defValue; 259 } 260 261 ColorStateList colorStateList = ResourceHelper.getColorStateList( 262 mResourceData[index], mContext); 263 if (colorStateList != null) { 264 return colorStateList.getDefaultColor(); 265 } 266 267 return defValue; 268 } 269 270 /** 271 * Retrieve the ColorStateList for the attribute at <var>index</var>. 272 * The value may be either a single solid color or a reference to 273 * a color or complex {@link android.content.res.ColorStateList} description. 274 * 275 * @param index Index of attribute to retrieve. 276 * 277 * @return ColorStateList for the attribute, or null if not defined. 278 */ 279 @Override getColorStateList(int index)280 public ColorStateList getColorStateList(int index) { 281 if (!hasValue(index)) { 282 return null; 283 } 284 285 ResourceValue resValue = mResourceData[index]; 286 String value = resValue.getValue(); 287 288 if (value == null) { 289 return null; 290 } 291 292 // let the framework inflate the ColorStateList from the XML file. 293 File f = new File(value); 294 if (f.isFile()) { 295 try { 296 XmlPullParser parser = ParserFactory.create(f); 297 298 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 299 parser, mContext, resValue.isFramework()); 300 try { 301 return ColorStateList.createFromXml(mContext.getResources(), blockParser); 302 } finally { 303 blockParser.ensurePopped(); 304 } 305 } catch (XmlPullParserException e) { 306 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 307 "Failed to configure parser for " + value, e, null); 308 return null; 309 } catch (Exception e) { 310 // this is an error and not warning since the file existence is checked before 311 // attempting to parse it. 312 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 313 "Failed to parse file " + value, e, null); 314 315 return null; 316 } 317 } 318 319 try { 320 int color = ResourceHelper.getColor(value); 321 return ColorStateList.valueOf(color); 322 } catch (NumberFormatException e) { 323 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null); 324 } 325 326 return null; 327 } 328 329 /** 330 * Retrieve the integer value for the attribute at <var>index</var>. 331 * 332 * @param index Index of attribute to retrieve. 333 * @param defValue Value to return if the attribute is not defined or 334 * not a resource. 335 * 336 * @return Attribute integer value, or defValue if not defined. 337 */ 338 @Override getInteger(int index, int defValue)339 public int getInteger(int index, int defValue) { 340 return getInt(index, defValue); 341 } 342 343 /** 344 * Retrieve a dimensional unit attribute at <var>index</var>. Unit 345 * conversions are based on the current {@link DisplayMetrics} 346 * associated with the resources this {@link TypedArray} object 347 * came from. 348 * 349 * @param index Index of attribute to retrieve. 350 * @param defValue Value to return if the attribute is not defined or 351 * not a resource. 352 * 353 * @return Attribute dimension value multiplied by the appropriate 354 * metric, or defValue if not defined. 355 * 356 * @see #getDimensionPixelOffset 357 * @see #getDimensionPixelSize 358 */ 359 @Override getDimension(int index, float defValue)360 public float getDimension(int index, float defValue) { 361 String s = getString(index); 362 if (s == null) { 363 return defValue; 364 } 365 // Check if the value is a magic constant that doesn't require a unit. 366 try { 367 int i = Integer.parseInt(s); 368 if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { 369 return i; 370 } 371 } catch (NumberFormatException ignored) { 372 // pass 373 } 374 375 if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { 376 return mValue.getDimension(mBridgeResources.getDisplayMetrics()); 377 } 378 379 return defValue; 380 } 381 382 /** 383 * Retrieve a dimensional unit attribute at <var>index</var> for use 384 * as an offset in raw pixels. This is the same as 385 * {@link #getDimension}, except the returned value is converted to 386 * integer pixels for you. An offset conversion involves simply 387 * truncating the base value to an integer. 388 * 389 * @param index Index of attribute to retrieve. 390 * @param defValue Value to return if the attribute is not defined or 391 * not a resource. 392 * 393 * @return Attribute dimension value multiplied by the appropriate 394 * metric and truncated to integer pixels, or defValue if not defined. 395 * 396 * @see #getDimension 397 * @see #getDimensionPixelSize 398 */ 399 @Override getDimensionPixelOffset(int index, int defValue)400 public int getDimensionPixelOffset(int index, int defValue) { 401 return (int) getDimension(index, defValue); 402 } 403 404 /** 405 * Retrieve a dimensional unit attribute at <var>index</var> for use 406 * as a size in raw pixels. This is the same as 407 * {@link #getDimension}, except the returned value is converted to 408 * integer pixels for use as a size. A size conversion involves 409 * rounding the base value, and ensuring that a non-zero base value 410 * is at least one pixel in size. 411 * 412 * @param index Index of attribute to retrieve. 413 * @param defValue Value to return if the attribute is not defined or 414 * not a resource. 415 * 416 * @return Attribute dimension value multiplied by the appropriate 417 * metric and truncated to integer pixels, or defValue if not defined. 418 * 419 * @see #getDimension 420 * @see #getDimensionPixelOffset 421 */ 422 @Override getDimensionPixelSize(int index, int defValue)423 public int getDimensionPixelSize(int index, int defValue) { 424 try { 425 return getDimension(index); 426 } catch (RuntimeException e) { 427 String s = getString(index); 428 429 if (s != null) { 430 // looks like we were unable to resolve the dimension value 431 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 432 String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.", 433 s, mNames[index]), null); 434 } 435 436 return defValue; 437 } 438 } 439 440 /** 441 * Special version of {@link #getDimensionPixelSize} for retrieving 442 * {@link android.view.ViewGroup}'s layout_width and layout_height 443 * attributes. This is only here for performance reasons; applications 444 * should use {@link #getDimensionPixelSize}. 445 * 446 * @param index Index of the attribute to retrieve. 447 * @param name Textual name of attribute for error reporting. 448 * 449 * @return Attribute dimension value multiplied by the appropriate 450 * metric and truncated to integer pixels. 451 */ 452 @Override getLayoutDimension(int index, String name)453 public int getLayoutDimension(int index, String name) { 454 try { 455 // this will throw an exception 456 return getDimension(index); 457 } catch (RuntimeException e) { 458 459 if (LayoutInflater_Delegate.sIsInInclude) { 460 throw new RuntimeException(); 461 } 462 463 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 464 "You must supply a " + name + " attribute.", null); 465 466 return 0; 467 } 468 } 469 470 @Override getLayoutDimension(int index, int defValue)471 public int getLayoutDimension(int index, int defValue) { 472 return getDimensionPixelSize(index, defValue); 473 } 474 getDimension(int index)475 private int getDimension(int index) { 476 String s = getString(index); 477 if (s == null) { 478 throw new RuntimeException(); 479 } 480 // Check if the value is a magic constant that doesn't require a unit. 481 try { 482 int i = Integer.parseInt(s); 483 if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { 484 return i; 485 } 486 } catch (NumberFormatException ignored) { 487 // pass 488 } 489 if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { 490 float f = mValue.getDimension(mBridgeResources.getDisplayMetrics()); 491 492 final int res = (int)(f+0.5f); 493 if (res != 0) return res; 494 if (f == 0) return 0; 495 if (f > 0) return 1; 496 } 497 498 throw new RuntimeException(); 499 } 500 501 /** 502 * Retrieve a fractional unit attribute at <var>index</var>. 503 * 504 * @param index Index of attribute to retrieve. 505 * @param base The base value of this fraction. In other words, a 506 * standard fraction is multiplied by this value. 507 * @param pbase The parent base value of this fraction. In other 508 * words, a parent fraction (nn%p) is multiplied by this 509 * value. 510 * @param defValue Value to return if the attribute is not defined or 511 * not a resource. 512 * 513 * @return Attribute fractional value multiplied by the appropriate 514 * base value, or defValue if not defined. 515 */ 516 @Override getFraction(int index, int base, int pbase, float defValue)517 public float getFraction(int index, int base, int pbase, float defValue) { 518 String value = getString(index); 519 if (value == null) { 520 return defValue; 521 } 522 523 if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) { 524 return mValue.getFraction(base, pbase); 525 } 526 527 // looks like we were unable to resolve the fraction value 528 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, 529 String.format( 530 "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", 531 value, mNames[index]), null); 532 533 return defValue; 534 } 535 536 /** 537 * Retrieve the resource identifier for the attribute at 538 * <var>index</var>. Note that attribute resource as resolved when 539 * the overall {@link TypedArray} object is retrieved. As a 540 * result, this function will return the resource identifier of the 541 * final resource value that was found, <em>not</em> necessarily the 542 * original resource that was specified by the attribute. 543 * 544 * @param index Index of attribute to retrieve. 545 * @param defValue Value to return if the attribute is not defined or 546 * not a resource. 547 * 548 * @return Attribute resource identifier, or defValue if not defined. 549 */ 550 @Override getResourceId(int index, int defValue)551 public int getResourceId(int index, int defValue) { 552 if (index < 0 || index >= mResourceData.length) { 553 return defValue; 554 } 555 556 // get the Resource for this index 557 ResourceValue resValue = mResourceData[index]; 558 559 // no data, return the default value. 560 if (resValue == null) { 561 return defValue; 562 } 563 564 // check if this is a style resource 565 if (resValue instanceof StyleResourceValue) { 566 // get the id that will represent this style. 567 return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); 568 } 569 570 // if the attribute was a reference to a resource, and not a declaration of an id (@+id), 571 // then the xml attribute value was "resolved" which leads us to a ResourceValue with a 572 // valid getType() and getName() returning a resource name. 573 // (and getValue() returning null!). We need to handle this! 574 if (resValue.getResourceType() != null) { 575 // if this is a framework id 576 if (mPlatformFile || resValue.isFramework()) { 577 // look for idName in the android R classes 578 return mContext.getFrameworkResourceValue( 579 resValue.getResourceType(), resValue.getName(), defValue); 580 } 581 582 // look for idName in the project R class. 583 return mContext.getProjectResourceValue( 584 resValue.getResourceType(), resValue.getName(), defValue); 585 } 586 587 // else, try to get the value, and resolve it somehow. 588 String value = resValue.getValue(); 589 if (value == null) { 590 return defValue; 591 } 592 593 // if the value is just an integer, return it. 594 try { 595 int i = Integer.parseInt(value); 596 if (Integer.toString(i).equals(value)) { 597 return i; 598 } 599 } catch (NumberFormatException e) { 600 // pass 601 } 602 603 // Handle the @id/<name>, @+id/<name> and @android:id/<name> 604 // We need to return the exact value that was compiled (from the various R classes), 605 // as these values can be reused internally with calls to findViewById(). 606 // There's a trick with platform layouts that not use "android:" but their IDs are in 607 // fact in the android.R and com.android.internal.R classes. 608 // The field mPlatformFile will indicate that all IDs are to be looked up in the android R 609 // classes exclusively. 610 611 // if this is a reference to an id, find it. 612 if (value.startsWith("@id/") || value.startsWith("@+") || 613 value.startsWith("@android:id/")) { 614 615 int pos = value.indexOf('/'); 616 String idName = value.substring(pos + 1); 617 boolean create = value.startsWith("@+"); 618 boolean isFrameworkId = 619 mPlatformFile || value.startsWith("@android") || value.startsWith("@+android"); 620 621 // Look for the idName in project or android R class depending on isPlatform. 622 if (create) { 623 Integer idValue; 624 if (isFrameworkId) { 625 idValue = Bridge.getResourceId(ResourceType.ID, idName); 626 } else { 627 idValue = mContext.getProjectCallback().getResourceId(ResourceType.ID, idName); 628 } 629 return idValue == null ? defValue : idValue; 630 } 631 // This calls the same method as in if(create), but doesn't create a dynamic id, if 632 // one is not found. 633 if (isFrameworkId) { 634 return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); 635 } else { 636 return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); 637 } 638 } 639 640 // not a direct id valid reference? resolve it 641 Integer idValue; 642 643 if (resValue.isFramework()) { 644 idValue = Bridge.getResourceId(resValue.getResourceType(), 645 resValue.getName()); 646 } else { 647 idValue = mContext.getProjectCallback().getResourceId( 648 resValue.getResourceType(), resValue.getName()); 649 } 650 651 if (idValue != null) { 652 return idValue; 653 } 654 655 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, 656 String.format( 657 "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]), 658 resValue); 659 660 return defValue; 661 } 662 663 @Override getThemeAttributeId(int index, int defValue)664 public int getThemeAttributeId(int index, int defValue) { 665 // TODO: Get the right Theme Attribute ID to enable caching of the drawables. 666 return defValue; 667 } 668 669 /** 670 * Retrieve the Drawable for the attribute at <var>index</var>. This 671 * gets the resource ID of the selected attribute, and uses 672 * {@link Resources#getDrawable Resources.getDrawable} of the owning 673 * Resources object to retrieve its Drawable. 674 * 675 * @param index Index of attribute to retrieve. 676 * 677 * @return Drawable for the attribute, or null if not defined. 678 */ 679 @Override getDrawable(int index)680 public Drawable getDrawable(int index) { 681 if (!hasValue(index)) { 682 return null; 683 } 684 685 ResourceValue value = mResourceData[index]; 686 return ResourceHelper.getDrawable(value, mContext, mTheme); 687 } 688 689 690 /** 691 * Retrieve the CharSequence[] for the attribute at <var>index</var>. 692 * This gets the resource ID of the selected attribute, and uses 693 * {@link Resources#getTextArray Resources.getTextArray} of the owning 694 * Resources object to retrieve its String[]. 695 * 696 * @param index Index of attribute to retrieve. 697 * 698 * @return CharSequence[] for the attribute, or null if not defined. 699 */ 700 @Override getTextArray(int index)701 public CharSequence[] getTextArray(int index) { 702 String value = getString(index); 703 if (value != null) { 704 return new CharSequence[] { value }; 705 } 706 707 return null; 708 } 709 710 @Override extractThemeAttrs()711 public int[] extractThemeAttrs() { 712 // The drawables are always inflated with a Theme and we don't care about caching. So, 713 // just return. 714 return null; 715 } 716 717 @Override getChangingConfigurations()718 public int getChangingConfigurations() { 719 // We don't care about caching. Any change in configuration is a fresh render. So, 720 // just return. 721 return 0; 722 } 723 724 /** 725 * Retrieve the raw TypedValue for the attribute at <var>index</var>. 726 * 727 * @param index Index of attribute to retrieve. 728 * @param outValue TypedValue object in which to place the attribute's 729 * data. 730 * 731 * @return Returns true if the value was retrieved, else false. 732 */ 733 @Override getValue(int index, TypedValue outValue)734 public boolean getValue(int index, TypedValue outValue) { 735 String s = getString(index); 736 return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false); 737 } 738 739 /** 740 * Determines whether there is an attribute at <var>index</var>. 741 * 742 * @param index Index of attribute to retrieve. 743 * 744 * @return True if the attribute has a value, false otherwise. 745 */ 746 @Override hasValue(int index)747 public boolean hasValue(int index) { 748 return index >= 0 && index < mResourceData.length && mResourceData[index] != null; 749 } 750 751 /** 752 * Retrieve the raw TypedValue for the attribute at <var>index</var> 753 * and return a temporary object holding its data. This object is only 754 * valid until the next call on to {@link TypedArray}. 755 * 756 * @param index Index of attribute to retrieve. 757 * 758 * @return Returns a TypedValue object if the attribute is defined, 759 * containing its data; otherwise returns null. (You will not 760 * receive a TypedValue whose type is TYPE_NULL.) 761 */ 762 @Override peekValue(int index)763 public TypedValue peekValue(int index) { 764 if (index < 0 || index >= mResourceData.length) { 765 return null; 766 } 767 768 if (getValue(index, mValue)) { 769 return mValue; 770 } 771 772 return null; 773 } 774 775 /** 776 * Returns a message about the parser state suitable for printing error messages. 777 */ 778 @Override getPositionDescription()779 public String getPositionDescription() { 780 return "<internal -- stub if needed>"; 781 } 782 783 /** 784 * Give back a previously retrieved TypedArray, for later re-use. 785 */ 786 @Override recycle()787 public void recycle() { 788 // pass 789 } 790 791 @Override toString()792 public String toString() { 793 return Arrays.toString(mResourceData); 794 } 795 796 /** 797 * Searches for the string in the attributes (flag or enums) and returns the integer. 798 * If found, it will return an integer matching the value. 799 * 800 * @param index Index of attribute to retrieve. 801 * 802 * @return Attribute int value, or null if not defined. 803 */ resolveEnumAttribute(int index)804 private Integer resolveEnumAttribute(int index) { 805 // Get the map of attribute-constant -> IntegerValue 806 Map<String, Integer> map = null; 807 if (mIsFramework[index]) { 808 map = Bridge.getEnumValues(mNames[index]); 809 } else { 810 // get the styleable matching the resolved name 811 RenderResources res = mContext.getRenderResources(); 812 ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); 813 if (attr instanceof AttrResourceValue) { 814 map = ((AttrResourceValue) attr).getAttributeValues(); 815 } 816 } 817 818 if (map != null) { 819 // accumulator to store the value of the 1+ constants. 820 int result = 0; 821 boolean found = false; 822 823 // split the value in case this is a mix of several flags. 824 String[] keywords = mResourceData[index].getValue().split("\\|"); 825 for (String keyword : keywords) { 826 Integer i = map.get(keyword.trim()); 827 if (i != null) { 828 result |= i; 829 found = true; 830 } 831 // TODO: We should act smartly and log a warning for incorrect keywords. However, 832 // this method is currently called even if the resourceValue is not an enum. 833 } 834 if (found) { 835 return result; 836 } 837 } 838 839 return null; 840 } 841 obtain(Resources res, int len)842 static TypedArray obtain(Resources res, int len) { 843 return res instanceof BridgeResources ? 844 new BridgeTypedArray(((BridgeResources) res), null, len, true) : null; 845 } 846 } 847