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