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