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 getBoolean(Resources resources, int id)654 static boolean getBoolean(Resources resources, int id) throws NotFoundException { 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 return Boolean.parseBoolean(v); 664 } 665 } 666 } 667 668 // id was not found or not resolved. Throw a NotFoundException. 669 throwException(resources, id); 670 671 // this is not used since the method above always throws 672 return false; 673 } 674 675 @LayoutlibDelegate getResourceEntryName(Resources resources, int resid)676 static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { 677 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); 678 if (resourceInfo != null) { 679 return resourceInfo.getSecond(); 680 } 681 throwException(resid, null); 682 return null; 683 684 } 685 686 @LayoutlibDelegate getResourceName(Resources resources, int resid)687 static String getResourceName(Resources resources, int resid) throws NotFoundException { 688 boolean[] platformOut = new boolean[1]; 689 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); 690 String packageName; 691 if (resourceInfo != null) { 692 if (platformOut[0]) { 693 packageName = SdkConstants.ANDROID_NS_NAME; 694 } else { 695 packageName = getContext(resources).getPackageName(); 696 packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName; 697 } 698 return packageName + ':' + resourceInfo.getFirst().getName() + '/' + 699 resourceInfo.getSecond(); 700 } 701 throwException(resid, null); 702 return null; 703 } 704 705 @LayoutlibDelegate getResourcePackageName(Resources resources, int resid)706 static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { 707 boolean[] platformOut = new boolean[1]; 708 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); 709 if (resourceInfo != null) { 710 if (platformOut[0]) { 711 return SdkConstants.ANDROID_NS_NAME; 712 } 713 String packageName = getContext(resources).getPackageName(); 714 return packageName == null ? SdkConstants.APP_PREFIX : packageName; 715 } 716 throwException(resid, null); 717 return null; 718 } 719 720 @LayoutlibDelegate getResourceTypeName(Resources resources, int resid)721 static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { 722 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); 723 if (resourceInfo != null) { 724 return resourceInfo.getFirst().getName(); 725 } 726 throwException(resid, null); 727 return null; 728 } 729 730 @LayoutlibDelegate getString(Resources resources, int id, Object... formatArgs)731 static String getString(Resources resources, int id, Object... formatArgs) 732 throws NotFoundException { 733 String s = getString(resources, id); 734 if (s != null) { 735 return String.format(s, formatArgs); 736 737 } 738 739 // id was not found or not resolved. Throw a NotFoundException. 740 throwException(resources, id); 741 742 // this is not used since the method above always throws 743 return null; 744 } 745 746 @LayoutlibDelegate getString(Resources resources, int id)747 static String getString(Resources resources, int id) throws NotFoundException { 748 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 749 750 if (value != null && value.getSecond().getValue() != null) { 751 return value.getSecond().getValue(); 752 } 753 754 // id was not found or not resolved. Throw a NotFoundException. 755 throwException(resources, id); 756 757 // this is not used since the method above always throws 758 return null; 759 } 760 761 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity)762 static String getQuantityString(Resources resources, int id, int quantity) throws 763 NotFoundException { 764 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 765 766 if (value != null) { 767 if (value.getSecond() instanceof PluralsResourceValue) { 768 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); 769 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() 770 .get(0)); 771 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); 772 if (strValue == null) { 773 strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); 774 } 775 776 return strValue; 777 } 778 else { 779 return value.getSecond().getValue(); 780 } 781 } 782 783 // id was not found or not resolved. Throw a NotFoundException. 784 throwException(resources, id); 785 786 // this is not used since the method above always throws 787 return null; 788 } 789 790 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)791 static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) 792 throws NotFoundException { 793 String raw = getQuantityString(resources, id, quantity); 794 return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); 795 } 796 797 @LayoutlibDelegate getQuantityText(Resources resources, int id, int quantity)798 static CharSequence getQuantityText(Resources resources, int id, int quantity) throws 799 NotFoundException { 800 return getQuantityString(resources, id, quantity); 801 } 802 803 @LayoutlibDelegate getFont(Resources resources, int id)804 static Typeface getFont(Resources resources, int id) throws 805 NotFoundException { 806 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 807 if (value != null) { 808 return ResourceHelper.getFont(value.getSecond(), getContext(resources), null); 809 } 810 811 throwException(resources, id); 812 813 // this is not used since the method above always throws 814 return null; 815 } 816 817 @LayoutlibDelegate getFont(Resources resources, TypedValue outValue, int id)818 static Typeface getFont(Resources resources, TypedValue outValue, int id) throws 819 NotFoundException { 820 Resources_Delegate.getValue(resources, id, outValue, true); 821 if (outValue.string != null) { 822 return ResourceHelper.getFont(outValue.string.toString(), getContext(resources), null, 823 mPlatformResourceFlag[0]); 824 } 825 826 throwException(resources, id); 827 828 // this is not used since the method above always throws 829 return null; 830 } 831 832 @LayoutlibDelegate getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)833 static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) 834 throws NotFoundException { 835 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 836 837 if (value != null) { 838 ResourceValue resVal = value.getSecond(); 839 String v = resVal != null ? resVal.getValue() : null; 840 841 if (v != null) { 842 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 843 false /*requireUnit*/)) { 844 return; 845 } 846 if (resVal instanceof DensityBasedResourceValue) { 847 outValue.density = 848 ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); 849 } 850 851 // else it's a string 852 outValue.type = TypedValue.TYPE_STRING; 853 outValue.string = v; 854 return; 855 } 856 } 857 858 // id was not found or not resolved. Throw a NotFoundException. 859 throwException(resources, id); 860 } 861 862 @LayoutlibDelegate getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)863 static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs) 864 throws NotFoundException { 865 throw new UnsupportedOperationException(); 866 } 867 868 @LayoutlibDelegate getXml(Resources resources, int id)869 static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException { 870 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 871 872 if (value != null) { 873 String v = value.getSecond().getValue(); 874 875 if (v != null) { 876 // check this is a file 877 File f = new File(v); 878 if (f.isFile()) { 879 try { 880 XmlPullParser parser = ParserFactory.create(f); 881 882 return new BridgeXmlBlockParser(parser, getContext(resources), 883 mPlatformResourceFlag[0]); 884 } catch (XmlPullParserException e) { 885 NotFoundException newE = new NotFoundException(); 886 newE.initCause(e); 887 throw newE; 888 } catch (FileNotFoundException e) { 889 NotFoundException newE = new NotFoundException(); 890 newE.initCause(e); 891 throw newE; 892 } 893 } 894 } 895 } 896 897 // id was not found or not resolved. Throw a NotFoundException. 898 throwException(resources, id); 899 900 // this is not used since the method above always throws 901 return null; 902 } 903 904 @LayoutlibDelegate loadXmlResourceParser(Resources resources, int id, String type)905 static XmlResourceParser loadXmlResourceParser(Resources resources, int id, 906 String type) throws NotFoundException { 907 return resources.loadXmlResourceParser_Original(id, type); 908 } 909 910 @LayoutlibDelegate loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)911 static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id, 912 int assetCookie, String type) throws NotFoundException { 913 // even though we know the XML file to load directly, we still need to resolve the 914 // id so that we can know if it's a platform or project resource. 915 // (mPlatformResouceFlag will get the result and will be used later). 916 getResourceValue(resources, id, mPlatformResourceFlag); 917 918 File f = new File(file); 919 try { 920 XmlPullParser parser = ParserFactory.create(f); 921 922 return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]); 923 } catch (XmlPullParserException e) { 924 NotFoundException newE = new NotFoundException(); 925 newE.initCause(e); 926 throw newE; 927 } catch (FileNotFoundException e) { 928 NotFoundException newE = new NotFoundException(); 929 newE.initCause(e); 930 throw newE; 931 } 932 } 933 934 @LayoutlibDelegate openRawResource(Resources resources, int id)935 static InputStream openRawResource(Resources resources, int id) throws NotFoundException { 936 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 937 938 if (value != null) { 939 String path = value.getSecond().getValue(); 940 941 if (path != null) { 942 // check this is a file 943 File f = new File(path); 944 if (f.isFile()) { 945 try { 946 // if it's a nine-patch return a custom input stream so that 947 // other methods (mainly bitmap factory) can detect it's a 9-patch 948 // and actually load it as a 9-patch instead of a normal bitmap 949 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 950 return new NinePatchInputStream(f); 951 } 952 return new FileInputStream(f); 953 } catch (FileNotFoundException e) { 954 NotFoundException newE = new NotFoundException(); 955 newE.initCause(e); 956 throw newE; 957 } 958 } 959 } 960 } 961 962 // id was not found or not resolved. Throw a NotFoundException. 963 throwException(resources, id); 964 965 // this is not used since the method above always throws 966 return null; 967 } 968 969 @LayoutlibDelegate openRawResource(Resources resources, int id, TypedValue value)970 static InputStream openRawResource(Resources resources, int id, TypedValue value) throws 971 NotFoundException { 972 getValue(resources, id, value, true); 973 974 String path = value.string.toString(); 975 976 File f = new File(path); 977 if (f.isFile()) { 978 try { 979 // if it's a nine-patch return a custom input stream so that 980 // other methods (mainly bitmap factory) can detect it's a 9-patch 981 // and actually load it as a 9-patch instead of a normal bitmap 982 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 983 return new NinePatchInputStream(f); 984 } 985 return new FileInputStream(f); 986 } catch (FileNotFoundException e) { 987 NotFoundException exception = new NotFoundException(); 988 exception.initCause(e); 989 throw exception; 990 } 991 } 992 993 throw new NotFoundException(); 994 } 995 996 @LayoutlibDelegate openRawResourceFd(Resources resources, int id)997 static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws 998 NotFoundException { 999 throw new UnsupportedOperationException(); 1000 } 1001 1002 @VisibleForTesting 1003 @Nullable resourceUrlFromName(@onNull String name, @Nullable String defType, @Nullable String defPackage)1004 static ResourceUrl resourceUrlFromName(@NonNull String name, @Nullable String defType, 1005 @Nullable 1006 String defPackage) { 1007 int colonIdx = name.indexOf(':'); 1008 int slashIdx = name.indexOf('/'); 1009 1010 if (colonIdx != -1 && slashIdx != -1) { 1011 // Easy case 1012 return ResourceUrl.parse(PREFIX_RESOURCE_REF + name); 1013 } 1014 1015 if (colonIdx == -1 && slashIdx == -1) { 1016 if (defType == null) { 1017 throw new IllegalArgumentException("name does not define a type an no defType was" + 1018 " passed"); 1019 } 1020 1021 // It does not define package or type 1022 return ResourceUrl.parse( 1023 PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType + 1024 "/" + name); 1025 } 1026 1027 if (colonIdx != -1) { 1028 if (defType == null) { 1029 throw new IllegalArgumentException("name does not define a type an no defType was" + 1030 " passed"); 1031 } 1032 // We have package but no type 1033 String pkg = name.substring(0, colonIdx); 1034 ResourceType type = ResourceType.getEnum(defType); 1035 return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) : 1036 null; 1037 } 1038 1039 ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx)); 1040 if (type == null) { 1041 return null; 1042 } 1043 // We have type but no package 1044 return ResourceUrl.create(defPackage, 1045 type, 1046 name.substring(slashIdx + 1)); 1047 } 1048 1049 @LayoutlibDelegate getIdentifier(Resources resources, String name, String defType, String defPackage)1050 static int getIdentifier(Resources resources, String name, String defType, String defPackage) { 1051 if (name == null) { 1052 return 0; 1053 } 1054 1055 ResourceUrl url = resourceUrlFromName(name, defType, defPackage); 1056 Integer id = null; 1057 if (url != null) { 1058 id = ANDROID_PKG.equals(url.namespace) ? Bridge.getResourceId(url.type, url.name) : 1059 getLayoutlibCallback(resources).getResourceId(url.type, url.name); 1060 } 1061 1062 return id != null ? id : 0; 1063 } 1064 1065 /** 1066 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource 1067 * type. 1068 * 1069 * @param id the id of the resource 1070 * 1071 * @throws NotFoundException 1072 */ throwException(Resources resources, int id)1073 private static void throwException(Resources resources, int id) throws NotFoundException { 1074 throwException(id, getResourceInfo(resources, id, new boolean[1])); 1075 } 1076 throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo)1077 private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) { 1078 String message; 1079 if (resourceInfo != null) { 1080 message = String.format( 1081 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 1082 resourceInfo.getFirst(), id, resourceInfo.getSecond()); 1083 } else { 1084 message = String.format("Could not resolve resource value: 0x%1$X.", id); 1085 } 1086 1087 throw new NotFoundException(message); 1088 } 1089 getInt(String v)1090 private static int getInt(String v) throws NumberFormatException { 1091 int radix = 10; 1092 if (v.startsWith("0x")) { 1093 v = v.substring(2); 1094 radix = 16; 1095 } else if (v.startsWith("0")) { 1096 radix = 8; 1097 } 1098 return Integer.parseInt(v, radix); 1099 } 1100 } 1101