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.Typeface; 55 import android.graphics.drawable.Drawable; 56 import android.graphics.drawable.DrawableInflater_Delegate; 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 DrawableInflater_Delegate.clearConstructorCache(); 125 Resources.mSystem = null; 126 } 127 newTypeArray(Resources resources, int numEntries)128 public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) { 129 return new BridgeTypedArray(resources, getContext(resources), numEntries); 130 } 131 getResourceInfo(Resources resources, int id)132 private static ResourceReference getResourceInfo(Resources resources, int id) { 133 // first get the String related to this id in the framework 134 ResourceReference resourceInfo = Bridge.resolveResourceId(id); 135 136 assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called"; 137 // Set the layoutlib callback and context for resources 138 if (resources != Resources.mSystem && 139 (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) { 140 sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem)); 141 sContexts.put(resources, getContext(Resources.mSystem)); 142 } 143 144 if (resourceInfo == null) { 145 // Didn't find a match in the framework? Look in the project. 146 resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id); 147 } 148 149 return resourceInfo; 150 } 151 getResourceValue(Resources resources, int id)152 private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id) { 153 ResourceReference resourceInfo = getResourceInfo(resources, id); 154 155 if (resourceInfo != null) { 156 String attributeName = resourceInfo.getName(); 157 RenderResources renderResources = getContext(resources).getRenderResources(); 158 ResourceValue value = renderResources.getResolvedResource(resourceInfo); 159 if (value == null) { 160 // Unable to resolve the attribute, just leave the unresolved value. 161 value = new ResourceValueImpl(resourceInfo.getNamespace(), 162 resourceInfo.getResourceType(), attributeName, attributeName); 163 } 164 return Pair.of(attributeName, value); 165 } 166 167 return null; 168 } 169 170 @LayoutlibDelegate getDrawable(Resources resources, int id)171 static Drawable getDrawable(Resources resources, int id) { 172 return getDrawable(resources, id, null); 173 } 174 175 @LayoutlibDelegate getDrawable(Resources resources, int id, Theme theme)176 static Drawable getDrawable(Resources resources, int id, Theme theme) { 177 Pair<String, ResourceValue> value = getResourceValue(resources, id); 178 if (value != null) { 179 String key = value.getSecond().getValue(); 180 181 Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null; 182 Drawable drawable; 183 if (constantState != null) { 184 drawable = constantState.newDrawable(resources, theme); 185 } else { 186 drawable = 187 ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme); 188 189 if (key != null) { 190 sDrawableCache.put(key, drawable.getConstantState()); 191 } 192 } 193 194 return drawable; 195 } 196 197 // id was not found or not resolved. Throw a NotFoundException. 198 throwException(resources, id); 199 200 // this is not used since the method above always throws 201 return null; 202 } 203 204 @LayoutlibDelegate getColor(Resources resources, int id)205 static int getColor(Resources resources, int id) { 206 return getColor(resources, id, null); 207 } 208 209 @LayoutlibDelegate getColor(Resources resources, int id, Theme theme)210 static int getColor(Resources resources, int id, Theme theme) throws NotFoundException { 211 Pair<String, ResourceValue> value = getResourceValue(resources, id); 212 213 if (value != null) { 214 ResourceValue resourceValue = value.getSecond(); 215 try { 216 return ResourceHelper.getColor(resourceValue.getValue()); 217 } catch (NumberFormatException e) { 218 // Check if the value passed is a file. If it is, mostly likely, user is referencing 219 // a color state list from a place where they should reference only a pure color. 220 AssetRepository repository = getAssetRepository(resources); 221 String message; 222 if (repository.isFileResource(resourceValue.getValue())) { 223 String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/" 224 + resourceValue.getName(); 225 message = "Hexadecimal color expected, found Color State List for " + resource; 226 } else { 227 message = e.getMessage(); 228 } 229 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null, null); 230 return 0; 231 } 232 } 233 234 // Suppress possible NPE. getColorStateList will never return null, it will instead 235 // throw an exception, but intelliJ can't figure that out 236 //noinspection ConstantConditions 237 return getColorStateList(resources, id, theme).getDefaultColor(); 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.getSecond(), 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.getSecond(); 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.getSecond(); 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(LayoutLog.TAG_RESOURCES_FORMAT, 376 "Integer resource array contains non-integer value: \"" + element + 377 "\"", null, null); 378 } catch (IllegalArgumentException e) { 379 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 380 "Integer resource array contains wrong color format: \"" + element + 381 "\"", null, null); 382 } 383 } else { 384 Bridge.getLog().error(LayoutLog.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(LayoutLog.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(LayoutLog.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.getSecond(); 430 431 assert resValue != null; 432 if (resValue != null) { 433 final ResourceType type = resValue.getResourceType(); 434 if (type != ResourceType.ARRAY) { 435 Bridge.getLog().error(LayoutLog.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(LayoutLog.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.getSecond(); 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(LayoutLog.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.getSecond(); 510 511 try { 512 return ResourceHelper.getXmlBlockParser(getContext(resources), value); 513 } catch (XmlPullParserException e) { 514 Bridge.getLog().error(LayoutLog.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 return getContext(resources).obtainStyledAttributes(set, attrs); 530 } 531 532 @LayoutlibDelegate obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet set, int[] attrs)533 static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet 534 set, int[] attrs) { 535 return Resources.obtainAttributes_Original(resources, theme, set, attrs); 536 } 537 538 @LayoutlibDelegate obtainTypedArray(Resources resources, int id)539 static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException { 540 BridgeContext context = getContext(resources); 541 ResourceReference reference = context.resolveId(id); 542 RenderResources renderResources = context.getRenderResources(); 543 ResourceValue value = renderResources.getResolvedResource(reference); 544 545 if (!(value instanceof ArrayResourceValue)) { 546 throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); 547 } 548 549 ArrayResourceValue arrayValue = (ArrayResourceValue) value; 550 int length = arrayValue.getElementCount(); 551 ResourceNamespace namespace = arrayValue.getNamespace(); 552 BridgeTypedArray typedArray = newTypeArray(resources, length); 553 554 for (int i = 0; i < length; i++) { 555 ResourceValue elementValue; 556 ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i)); 557 if (resourceUrl != null) { 558 ResourceReference elementRef = 559 resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver()); 560 elementValue = renderResources.getResolvedResource(elementRef); 561 } else { 562 elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i, 563 arrayValue.getElement(i)); 564 } 565 typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue); 566 } 567 568 typedArray.sealArray(); 569 return typedArray; 570 } 571 572 @LayoutlibDelegate getDimension(Resources resources, int id)573 static float getDimension(Resources resources, int id) throws NotFoundException { 574 Pair<String, ResourceValue> value = getResourceValue(resources, id); 575 576 if (value != null) { 577 ResourceValue resValue = value.getSecond(); 578 579 assert resValue != null; 580 if (resValue != null) { 581 String v = resValue.getValue(); 582 if (v != null) { 583 if (v.equals(BridgeConstants.MATCH_PARENT) || 584 v.equals(BridgeConstants.FILL_PARENT)) { 585 return LayoutParams.MATCH_PARENT; 586 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { 587 return LayoutParams.WRAP_CONTENT; 588 } 589 TypedValue tmpValue = new TypedValue(); 590 if (ResourceHelper.parseFloatAttribute( 591 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 592 tmpValue.type == TypedValue.TYPE_DIMENSION) { 593 return tmpValue.getDimension(resources.getDisplayMetrics()); 594 } 595 } 596 } 597 } 598 599 // id was not found or not resolved. Throw a NotFoundException. 600 throwException(resources, id); 601 602 // this is not used since the method above always throws 603 return 0; 604 } 605 606 @LayoutlibDelegate getDimensionPixelOffset(Resources resources, int id)607 static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException { 608 Pair<String, ResourceValue> value = getResourceValue(resources, id); 609 610 if (value != null) { 611 ResourceValue resValue = value.getSecond(); 612 613 assert resValue != null; 614 if (resValue != null) { 615 String v = resValue.getValue(); 616 if (v != null) { 617 TypedValue tmpValue = new TypedValue(); 618 if (ResourceHelper.parseFloatAttribute( 619 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 620 tmpValue.type == TypedValue.TYPE_DIMENSION) { 621 return TypedValue.complexToDimensionPixelOffset(tmpValue.data, 622 resources.getDisplayMetrics()); 623 } 624 } 625 } 626 } 627 628 // id was not found or not resolved. Throw a NotFoundException. 629 throwException(resources, id); 630 631 // this is not used since the method above always throws 632 return 0; 633 } 634 635 @LayoutlibDelegate getDimensionPixelSize(Resources resources, int id)636 static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException { 637 Pair<String, ResourceValue> value = getResourceValue(resources, id); 638 639 if (value != null) { 640 ResourceValue resValue = value.getSecond(); 641 642 assert resValue != null; 643 if (resValue != null) { 644 String v = resValue.getValue(); 645 if (v != null) { 646 TypedValue tmpValue = new TypedValue(); 647 if (ResourceHelper.parseFloatAttribute( 648 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 649 tmpValue.type == TypedValue.TYPE_DIMENSION) { 650 return TypedValue.complexToDimensionPixelSize(tmpValue.data, 651 resources.getDisplayMetrics()); 652 } 653 } 654 } 655 } 656 657 // id was not found or not resolved. Throw a NotFoundException. 658 throwException(resources, id); 659 660 // this is not used since the method above always throws 661 return 0; 662 } 663 664 @LayoutlibDelegate getInteger(Resources resources, int id)665 static int getInteger(Resources resources, int id) throws NotFoundException { 666 Pair<String, ResourceValue> value = getResourceValue(resources, id); 667 668 if (value != null) { 669 ResourceValue resValue = value.getSecond(); 670 671 assert resValue != null; 672 if (resValue != null) { 673 String v = resValue.getValue(); 674 if (v != null) { 675 try { 676 return getInt(v); 677 } catch (NumberFormatException e) { 678 // return exception below 679 } 680 } 681 } 682 } 683 684 // id was not found or not resolved. Throw a NotFoundException. 685 throwException(resources, id); 686 687 // this is not used since the method above always throws 688 return 0; 689 } 690 691 @LayoutlibDelegate getFloat(Resources resources, int id)692 static float getFloat(Resources resources, int id) { 693 Pair<String, ResourceValue> value = getResourceValue(resources, id); 694 695 if (value != null) { 696 ResourceValue resValue = value.getSecond(); 697 698 if (resValue != null) { 699 String v = resValue.getValue(); 700 if (v != null) { 701 try { 702 return Float.parseFloat(v); 703 } catch (NumberFormatException ignore) { 704 } 705 } 706 } 707 } 708 return 0; 709 } 710 711 @LayoutlibDelegate getBoolean(Resources resources, int id)712 static boolean getBoolean(Resources resources, int id) throws NotFoundException { 713 Pair<String, ResourceValue> value = getResourceValue(resources, id); 714 715 if (value != null) { 716 ResourceValue resValue = value.getSecond(); 717 718 if (resValue != null) { 719 String v = resValue.getValue(); 720 if (v != null) { 721 return Boolean.parseBoolean(v); 722 } 723 } 724 } 725 726 // id was not found or not resolved. Throw a NotFoundException. 727 throwException(resources, id); 728 729 // this is not used since the method above always throws 730 return false; 731 } 732 733 @LayoutlibDelegate getResourceEntryName(Resources resources, int resid)734 static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { 735 ResourceReference resourceInfo = getResourceInfo(resources, resid); 736 if (resourceInfo != null) { 737 return resourceInfo.getName(); 738 } 739 throwException(resid, null); 740 return null; 741 } 742 743 @LayoutlibDelegate getResourceName(Resources resources, int resid)744 static String getResourceName(Resources resources, int resid) throws NotFoundException { 745 ResourceReference resourceInfo = getResourceInfo(resources, resid); 746 if (resourceInfo != null) { 747 String packageName = getPackageName(resourceInfo, resources); 748 return packageName + ':' + resourceInfo.getResourceType().getName() + '/' + 749 resourceInfo.getName(); 750 } 751 throwException(resid, null); 752 return null; 753 } 754 755 @LayoutlibDelegate getResourcePackageName(Resources resources, int resid)756 static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { 757 ResourceReference resourceInfo = getResourceInfo(resources, resid); 758 if (resourceInfo != null) { 759 return getPackageName(resourceInfo, resources); 760 } 761 throwException(resid, null); 762 return null; 763 } 764 765 @LayoutlibDelegate getResourceTypeName(Resources resources, int resid)766 static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { 767 ResourceReference resourceInfo = getResourceInfo(resources, resid); 768 if (resourceInfo != null) { 769 return resourceInfo.getResourceType().getName(); 770 } 771 throwException(resid, null); 772 return null; 773 } 774 getPackageName(ResourceReference resourceInfo, Resources resources)775 private static String getPackageName(ResourceReference resourceInfo, Resources resources) { 776 String packageName = resourceInfo.getNamespace().getPackageName(); 777 if (packageName == null) { 778 packageName = getContext(resources).getPackageName(); 779 if (packageName == null) { 780 packageName = SdkConstants.APP_PREFIX; 781 } 782 } 783 return packageName; 784 } 785 786 @LayoutlibDelegate getString(Resources resources, int id, Object... formatArgs)787 static String getString(Resources resources, int id, Object... formatArgs) 788 throws NotFoundException { 789 String s = getString(resources, id); 790 if (s != null) { 791 return String.format(s, formatArgs); 792 793 } 794 795 // id was not found or not resolved. Throw a NotFoundException. 796 throwException(resources, id); 797 798 // this is not used since the method above always throws 799 return null; 800 } 801 802 @LayoutlibDelegate getString(Resources resources, int id)803 static String getString(Resources resources, int id) throws NotFoundException { 804 Pair<String, ResourceValue> value = getResourceValue(resources, id); 805 806 if (value != null && value.getSecond().getValue() != null) { 807 return value.getSecond().getValue(); 808 } 809 810 // id was not found or not resolved. Throw a NotFoundException. 811 throwException(resources, id); 812 813 // this is not used since the method above always throws 814 return null; 815 } 816 817 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity)818 static String getQuantityString(Resources resources, int id, int quantity) throws 819 NotFoundException { 820 Pair<String, ResourceValue> value = getResourceValue(resources, id); 821 822 if (value != null) { 823 if (value.getSecond() instanceof PluralsResourceValue) { 824 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); 825 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() 826 .get(0)); 827 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); 828 if (strValue == null) { 829 strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); 830 } 831 832 return strValue; 833 } 834 else { 835 return value.getSecond().getValue(); 836 } 837 } 838 839 // id was not found or not resolved. Throw a NotFoundException. 840 throwException(resources, id); 841 842 // this is not used since the method above always throws 843 return null; 844 } 845 846 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)847 static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) 848 throws NotFoundException { 849 String raw = getQuantityString(resources, id, quantity); 850 return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); 851 } 852 853 @LayoutlibDelegate getQuantityText(Resources resources, int id, int quantity)854 static CharSequence getQuantityText(Resources resources, int id, int quantity) throws 855 NotFoundException { 856 return getQuantityString(resources, id, quantity); 857 } 858 859 @LayoutlibDelegate getFont(Resources resources, int id)860 static Typeface getFont(Resources resources, int id) throws 861 NotFoundException { 862 Pair<String, ResourceValue> value = getResourceValue(resources, id); 863 if (value != null) { 864 return ResourceHelper.getFont(value.getSecond(), getContext(resources), null); 865 } 866 867 throwException(resources, id); 868 869 // this is not used since the method above always throws 870 return null; 871 } 872 873 @LayoutlibDelegate getFont(Resources resources, TypedValue outValue, int id)874 static Typeface getFont(Resources resources, TypedValue outValue, int id) throws 875 NotFoundException { 876 ResourceValue resVal = getResourceValue(resources, id, outValue); 877 if (resVal != null) { 878 return ResourceHelper.getFont(resVal, getContext(resources), null); 879 } 880 881 throwException(resources, id); 882 return null; // This is not used since the method above always throws. 883 } 884 885 @LayoutlibDelegate getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)886 static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) 887 throws NotFoundException { 888 getResourceValue(resources, id, outValue); 889 } 890 getResourceValue(Resources resources, int id, TypedValue outValue)891 private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue) 892 throws NotFoundException { 893 Pair<String, ResourceValue> value = getResourceValue(resources, id); 894 895 if (value != null) { 896 ResourceValue resVal = value.getSecond(); 897 String v = resVal != null ? resVal.getValue() : null; 898 899 if (v != null) { 900 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 901 false /*requireUnit*/)) { 902 return resVal; 903 } 904 if (resVal instanceof DensityBasedResourceValue) { 905 outValue.density = 906 ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); 907 } 908 909 // else it's a string 910 outValue.type = TypedValue.TYPE_STRING; 911 outValue.string = v; 912 return resVal; 913 } 914 } 915 916 // id was not found or not resolved. Throw a NotFoundException. 917 throwException(resources, id); 918 return null; // This is not used since the method above always throws. 919 } 920 921 @LayoutlibDelegate getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)922 static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs) 923 throws NotFoundException { 924 throw new UnsupportedOperationException(); 925 } 926 927 @LayoutlibDelegate getValueForDensity(Resources resources, int id, int density, TypedValue outValue, boolean resolveRefs)928 static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue, 929 boolean resolveRefs) throws NotFoundException { 930 getValue(resources, id, outValue, resolveRefs); 931 } 932 933 @LayoutlibDelegate getAttributeSetSourceResId(@ullable AttributeSet set)934 static int getAttributeSetSourceResId(@Nullable AttributeSet set) { 935 // Not supported in layoutlib 936 return Resources.ID_NULL; 937 } 938 939 @LayoutlibDelegate getXml(Resources resources, int id)940 static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException { 941 Pair<String, ResourceValue> v = getResourceValue(resources, id); 942 943 if (v != null) { 944 ResourceValue value = v.getSecond(); 945 946 try { 947 return ResourceHelper.getXmlBlockParser(getContext(resources), value); 948 } catch (XmlPullParserException e) { 949 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 950 "Failed to parse " + value.getValue(), e, null, null /*data*/); 951 // we'll return null below. 952 } 953 } 954 955 // id was not found or not resolved. Throw a NotFoundException. 956 throwException(resources, id); 957 958 // this is not used since the method above always throws 959 return null; 960 } 961 962 @LayoutlibDelegate loadXmlResourceParser(Resources resources, int id, String type)963 static XmlResourceParser loadXmlResourceParser(Resources resources, int id, 964 String type) throws NotFoundException { 965 return resources.loadXmlResourceParser_Original(id, type); 966 } 967 968 @LayoutlibDelegate loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)969 static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id, 970 int assetCookie, String type) throws NotFoundException { 971 // even though we know the XML file to load directly, we still need to resolve the 972 // id so that we can know if it's a platform or project resource. 973 // (mPlatformResouceFlag will get the result and will be used later). 974 Pair<String, ResourceValue> result = getResourceValue(resources, id); 975 976 ResourceNamespace layoutNamespace; 977 if (result != null && result.getSecond() != null) { 978 layoutNamespace = result.getSecond().getNamespace(); 979 } else { 980 // We need to pick something, even though the resource system never heard about a layout 981 // with this numeric id. 982 layoutNamespace = ResourceNamespace.RES_AUTO; 983 } 984 985 try { 986 XmlPullParser parser = ParserFactory.create(file); 987 return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace); 988 } catch (XmlPullParserException e) { 989 NotFoundException newE = new NotFoundException(); 990 newE.initCause(e); 991 throw newE; 992 } 993 } 994 995 @LayoutlibDelegate openRawResource(Resources resources, int id)996 static InputStream openRawResource(Resources resources, int id) throws NotFoundException { 997 Pair<String, ResourceValue> value = getResourceValue(resources, id); 998 999 if (value != null) { 1000 String path = value.getSecond().getValue(); 1001 if (path != null) { 1002 return openRawResource(resources, path); 1003 } 1004 } 1005 1006 // id was not found or not resolved. Throw a NotFoundException. 1007 throwException(resources, id); 1008 1009 // this is not used since the method above always throws 1010 return null; 1011 } 1012 1013 @LayoutlibDelegate openRawResource(Resources resources, int id, TypedValue value)1014 static InputStream openRawResource(Resources resources, int id, TypedValue value) 1015 throws NotFoundException { 1016 getValue(resources, id, value, true); 1017 1018 String path = value.string.toString(); 1019 return openRawResource(resources, path); 1020 } 1021 openRawResource(Resources resources, String path)1022 private static InputStream openRawResource(Resources resources, String path) 1023 throws NotFoundException { 1024 AssetRepository repository = getAssetRepository(resources); 1025 try { 1026 InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING); 1027 if (stream == null) { 1028 throw new NotFoundException(path); 1029 } 1030 // If it's a nine-patch return a custom input stream so that 1031 // other methods (mainly bitmap factory) can detect it's a 9-patch 1032 // and actually load it as a 9-patch instead of a normal bitmap. 1033 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 1034 return new NinePatchInputStream(stream); 1035 } 1036 return stream; 1037 } catch (IOException e) { 1038 NotFoundException exception = new NotFoundException(); 1039 exception.initCause(e); 1040 throw exception; 1041 } 1042 } 1043 1044 @LayoutlibDelegate openRawResourceFd(Resources resources, int id)1045 static AssetFileDescriptor openRawResourceFd(Resources resources, int id) 1046 throws NotFoundException { 1047 throw new UnsupportedOperationException(); 1048 } 1049 1050 @VisibleForTesting 1051 @Nullable resourceUrlFromName( @onNull String name, @Nullable String defType, @Nullable String defPackage)1052 static ResourceUrl resourceUrlFromName( 1053 @NonNull String name, @Nullable String defType, @Nullable String defPackage) { 1054 int colonIdx = name.indexOf(':'); 1055 int slashIdx = name.indexOf('/'); 1056 1057 if (colonIdx != -1 && slashIdx != -1) { 1058 // Easy case 1059 return ResourceUrl.parse(PREFIX_RESOURCE_REF + name); 1060 } 1061 1062 if (colonIdx == -1 && slashIdx == -1) { 1063 if (defType == null) { 1064 throw new IllegalArgumentException("name does not define a type an no defType was" + 1065 " passed"); 1066 } 1067 1068 // It does not define package or type 1069 return ResourceUrl.parse( 1070 PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType + 1071 "/" + name); 1072 } 1073 1074 if (colonIdx != -1) { 1075 if (defType == null) { 1076 throw new IllegalArgumentException("name does not define a type an no defType was" + 1077 " passed"); 1078 } 1079 // We have package but no type 1080 String pkg = name.substring(0, colonIdx); 1081 ResourceType type = ResourceType.getEnum(defType); 1082 return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) : 1083 null; 1084 } 1085 1086 ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx)); 1087 if (type == null) { 1088 return null; 1089 } 1090 // We have type but no package 1091 return ResourceUrl.create(defPackage, 1092 type, 1093 name.substring(slashIdx + 1)); 1094 } 1095 1096 @LayoutlibDelegate getIdentifier(Resources resources, String name, String defType, String defPackage)1097 static int getIdentifier(Resources resources, String name, String defType, String defPackage) { 1098 if (name == null) { 1099 return 0; 1100 } 1101 1102 ResourceUrl url = resourceUrlFromName(name, defType, defPackage); 1103 if (url != null) { 1104 if (ANDROID_PKG.equals(url.namespace)) { 1105 return Bridge.getResourceId(url.type, url.name); 1106 } 1107 1108 if (getContext(resources).getPackageName().equals(url.namespace)) { 1109 return getLayoutlibCallback(resources).getOrGenerateResourceId( 1110 new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name)); 1111 } 1112 } 1113 1114 return 0; 1115 } 1116 1117 /** 1118 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource 1119 * type. 1120 * 1121 * @param id the id of the resource 1122 * @param expectedType the type of resource that was expected 1123 * 1124 * @throws NotFoundException 1125 */ throwException(Resources resources, int id, @Nullable String expectedType)1126 private static void throwException(Resources resources, int id, @Nullable String expectedType) 1127 throws NotFoundException { 1128 throwException(id, getResourceInfo(resources, id), expectedType); 1129 } 1130 throwException(Resources resources, int id)1131 private static void throwException(Resources resources, int id) throws NotFoundException { 1132 throwException(resources, id, null); 1133 } 1134 throwException(int id, @Nullable ResourceReference resourceInfo)1135 private static void throwException(int id, @Nullable ResourceReference resourceInfo) { 1136 throwException(id, resourceInfo, null); 1137 } throwException(int id, @Nullable ResourceReference resourceInfo, @Nullable String expectedType)1138 private static void throwException(int id, @Nullable ResourceReference resourceInfo, 1139 @Nullable String expectedType) { 1140 String message; 1141 if (resourceInfo != null) { 1142 message = String.format( 1143 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 1144 resourceInfo.getResourceType(), id, resourceInfo.getName()); 1145 } else { 1146 message = String.format("Could not resolve resource value: 0x%1$X.", id); 1147 } 1148 1149 if (expectedType != null) { 1150 message += " Or the resolved value was not of type " + expectedType + " as expected."; 1151 } 1152 throw new NotFoundException(message); 1153 } 1154 getInt(String v)1155 private static int getInt(String v) throws NumberFormatException { 1156 int radix = 10; 1157 if (v.startsWith("0x")) { 1158 v = v.substring(2); 1159 radix = 16; 1160 } else if (v.startsWith("0")) { 1161 radix = 8; 1162 } 1163 return Integer.parseInt(v, radix); 1164 } 1165 getAssetRepository(Resources resources)1166 private static AssetRepository getAssetRepository(Resources resources) { 1167 BridgeContext context = getContext(resources); 1168 BridgeAssetManager assetManager = context.getAssets(); 1169 return assetManager.getAssetRepository(); 1170 } 1171 } 1172