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