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