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