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