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