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