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