1 /* 2 * Copyright (C) 2016 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.SdkConstants; 20 import com.android.ide.common.rendering.api.ArrayResourceValue; 21 import com.android.ide.common.rendering.api.AssetRepository; 22 import com.android.ide.common.rendering.api.DensityBasedResourceValue; 23 import com.android.ide.common.rendering.api.LayoutLog; 24 import com.android.ide.common.rendering.api.LayoutlibCallback; 25 import com.android.ide.common.rendering.api.PluralsResourceValue; 26 import com.android.ide.common.rendering.api.RenderResources; 27 import com.android.ide.common.rendering.api.ResourceNamespace; 28 import com.android.ide.common.rendering.api.ResourceNamespace.Resolver; 29 import com.android.ide.common.rendering.api.ResourceReference; 30 import com.android.ide.common.rendering.api.ResourceValue; 31 import com.android.ide.common.rendering.api.ResourceValueImpl; 32 import com.android.layoutlib.bridge.Bridge; 33 import com.android.layoutlib.bridge.BridgeConstants; 34 import com.android.layoutlib.bridge.android.BridgeContext; 35 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 36 import com.android.layoutlib.bridge.android.UnresolvedResourceValue; 37 import com.android.layoutlib.bridge.impl.ParserFactory; 38 import com.android.layoutlib.bridge.impl.ResourceHelper; 39 import com.android.layoutlib.bridge.util.NinePatchInputStream; 40 import com.android.ninepatch.NinePatch; 41 import com.android.resources.ResourceType; 42 import com.android.resources.ResourceUrl; 43 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 44 import com.android.tools.layoutlib.annotations.VisibleForTesting; 45 import com.android.util.Pair; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import android.annotation.NonNull; 51 import android.annotation.Nullable; 52 import android.content.res.Resources.NotFoundException; 53 import android.content.res.Resources.Theme; 54 import android.graphics.Color; 55 import android.graphics.Typeface; 56 import android.graphics.drawable.Drawable; 57 import android.icu.text.PluralRules; 58 import android.util.AttributeSet; 59 import android.util.DisplayMetrics; 60 import android.util.LruCache; 61 import android.util.TypedValue; 62 import android.view.DisplayAdjustments; 63 import android.view.ViewGroup.LayoutParams; 64 65 import java.io.IOException; 66 import java.io.InputStream; 67 import java.util.Objects; 68 import java.util.WeakHashMap; 69 70 import static android.content.res.AssetManager.ACCESS_STREAMING; 71 import static com.android.SdkConstants.ANDROID_PKG; 72 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 73 74 @SuppressWarnings("deprecation") 75 public class Resources_Delegate { 76 private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks = 77 new WeakHashMap<>(); 78 private static WeakHashMap<Resources, BridgeContext> sContexts = new WeakHashMap<>(); 79 80 // TODO: This cache is cleared every time a render session is disposed. Look into making this 81 // more long lived. 82 private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50); 83 initSystem(@onNull BridgeContext context, @NonNull AssetManager assets, @NonNull DisplayMetrics metrics, @NonNull Configuration config, @NonNull LayoutlibCallback layoutlibCallback)84 public static Resources initSystem(@NonNull BridgeContext context, 85 @NonNull AssetManager assets, 86 @NonNull DisplayMetrics metrics, 87 @NonNull Configuration config, 88 @NonNull LayoutlibCallback layoutlibCallback) { 89 assert Resources.mSystem == null : 90 "Resources_Delegate.initSystem called twice before disposeSystem was called"; 91 Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); 92 resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); 93 sContexts.put(resources, Objects.requireNonNull(context)); 94 sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback)); 95 return Resources.mSystem = resources; 96 } 97 98 /** Returns the {@link BridgeContext} associated to the given {@link Resources} */ 99 @VisibleForTesting 100 @NonNull getContext(@onNull Resources resources)101 public static BridgeContext getContext(@NonNull Resources resources) { 102 assert sContexts.containsKey(resources) : 103 "Resources_Delegate.getContext called before initSystem"; 104 return sContexts.get(resources); 105 } 106 107 /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */ 108 @VisibleForTesting 109 @NonNull getLayoutlibCallback(@onNull Resources resources)110 public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) { 111 assert sLayoutlibCallbacks.containsKey(resources) : 112 "Resources_Delegate.getLayoutlibCallback called before initSystem"; 113 return sLayoutlibCallbacks.get(resources); 114 } 115 116 /** 117 * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that 118 * would prevent us from unloading the library. 119 */ disposeSystem()120 public static void disposeSystem() { 121 sDrawableCache.evictAll(); 122 sContexts.clear(); 123 sLayoutlibCallbacks.clear(); 124 Resources.mSystem = null; 125 } 126 newTypeArray(Resources resources, int numEntries)127 public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) { 128 return new BridgeTypedArray(resources, getContext(resources), numEntries); 129 } 130 getResourceInfo(Resources resources, int id)131 private static ResourceReference getResourceInfo(Resources resources, int id) { 132 // first get the String related to this id in the framework 133 ResourceReference resourceInfo = Bridge.resolveResourceId(id); 134 135 assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called"; 136 // Set the layoutlib callback and context for resources 137 if (resources != Resources.mSystem && 138 (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) { 139 sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem)); 140 sContexts.put(resources, getContext(Resources.mSystem)); 141 } 142 143 if (resourceInfo == null) { 144 // Didn't find a match in the framework? Look in the project. 145 resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id); 146 } 147 148 return resourceInfo; 149 } 150 getResourceValue(Resources resources, int id)151 private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id) { 152 ResourceReference resourceInfo = getResourceInfo(resources, id); 153 154 if (resourceInfo != null) { 155 String attributeName = resourceInfo.getName(); 156 RenderResources renderResources = getContext(resources).getRenderResources(); 157 ResourceValue value = renderResources.getResolvedResource(resourceInfo); 158 if (value == null) { 159 // Unable to resolve the attribute, just leave the unresolved value. 160 value = new ResourceValueImpl(resourceInfo.getNamespace(), 161 resourceInfo.getResourceType(), attributeName, attributeName); 162 } 163 return Pair.of(attributeName, value); 164 } 165 166 return null; 167 } 168 169 @LayoutlibDelegate getDrawable(Resources resources, int id)170 static Drawable getDrawable(Resources resources, int id) { 171 return getDrawable(resources, id, null); 172 } 173 174 @LayoutlibDelegate getDrawable(Resources resources, int id, Theme theme)175 static Drawable getDrawable(Resources resources, int id, Theme theme) { 176 Pair<String, ResourceValue> value = getResourceValue(resources, id); 177 if (value != null) { 178 String key = value.getSecond().getValue(); 179 180 Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null; 181 Drawable drawable; 182 if (constantState != null) { 183 drawable = constantState.newDrawable(resources, theme); 184 } else { 185 drawable = 186 ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme); 187 188 if (key != null) { 189 sDrawableCache.put(key, drawable.getConstantState()); 190 } 191 } 192 193 return drawable; 194 } 195 196 // id was not found or not resolved. Throw a NotFoundException. 197 throwException(resources, id); 198 199 // this is not used since the method above always throws 200 return null; 201 } 202 203 @LayoutlibDelegate getColor(Resources resources, int id)204 static int getColor(Resources resources, int id) { 205 return getColor(resources, id, null); 206 } 207 208 @LayoutlibDelegate getColor(Resources resources, int id, Theme theme)209 static int getColor(Resources resources, int id, Theme theme) throws NotFoundException { 210 Pair<String, ResourceValue> value = getResourceValue(resources, id); 211 212 if (value != null) { 213 ResourceValue resourceValue = value.getSecond(); 214 try { 215 return ResourceHelper.getColor(resourceValue.getValue()); 216 } catch (NumberFormatException e) { 217 // Check if the value passed is a file. If it is, mostly likely, user is referencing 218 // a color state list from a place where they should reference only a pure color. 219 AssetRepository repository = getAssetRepository(resources); 220 String message; 221 if (repository.isFileResource(resourceValue.getValue())) { 222 String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/" 223 + resourceValue.getName(); 224 message = "Hexadecimal color expected, found Color State List for " + resource; 225 } else { 226 message = e.getMessage(); 227 } 228 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null); 229 return 0; 230 } 231 } 232 233 // Suppress possible NPE. getColorStateList will never return null, it will instead 234 // throw an exception, but intelliJ can't figure that out 235 //noinspection ConstantConditions 236 return getColorStateList(resources, id, theme).getDefaultColor(); 237 } 238 239 @LayoutlibDelegate getColorStateList(Resources resources, int id)240 static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException { 241 return getColorStateList(resources, id, null); 242 } 243 244 @LayoutlibDelegate getColorStateList(Resources resources, int id, Theme theme)245 static ColorStateList getColorStateList(Resources resources, int id, Theme theme) 246 throws NotFoundException { 247 Pair<String, ResourceValue> resValue = getResourceValue(resources, id); 248 249 if (resValue != null) { 250 ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), 251 getContext(resources), theme); 252 if (stateList != null) { 253 return stateList; 254 } 255 } 256 257 // id was not found or not resolved. Throw a NotFoundException. 258 throwException(resources, id); 259 260 // this is not used since the method above always throws 261 return null; 262 } 263 264 @LayoutlibDelegate getText(Resources resources, int id, CharSequence def)265 static CharSequence getText(Resources resources, int id, CharSequence def) { 266 Pair<String, ResourceValue> value = getResourceValue(resources, id); 267 268 if (value != null) { 269 ResourceValue resValue = value.getSecond(); 270 271 assert resValue != null; 272 if (resValue != null) { 273 String v = resValue.getValue(); 274 if (v != null) { 275 return v; 276 } 277 } 278 } 279 280 return def; 281 } 282 283 @LayoutlibDelegate getText(Resources resources, int id)284 static CharSequence getText(Resources resources, int id) throws NotFoundException { 285 Pair<String, ResourceValue> value = getResourceValue(resources, id); 286 287 if (value != null) { 288 ResourceValue resValue = value.getSecond(); 289 290 assert resValue != null; 291 if (resValue != null) { 292 String v = resValue.getValue(); 293 if (v != null) { 294 return v; 295 } 296 } 297 } 298 299 // id was not found or not resolved. Throw a NotFoundException. 300 throwException(resources, id); 301 302 // this is not used since the method above always throws 303 return null; 304 } 305 306 @LayoutlibDelegate getTextArray(Resources resources, int id)307 static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException { 308 ResourceValue resValue = getArrayResourceValue(resources, id); 309 if (resValue == null) { 310 // Error already logged by getArrayResourceValue. 311 return new CharSequence[0]; 312 } 313 if (resValue instanceof ArrayResourceValue) { 314 ArrayResourceValue arrayValue = (ArrayResourceValue) resValue; 315 return resolveValues(resources, arrayValue); 316 } 317 RenderResources renderResources = getContext(resources).getRenderResources(); 318 return new CharSequence[] { renderResources.resolveResValue(resValue).getValue() }; 319 } 320 321 @LayoutlibDelegate getStringArray(Resources resources, int id)322 static String[] getStringArray(Resources resources, int id) throws NotFoundException { 323 ResourceValue resValue = getArrayResourceValue(resources, id); 324 if (resValue == null) { 325 // Error already logged by getArrayResourceValue. 326 return new String[0]; 327 } 328 if (resValue instanceof ArrayResourceValue) { 329 ArrayResourceValue arv = (ArrayResourceValue) resValue; 330 return resolveValues(resources, arv); 331 } 332 return new String[] { resolveReference(resources, resValue) }; 333 } 334 335 /** 336 * Resolves each element in resValue and returns an array of resolved values. The returned array 337 * may contain nulls. 338 */ 339 @NonNull resolveValues(@onNull Resources resources, @NonNull ArrayResourceValue resValue)340 static String[] resolveValues(@NonNull Resources resources, 341 @NonNull ArrayResourceValue resValue) { 342 String[] result = new String[resValue.getElementCount()]; 343 for (int i = 0; i < resValue.getElementCount(); i++) { 344 String value = resValue.getElement(i); 345 result[i] = resolveReference(resources, value, 346 resValue.getNamespace(), resValue.getNamespaceResolver()); 347 } 348 return result; 349 } 350 351 @LayoutlibDelegate getIntArray(Resources resources, int id)352 static int[] getIntArray(Resources resources, int id) throws NotFoundException { 353 ResourceValue rv = getArrayResourceValue(resources, id); 354 if (rv == null) { 355 // Error already logged by getArrayResourceValue. 356 return new int[0]; 357 } 358 if (rv instanceof ArrayResourceValue) { 359 ArrayResourceValue resValue = (ArrayResourceValue) rv; 360 int n = resValue.getElementCount(); 361 int[] values = new int[n]; 362 for (int i = 0; i < n; i++) { 363 String element = resolveReference(resources, resValue.getElement(i), 364 resValue.getNamespace(), resValue.getNamespaceResolver()); 365 if (element != null) { 366 try { 367 if (element.startsWith("#")) { 368 // This integer represents a color (starts with #). 369 values[i] = Color.parseColor(element); 370 } else { 371 values[i] = getInt(element); 372 } 373 } catch (NumberFormatException e) { 374 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 375 "Integer resource array contains non-integer value: \"" + element + 376 "\"", null); 377 } catch (IllegalArgumentException e) { 378 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 379 "Integer resource array contains wrong color format: \"" + element + 380 "\"", null); 381 } 382 } else { 383 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 384 "Integer resource array contains non-integer value: \"" + 385 resValue.getElement(i) + "\"", null); 386 } 387 } 388 return values; 389 } 390 391 // This is an older IDE that can only give us the first element of the array. 392 String firstValue = resolveReference(resources, rv); 393 if (firstValue != null) { 394 try { 395 return new int[]{getInt(firstValue)}; 396 } catch (NumberFormatException e) { 397 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 398 "Integer resource array contains non-integer value: \"" + firstValue + "\"", 399 null); 400 return new int[1]; 401 } 402 } else { 403 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 404 "Integer resource array contains non-integer value: \"" + 405 rv.getValue() + "\"", null); 406 return new int[1]; 407 } 408 } 409 410 /** 411 * Try to find the ArrayResourceValue for the given id. 412 * <p/> 413 * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an 414 * error and return null. However, if the ResourceValue found has type {@code 415 * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the 416 * method returns the ResourceValue. This happens on older versions of the IDE, which did not 417 * parse the array resources properly. 418 * <p/> 419 * 420 * @throws NotFoundException if no resource if found 421 */ 422 @Nullable getArrayResourceValue(Resources resources, int id)423 private static ResourceValue getArrayResourceValue(Resources resources, int id) 424 throws NotFoundException { 425 Pair<String, ResourceValue> v = getResourceValue(resources, id); 426 427 if (v != null) { 428 ResourceValue resValue = v.getSecond(); 429 430 assert resValue != null; 431 if (resValue != null) { 432 final ResourceType type = resValue.getResourceType(); 433 if (type != ResourceType.ARRAY) { 434 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, 435 String.format( 436 "Resource with id 0x%1$X is not an array resource, but %2$s", 437 id, type == null ? "null" : type.getDisplayName()), 438 null); 439 return null; 440 } 441 if (!(resValue instanceof ArrayResourceValue)) { 442 Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, 443 "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.", 444 null); 445 } 446 return resValue; 447 } 448 } 449 450 // id was not found or not resolved. Throw a NotFoundException. 451 throwException(resources, id); 452 453 // this is not used since the method above always throws 454 return null; 455 } 456 457 @Nullable resolveReference(@onNull Resources resources, @Nullable String value, @NonNull ResourceNamespace contextNamespace, @NonNull ResourceNamespace.Resolver resolver)458 private static String resolveReference(@NonNull Resources resources, @Nullable String value, 459 @NonNull ResourceNamespace contextNamespace, 460 @NonNull ResourceNamespace.Resolver resolver) { 461 if (value != null) { 462 ResourceValue resValue = new UnresolvedResourceValue(value, contextNamespace, resolver); 463 return resolveReference(resources, resValue); 464 } 465 return null; 466 } 467 468 @Nullable resolveReference(@onNull Resources resources, @NonNull ResourceValue value)469 private static String resolveReference(@NonNull Resources resources, 470 @NonNull ResourceValue value) { 471 RenderResources renderResources = getContext(resources).getRenderResources(); 472 ResourceValue resolvedValue = renderResources.resolveResValue(value); 473 return resolvedValue == null ? null : resolvedValue.getValue(); 474 } 475 476 @LayoutlibDelegate getLayout(Resources resources, int id)477 static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException { 478 Pair<String, ResourceValue> v = getResourceValue(resources, id); 479 480 if (v != null) { 481 ResourceValue value = v.getSecond(); 482 483 try { 484 BridgeXmlBlockParser parser = 485 ResourceHelper.getXmlBlockParser(getContext(resources), value); 486 if (parser != null) { 487 return parser; 488 } 489 } catch (XmlPullParserException e) { 490 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 491 "Failed to parse " + value.getValue(), e, null /*data*/); 492 // we'll return null below. 493 } 494 } 495 496 // id was not found or not resolved. Throw a NotFoundException. 497 throwException(resources, id, "layout"); 498 499 // this is not used since the method above always throws 500 return null; 501 } 502 503 @LayoutlibDelegate getAnimation(Resources resources, int id)504 static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException { 505 Pair<String, ResourceValue> v = getResourceValue(resources, id); 506 507 if (v != null) { 508 ResourceValue value = v.getSecond(); 509 510 try { 511 return ResourceHelper.getXmlBlockParser(getContext(resources), value); 512 } catch (XmlPullParserException e) { 513 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 514 "Failed to parse " + value.getValue(), e, null /*data*/); 515 // we'll return null below. 516 } 517 } 518 519 // id was not found or not resolved. Throw a NotFoundException. 520 throwException(resources, id); 521 522 // this is not used since the method above always throws 523 return null; 524 } 525 526 @LayoutlibDelegate obtainAttributes(Resources resources, AttributeSet set, int[] attrs)527 static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) { 528 return getContext(resources).obtainStyledAttributes(set, attrs); 529 } 530 531 @LayoutlibDelegate obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet set, int[] attrs)532 static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet 533 set, int[] attrs) { 534 return Resources.obtainAttributes_Original(resources, theme, set, attrs); 535 } 536 537 @LayoutlibDelegate obtainTypedArray(Resources resources, int id)538 static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException { 539 BridgeContext context = getContext(resources); 540 ResourceReference reference = context.resolveId(id); 541 RenderResources renderResources = context.getRenderResources(); 542 ResourceValue value = renderResources.getResolvedResource(reference); 543 544 if (!(value instanceof ArrayResourceValue)) { 545 throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); 546 } 547 548 ArrayResourceValue arrayValue = (ArrayResourceValue) value; 549 int length = arrayValue.getElementCount(); 550 ResourceNamespace namespace = arrayValue.getNamespace(); 551 BridgeTypedArray typedArray = newTypeArray(resources, length); 552 553 for (int i = 0; i < length; i++) { 554 ResourceValue elementValue; 555 ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i)); 556 if (resourceUrl != null) { 557 ResourceReference elementRef = 558 resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver()); 559 elementValue = renderResources.getResolvedResource(elementRef); 560 } else { 561 elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i, 562 arrayValue.getElement(i)); 563 } 564 typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue); 565 } 566 567 typedArray.sealArray(); 568 return typedArray; 569 } 570 571 @LayoutlibDelegate getDimension(Resources resources, int id)572 static float getDimension(Resources resources, int id) throws NotFoundException { 573 Pair<String, ResourceValue> value = getResourceValue(resources, id); 574 575 if (value != null) { 576 ResourceValue resValue = value.getSecond(); 577 578 assert resValue != null; 579 if (resValue != null) { 580 String v = resValue.getValue(); 581 if (v != null) { 582 if (v.equals(BridgeConstants.MATCH_PARENT) || 583 v.equals(BridgeConstants.FILL_PARENT)) { 584 return LayoutParams.MATCH_PARENT; 585 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { 586 return LayoutParams.WRAP_CONTENT; 587 } 588 TypedValue tmpValue = new TypedValue(); 589 if (ResourceHelper.parseFloatAttribute( 590 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 591 tmpValue.type == TypedValue.TYPE_DIMENSION) { 592 return tmpValue.getDimension(resources.getDisplayMetrics()); 593 } 594 } 595 } 596 } 597 598 // id was not found or not resolved. Throw a NotFoundException. 599 throwException(resources, id); 600 601 // this is not used since the method above always throws 602 return 0; 603 } 604 605 @LayoutlibDelegate getDimensionPixelOffset(Resources resources, int id)606 static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException { 607 Pair<String, ResourceValue> value = getResourceValue(resources, id); 608 609 if (value != null) { 610 ResourceValue resValue = value.getSecond(); 611 612 assert resValue != null; 613 if (resValue != null) { 614 String v = resValue.getValue(); 615 if (v != null) { 616 TypedValue tmpValue = new TypedValue(); 617 if (ResourceHelper.parseFloatAttribute( 618 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 619 tmpValue.type == TypedValue.TYPE_DIMENSION) { 620 return TypedValue.complexToDimensionPixelOffset(tmpValue.data, 621 resources.getDisplayMetrics()); 622 } 623 } 624 } 625 } 626 627 // id was not found or not resolved. Throw a NotFoundException. 628 throwException(resources, id); 629 630 // this is not used since the method above always throws 631 return 0; 632 } 633 634 @LayoutlibDelegate getDimensionPixelSize(Resources resources, int id)635 static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException { 636 Pair<String, ResourceValue> value = getResourceValue(resources, id); 637 638 if (value != null) { 639 ResourceValue resValue = value.getSecond(); 640 641 assert resValue != null; 642 if (resValue != null) { 643 String v = resValue.getValue(); 644 if (v != null) { 645 TypedValue tmpValue = new TypedValue(); 646 if (ResourceHelper.parseFloatAttribute( 647 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 648 tmpValue.type == TypedValue.TYPE_DIMENSION) { 649 return TypedValue.complexToDimensionPixelSize(tmpValue.data, 650 resources.getDisplayMetrics()); 651 } 652 } 653 } 654 } 655 656 // id was not found or not resolved. Throw a NotFoundException. 657 throwException(resources, id); 658 659 // this is not used since the method above always throws 660 return 0; 661 } 662 663 @LayoutlibDelegate getInteger(Resources resources, int id)664 static int getInteger(Resources resources, int id) throws NotFoundException { 665 Pair<String, ResourceValue> value = getResourceValue(resources, id); 666 667 if (value != null) { 668 ResourceValue resValue = value.getSecond(); 669 670 assert resValue != null; 671 if (resValue != null) { 672 String v = resValue.getValue(); 673 if (v != null) { 674 try { 675 return getInt(v); 676 } catch (NumberFormatException e) { 677 // return exception below 678 } 679 } 680 } 681 } 682 683 // id was not found or not resolved. Throw a NotFoundException. 684 throwException(resources, id); 685 686 // this is not used since the method above always throws 687 return 0; 688 } 689 690 @LayoutlibDelegate getFloat(Resources resources, int id)691 static float getFloat(Resources resources, int id) { 692 Pair<String, ResourceValue> value = getResourceValue(resources, id); 693 694 if (value != null) { 695 ResourceValue resValue = value.getSecond(); 696 697 if (resValue != null) { 698 String v = resValue.getValue(); 699 if (v != null) { 700 try { 701 return Float.parseFloat(v); 702 } catch (NumberFormatException ignore) { 703 } 704 } 705 } 706 } 707 return 0; 708 } 709 710 @LayoutlibDelegate getBoolean(Resources resources, int id)711 static boolean getBoolean(Resources resources, int id) throws NotFoundException { 712 Pair<String, ResourceValue> value = getResourceValue(resources, id); 713 714 if (value != null) { 715 ResourceValue resValue = value.getSecond(); 716 717 if (resValue != null) { 718 String v = resValue.getValue(); 719 if (v != null) { 720 return Boolean.parseBoolean(v); 721 } 722 } 723 } 724 725 // id was not found or not resolved. Throw a NotFoundException. 726 throwException(resources, id); 727 728 // this is not used since the method above always throws 729 return false; 730 } 731 732 @LayoutlibDelegate getResourceEntryName(Resources resources, int resid)733 static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { 734 ResourceReference resourceInfo = getResourceInfo(resources, resid); 735 if (resourceInfo != null) { 736 return resourceInfo.getName(); 737 } 738 throwException(resid, null); 739 return null; 740 } 741 742 @LayoutlibDelegate getResourceName(Resources resources, int resid)743 static String getResourceName(Resources resources, int resid) throws NotFoundException { 744 ResourceReference resourceInfo = getResourceInfo(resources, resid); 745 if (resourceInfo != null) { 746 String packageName = getPackageName(resourceInfo, resources); 747 return packageName + ':' + resourceInfo.getResourceType().getName() + '/' + 748 resourceInfo.getName(); 749 } 750 throwException(resid, null); 751 return null; 752 } 753 754 @LayoutlibDelegate getResourcePackageName(Resources resources, int resid)755 static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { 756 ResourceReference resourceInfo = getResourceInfo(resources, resid); 757 if (resourceInfo != null) { 758 return getPackageName(resourceInfo, resources); 759 } 760 throwException(resid, null); 761 return null; 762 } 763 764 @LayoutlibDelegate getResourceTypeName(Resources resources, int resid)765 static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { 766 ResourceReference resourceInfo = getResourceInfo(resources, resid); 767 if (resourceInfo != null) { 768 return resourceInfo.getResourceType().getName(); 769 } 770 throwException(resid, null); 771 return null; 772 } 773 getPackageName(ResourceReference resourceInfo, Resources resources)774 private static String getPackageName(ResourceReference resourceInfo, Resources resources) { 775 String packageName = resourceInfo.getNamespace().getPackageName(); 776 if (packageName == null) { 777 packageName = getContext(resources).getPackageName(); 778 if (packageName == null) { 779 packageName = SdkConstants.APP_PREFIX; 780 } 781 } 782 return packageName; 783 } 784 785 @LayoutlibDelegate getString(Resources resources, int id, Object... formatArgs)786 static String getString(Resources resources, int id, Object... formatArgs) 787 throws NotFoundException { 788 String s = getString(resources, id); 789 if (s != null) { 790 return String.format(s, formatArgs); 791 792 } 793 794 // id was not found or not resolved. Throw a NotFoundException. 795 throwException(resources, id); 796 797 // this is not used since the method above always throws 798 return null; 799 } 800 801 @LayoutlibDelegate getString(Resources resources, int id)802 static String getString(Resources resources, int id) throws NotFoundException { 803 Pair<String, ResourceValue> value = getResourceValue(resources, id); 804 805 if (value != null && value.getSecond().getValue() != null) { 806 return value.getSecond().getValue(); 807 } 808 809 // id was not found or not resolved. Throw a NotFoundException. 810 throwException(resources, id); 811 812 // this is not used since the method above always throws 813 return null; 814 } 815 816 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity)817 static String getQuantityString(Resources resources, int id, int quantity) throws 818 NotFoundException { 819 Pair<String, ResourceValue> value = getResourceValue(resources, id); 820 821 if (value != null) { 822 if (value.getSecond() instanceof PluralsResourceValue) { 823 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); 824 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() 825 .get(0)); 826 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); 827 if (strValue == null) { 828 strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); 829 } 830 831 return strValue; 832 } 833 else { 834 return value.getSecond().getValue(); 835 } 836 } 837 838 // id was not found or not resolved. Throw a NotFoundException. 839 throwException(resources, id); 840 841 // this is not used since the method above always throws 842 return null; 843 } 844 845 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)846 static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) 847 throws NotFoundException { 848 String raw = getQuantityString(resources, id, quantity); 849 return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); 850 } 851 852 @LayoutlibDelegate getQuantityText(Resources resources, int id, int quantity)853 static CharSequence getQuantityText(Resources resources, int id, int quantity) throws 854 NotFoundException { 855 return getQuantityString(resources, id, quantity); 856 } 857 858 @LayoutlibDelegate getFont(Resources resources, int id)859 static Typeface getFont(Resources resources, int id) throws 860 NotFoundException { 861 Pair<String, ResourceValue> value = getResourceValue(resources, id); 862 if (value != null) { 863 return ResourceHelper.getFont(value.getSecond(), getContext(resources), null); 864 } 865 866 throwException(resources, id); 867 868 // this is not used since the method above always throws 869 return null; 870 } 871 872 @LayoutlibDelegate getFont(Resources resources, TypedValue outValue, int id)873 static Typeface getFont(Resources resources, TypedValue outValue, int id) throws 874 NotFoundException { 875 ResourceValue resVal = getResourceValue(resources, id, outValue); 876 if (resVal != null) { 877 return ResourceHelper.getFont(resVal, getContext(resources), null); 878 } 879 880 throwException(resources, id); 881 return null; // This is not used since the method above always throws. 882 } 883 884 @LayoutlibDelegate getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)885 static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) 886 throws NotFoundException { 887 getResourceValue(resources, id, outValue); 888 } 889 getResourceValue(Resources resources, int id, TypedValue outValue)890 private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue) 891 throws NotFoundException { 892 Pair<String, ResourceValue> value = getResourceValue(resources, id); 893 894 if (value != null) { 895 ResourceValue resVal = value.getSecond(); 896 String v = resVal != null ? resVal.getValue() : null; 897 898 if (v != null) { 899 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 900 false /*requireUnit*/)) { 901 return resVal; 902 } 903 if (resVal instanceof DensityBasedResourceValue) { 904 outValue.density = 905 ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); 906 } 907 908 // else it's a string 909 outValue.type = TypedValue.TYPE_STRING; 910 outValue.string = v; 911 return resVal; 912 } 913 } 914 915 // id was not found or not resolved. Throw a NotFoundException. 916 throwException(resources, id); 917 return null; // This is not used since the method above always throws. 918 } 919 920 @LayoutlibDelegate getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)921 static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs) 922 throws NotFoundException { 923 throw new UnsupportedOperationException(); 924 } 925 926 @LayoutlibDelegate getValueForDensity(Resources resources, int id, int density, TypedValue outValue, boolean resolveRefs)927 static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue, 928 boolean resolveRefs) throws NotFoundException { 929 getValue(resources, id, outValue, resolveRefs); 930 } 931 932 @LayoutlibDelegate getAttributeSetSourceResId(@ullable AttributeSet set)933 static int getAttributeSetSourceResId(@Nullable AttributeSet set) { 934 // Not supported in layoutlib 935 return Resources.ID_NULL; 936 } 937 938 @LayoutlibDelegate getXml(Resources resources, int id)939 static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException { 940 Pair<String, ResourceValue> v = getResourceValue(resources, id); 941 942 if (v != null) { 943 ResourceValue value = v.getSecond(); 944 945 try { 946 return ResourceHelper.getXmlBlockParser(getContext(resources), value); 947 } catch (XmlPullParserException e) { 948 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 949 "Failed to parse " + value.getValue(), e, null /*data*/); 950 // we'll return null below. 951 } 952 } 953 954 // id was not found or not resolved. Throw a NotFoundException. 955 throwException(resources, id); 956 957 // this is not used since the method above always throws 958 return null; 959 } 960 961 @LayoutlibDelegate loadXmlResourceParser(Resources resources, int id, String type)962 static XmlResourceParser loadXmlResourceParser(Resources resources, int id, 963 String type) throws NotFoundException { 964 return resources.loadXmlResourceParser_Original(id, type); 965 } 966 967 @LayoutlibDelegate loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)968 static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id, 969 int assetCookie, String type) throws NotFoundException { 970 // even though we know the XML file to load directly, we still need to resolve the 971 // id so that we can know if it's a platform or project resource. 972 // (mPlatformResouceFlag will get the result and will be used later). 973 Pair<String, ResourceValue> result = getResourceValue(resources, id); 974 975 ResourceNamespace layoutNamespace; 976 if (result != null && result.getSecond() != null) { 977 layoutNamespace = result.getSecond().getNamespace(); 978 } else { 979 // We need to pick something, even though the resource system never heard about a layout 980 // with this numeric id. 981 layoutNamespace = ResourceNamespace.RES_AUTO; 982 } 983 984 try { 985 XmlPullParser parser = ParserFactory.create(file); 986 return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace); 987 } catch (XmlPullParserException e) { 988 NotFoundException newE = new NotFoundException(); 989 newE.initCause(e); 990 throw newE; 991 } 992 } 993 994 @LayoutlibDelegate openRawResource(Resources resources, int id)995 static InputStream openRawResource(Resources resources, int id) throws NotFoundException { 996 Pair<String, ResourceValue> value = getResourceValue(resources, id); 997 998 if (value != null) { 999 String path = value.getSecond().getValue(); 1000 if (path != null) { 1001 return openRawResource(resources, path); 1002 } 1003 } 1004 1005 // id was not found or not resolved. Throw a NotFoundException. 1006 throwException(resources, id); 1007 1008 // this is not used since the method above always throws 1009 return null; 1010 } 1011 1012 @LayoutlibDelegate openRawResource(Resources resources, int id, TypedValue value)1013 static InputStream openRawResource(Resources resources, int id, TypedValue value) 1014 throws NotFoundException { 1015 getValue(resources, id, value, true); 1016 1017 String path = value.string.toString(); 1018 return openRawResource(resources, path); 1019 } 1020 openRawResource(Resources resources, String path)1021 private static InputStream openRawResource(Resources resources, String path) 1022 throws NotFoundException { 1023 AssetRepository repository = getAssetRepository(resources); 1024 try { 1025 InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING); 1026 if (stream == null) { 1027 throw new NotFoundException(path); 1028 } 1029 // If it's a nine-patch return a custom input stream so that 1030 // other methods (mainly bitmap factory) can detect it's a 9-patch 1031 // and actually load it as a 9-patch instead of a normal bitmap. 1032 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 1033 return new NinePatchInputStream(stream); 1034 } 1035 return stream; 1036 } catch (IOException e) { 1037 NotFoundException exception = new NotFoundException(); 1038 exception.initCause(e); 1039 throw exception; 1040 } 1041 } 1042 1043 @LayoutlibDelegate openRawResourceFd(Resources resources, int id)1044 static AssetFileDescriptor openRawResourceFd(Resources resources, int id) 1045 throws NotFoundException { 1046 throw new UnsupportedOperationException(); 1047 } 1048 1049 @VisibleForTesting 1050 @Nullable resourceUrlFromName( @onNull String name, @Nullable String defType, @Nullable String defPackage)1051 static ResourceUrl resourceUrlFromName( 1052 @NonNull String name, @Nullable String defType, @Nullable String defPackage) { 1053 int colonIdx = name.indexOf(':'); 1054 int slashIdx = name.indexOf('/'); 1055 1056 if (colonIdx != -1 && slashIdx != -1) { 1057 // Easy case 1058 return ResourceUrl.parse(PREFIX_RESOURCE_REF + name); 1059 } 1060 1061 if (colonIdx == -1 && slashIdx == -1) { 1062 if (defType == null) { 1063 throw new IllegalArgumentException("name does not define a type an no defType was" + 1064 " passed"); 1065 } 1066 1067 // It does not define package or type 1068 return ResourceUrl.parse( 1069 PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType + 1070 "/" + name); 1071 } 1072 1073 if (colonIdx != -1) { 1074 if (defType == null) { 1075 throw new IllegalArgumentException("name does not define a type an no defType was" + 1076 " passed"); 1077 } 1078 // We have package but no type 1079 String pkg = name.substring(0, colonIdx); 1080 ResourceType type = ResourceType.getEnum(defType); 1081 return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) : 1082 null; 1083 } 1084 1085 ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx)); 1086 if (type == null) { 1087 return null; 1088 } 1089 // We have type but no package 1090 return ResourceUrl.create(defPackage, 1091 type, 1092 name.substring(slashIdx + 1)); 1093 } 1094 1095 @LayoutlibDelegate getIdentifier(Resources resources, String name, String defType, String defPackage)1096 static int getIdentifier(Resources resources, String name, String defType, String defPackage) { 1097 if (name == null) { 1098 return 0; 1099 } 1100 1101 ResourceUrl url = resourceUrlFromName(name, defType, defPackage); 1102 if (url != null) { 1103 if (ANDROID_PKG.equals(url.namespace)) { 1104 return Bridge.getResourceId(url.type, url.name); 1105 } 1106 1107 if (getContext(resources).getPackageName().equals(url.namespace)) { 1108 return getLayoutlibCallback(resources).getOrGenerateResourceId( 1109 new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name)); 1110 } 1111 } 1112 1113 return 0; 1114 } 1115 1116 /** 1117 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource 1118 * type. 1119 * 1120 * @param id the id of the resource 1121 * @param expectedType the type of resource that was expected 1122 * 1123 * @throws NotFoundException 1124 */ throwException(Resources resources, int id, @Nullable String expectedType)1125 private static void throwException(Resources resources, int id, @Nullable String expectedType) 1126 throws NotFoundException { 1127 throwException(id, getResourceInfo(resources, id), expectedType); 1128 } 1129 throwException(Resources resources, int id)1130 private static void throwException(Resources resources, int id) throws NotFoundException { 1131 throwException(resources, id, null); 1132 } 1133 throwException(int id, @Nullable ResourceReference resourceInfo)1134 private static void throwException(int id, @Nullable ResourceReference resourceInfo) { 1135 throwException(id, resourceInfo, null); 1136 } throwException(int id, @Nullable ResourceReference resourceInfo, @Nullable String expectedType)1137 private static void throwException(int id, @Nullable ResourceReference resourceInfo, 1138 @Nullable String expectedType) { 1139 String message; 1140 if (resourceInfo != null) { 1141 message = String.format( 1142 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 1143 resourceInfo.getResourceType(), id, resourceInfo.getName()); 1144 } else { 1145 message = String.format("Could not resolve resource value: 0x%1$X.", id); 1146 } 1147 1148 if (expectedType != null) { 1149 message += " Or the resolved value was not of type " + expectedType + " as expected."; 1150 } 1151 throw new NotFoundException(message); 1152 } 1153 getInt(String v)1154 private static int getInt(String v) throws NumberFormatException { 1155 int radix = 10; 1156 if (v.startsWith("0x")) { 1157 v = v.substring(2); 1158 radix = 16; 1159 } else if (v.startsWith("0")) { 1160 radix = 8; 1161 } 1162 return Integer.parseInt(v, radix); 1163 } 1164 getAssetRepository(Resources resources)1165 private static AssetRepository getAssetRepository(Resources resources) { 1166 BridgeContext context = getContext(resources); 1167 BridgeAssetManager assetManager = context.getAssets(); 1168 return assetManager.getAssetRepository(); 1169 } 1170 } 1171