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