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