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