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