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