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