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