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