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.ide.common.rendering.api.IProjectCallback; 20 import com.android.ide.common.rendering.api.LayoutLog; 21 import com.android.ide.common.rendering.api.ResourceValue; 22 import com.android.layoutlib.bridge.Bridge; 23 import com.android.layoutlib.bridge.BridgeConstants; 24 import com.android.layoutlib.bridge.android.BridgeContext; 25 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 26 import com.android.layoutlib.bridge.impl.ParserFactory; 27 import com.android.layoutlib.bridge.impl.ResourceHelper; 28 import com.android.ninepatch.NinePatch; 29 import com.android.resources.ResourceType; 30 import com.android.util.Pair; 31 32 import org.xmlpull.v1.XmlPullParser; 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import android.graphics.drawable.Drawable; 36 import android.util.AttributeSet; 37 import android.util.DisplayMetrics; 38 import android.util.TypedValue; 39 import android.view.ViewGroup.LayoutParams; 40 41 import java.io.File; 42 import java.io.FileInputStream; 43 import java.io.FileNotFoundException; 44 import java.io.InputStream; 45 46 /** 47 * 48 */ 49 public final class BridgeResources extends Resources { 50 51 private BridgeContext mContext; 52 private IProjectCallback mProjectCallback; 53 private boolean[] mPlatformResourceFlag = new boolean[1]; 54 55 /** 56 * Simpler wrapper around FileInputStream. This is used when the input stream represent 57 * not a normal bitmap but a nine patch. 58 * This is useful when the InputStream is created in a method but used in another that needs 59 * to know whether this is 9-patch or not, such as BitmapFactory. 60 */ 61 public class NinePatchInputStream extends FileInputStream { 62 private boolean mFakeMarkSupport = true; 63 public NinePatchInputStream(File file) throws FileNotFoundException { 64 super(file); 65 } 66 67 @Override 68 public boolean markSupported() { 69 if (mFakeMarkSupport) { 70 // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream. 71 return true; 72 } 73 74 return super.markSupported(); 75 } 76 77 public void disableFakeMarkSupport() { 78 // disable fake mark support so that in case codec actually try to use them 79 // we don't lie to them. 80 mFakeMarkSupport = false; 81 } 82 } 83 84 /** 85 * This initializes the static field {@link Resources#mSystem} which is used 86 * by methods who get global resources using {@link Resources#getSystem()}. 87 * <p/> 88 * They will end up using our bridge resources. 89 * <p/> 90 * {@link Bridge} calls this method after setting up a new bridge. 91 */ 92 public static Resources initSystem(BridgeContext context, 93 AssetManager assets, 94 DisplayMetrics metrics, 95 Configuration config, 96 IProjectCallback projectCallback) { 97 return Resources.mSystem = new BridgeResources(context, 98 assets, 99 metrics, 100 config, 101 projectCallback); 102 } 103 104 /** 105 * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects 106 * around that would prevent us from unloading the library. 107 */ 108 public static void disposeSystem() { 109 if (Resources.mSystem instanceof BridgeResources) { 110 ((BridgeResources)(Resources.mSystem)).mContext = null; 111 ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; 112 } 113 Resources.mSystem = null; 114 } 115 116 private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics, 117 Configuration config, IProjectCallback projectCallback) { 118 super(assets, metrics, config); 119 mContext = context; 120 mProjectCallback = projectCallback; 121 } 122 123 public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile, 124 boolean platformStyleable, String styleableName) { 125 return new BridgeTypedArray(this, mContext, numEntries, platformFile, 126 platformStyleable, styleableName); 127 } 128 129 private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) { 130 // first get the String related to this id in the framework 131 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); 132 133 if (resourceInfo != null) { 134 platformResFlag_out[0] = true; 135 String attributeName = resourceInfo.getSecond(); 136 137 return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource( 138 resourceInfo.getFirst(), attributeName)); 139 } 140 141 // didn't find a match in the framework? look in the project. 142 if (mProjectCallback != null) { 143 resourceInfo = mProjectCallback.resolveResourceId(id); 144 145 if (resourceInfo != null) { 146 platformResFlag_out[0] = false; 147 String attributeName = resourceInfo.getSecond(); 148 149 return Pair.of(attributeName, mContext.getRenderResources().getProjectResource( 150 resourceInfo.getFirst(), attributeName)); 151 } 152 } 153 154 return null; 155 } 156 157 @Override 158 public Drawable getDrawable(int id) throws NotFoundException { 159 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 160 161 if (value != null) { 162 return ResourceHelper.getDrawable(value.getSecond(), mContext); 163 } 164 165 // id was not found or not resolved. Throw a NotFoundException. 166 throwException(id); 167 168 // this is not used since the method above always throws 169 return null; 170 } 171 172 @Override 173 public int getColor(int id) throws NotFoundException { 174 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 175 176 if (value != null) { 177 try { 178 return ResourceHelper.getColor(value.getSecond().getValue()); 179 } catch (NumberFormatException e) { 180 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, 181 null /*data*/); 182 return 0; 183 } 184 } 185 186 // id was not found or not resolved. Throw a NotFoundException. 187 throwException(id); 188 189 // this is not used since the method above always throws 190 return 0; 191 } 192 193 @Override 194 public ColorStateList getColorStateList(int id) throws NotFoundException { 195 Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag); 196 197 if (resValue != null) { 198 ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), 199 mContext); 200 if (stateList != null) { 201 return stateList; 202 } 203 } 204 205 // id was not found or not resolved. Throw a NotFoundException. 206 throwException(id); 207 208 // this is not used since the method above always throws 209 return null; 210 } 211 212 @Override 213 public CharSequence getText(int id) throws NotFoundException { 214 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 215 216 if (value != null) { 217 ResourceValue resValue = value.getSecond(); 218 219 assert resValue != null; 220 if (resValue != null) { 221 String v = resValue.getValue(); 222 if (v != null) { 223 return v; 224 } 225 } 226 } 227 228 // id was not found or not resolved. Throw a NotFoundException. 229 throwException(id); 230 231 // this is not used since the method above always throws 232 return null; 233 } 234 235 @Override 236 public XmlResourceParser getLayout(int id) throws NotFoundException { 237 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); 238 239 if (v != null) { 240 ResourceValue value = v.getSecond(); 241 XmlPullParser parser = null; 242 243 try { 244 // check if the current parser can provide us with a custom parser. 245 if (mPlatformResourceFlag[0] == false) { 246 parser = mProjectCallback.getParser(value); 247 } 248 249 // create a new one manually if needed. 250 if (parser == null) { 251 File xml = new File(value.getValue()); 252 if (xml.isFile()) { 253 // we need to create a pull parser around the layout XML file, and then 254 // give that to our XmlBlockParser 255 parser = ParserFactory.create(xml); 256 } 257 } 258 259 if (parser != null) { 260 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 261 } 262 } catch (XmlPullParserException e) { 263 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 264 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 265 // we'll return null below. 266 } catch (FileNotFoundException e) { 267 // this shouldn't happen since we check above. 268 } 269 270 } 271 272 // id was not found or not resolved. Throw a NotFoundException. 273 throwException(id); 274 275 // this is not used since the method above always throws 276 return null; 277 } 278 279 @Override 280 public XmlResourceParser getAnimation(int id) throws NotFoundException { 281 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); 282 283 if (v != null) { 284 ResourceValue value = v.getSecond(); 285 XmlPullParser parser = null; 286 287 try { 288 File xml = new File(value.getValue()); 289 if (xml.isFile()) { 290 // we need to create a pull parser around the layout XML file, and then 291 // give that to our XmlBlockParser 292 parser = ParserFactory.create(xml); 293 294 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 295 } 296 } catch (XmlPullParserException e) { 297 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 298 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 299 // we'll return null below. 300 } catch (FileNotFoundException e) { 301 // this shouldn't happen since we check above. 302 } 303 304 } 305 306 // id was not found or not resolved. Throw a NotFoundException. 307 throwException(id); 308 309 // this is not used since the method above always throws 310 return null; 311 } 312 313 @Override 314 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { 315 return mContext.obtainStyledAttributes(set, attrs); 316 } 317 318 @Override 319 public TypedArray obtainTypedArray(int id) throws NotFoundException { 320 throw new UnsupportedOperationException(); 321 } 322 323 324 @Override 325 public float getDimension(int id) throws NotFoundException { 326 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 327 328 if (value != null) { 329 ResourceValue resValue = value.getSecond(); 330 331 assert resValue != null; 332 if (resValue != null) { 333 String v = resValue.getValue(); 334 if (v != null) { 335 if (v.equals(BridgeConstants.MATCH_PARENT) || 336 v.equals(BridgeConstants.FILL_PARENT)) { 337 return LayoutParams.MATCH_PARENT; 338 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { 339 return LayoutParams.WRAP_CONTENT; 340 } 341 342 if (ResourceHelper.parseFloatAttribute( 343 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 344 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 345 return mTmpValue.getDimension(getDisplayMetrics()); 346 } 347 } 348 } 349 } 350 351 // id was not found or not resolved. Throw a NotFoundException. 352 throwException(id); 353 354 // this is not used since the method above always throws 355 return 0; 356 } 357 358 @Override 359 public int getDimensionPixelOffset(int id) throws NotFoundException { 360 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 361 362 if (value != null) { 363 ResourceValue resValue = value.getSecond(); 364 365 assert resValue != null; 366 if (resValue != null) { 367 String v = resValue.getValue(); 368 if (v != null) { 369 if (ResourceHelper.parseFloatAttribute( 370 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 371 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 372 return TypedValue.complexToDimensionPixelOffset(mTmpValue.data, 373 getDisplayMetrics()); 374 } 375 } 376 } 377 } 378 379 // id was not found or not resolved. Throw a NotFoundException. 380 throwException(id); 381 382 // this is not used since the method above always throws 383 return 0; 384 } 385 386 @Override 387 public int getDimensionPixelSize(int id) throws NotFoundException { 388 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 389 390 if (value != null) { 391 ResourceValue resValue = value.getSecond(); 392 393 assert resValue != null; 394 if (resValue != null) { 395 String v = resValue.getValue(); 396 if (v != null) { 397 if (ResourceHelper.parseFloatAttribute( 398 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 399 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 400 return TypedValue.complexToDimensionPixelSize(mTmpValue.data, 401 getDisplayMetrics()); 402 } 403 } 404 } 405 } 406 407 // id was not found or not resolved. Throw a NotFoundException. 408 throwException(id); 409 410 // this is not used since the method above always throws 411 return 0; 412 } 413 414 @Override 415 public int getInteger(int id) throws NotFoundException { 416 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 417 418 if (value != null) { 419 ResourceValue resValue = value.getSecond(); 420 421 assert resValue != null; 422 if (resValue != null) { 423 String v = resValue.getValue(); 424 if (v != null) { 425 int radix = 10; 426 if (v.startsWith("0x")) { 427 v = v.substring(2); 428 radix = 16; 429 } 430 try { 431 return Integer.parseInt(v, radix); 432 } catch (NumberFormatException e) { 433 // return exception below 434 } 435 } 436 } 437 } 438 439 // id was not found or not resolved. Throw a NotFoundException. 440 throwException(id); 441 442 // this is not used since the method above always throws 443 return 0; 444 } 445 446 @Override 447 public boolean getBoolean(int id) throws NotFoundException { 448 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 449 450 if (value != null) { 451 ResourceValue resValue = value.getSecond(); 452 453 assert resValue != null; 454 if (resValue != null) { 455 String v = resValue.getValue(); 456 if (v != null) { 457 return Boolean.parseBoolean(v); 458 } 459 } 460 } 461 462 // id was not found or not resolved. Throw a NotFoundException. 463 throwException(id); 464 465 // this is not used since the method above always throws 466 return false; 467 } 468 469 @Override 470 public String getResourceEntryName(int resid) throws NotFoundException { 471 throw new UnsupportedOperationException(); 472 } 473 474 @Override 475 public String getResourceName(int resid) throws NotFoundException { 476 throw new UnsupportedOperationException(); 477 } 478 479 @Override 480 public String getResourceTypeName(int resid) throws NotFoundException { 481 throw new UnsupportedOperationException(); 482 } 483 484 @Override 485 public String getString(int id, Object... formatArgs) throws NotFoundException { 486 String s = getString(id); 487 if (s != null) { 488 return String.format(s, formatArgs); 489 490 } 491 492 // id was not found or not resolved. Throw a NotFoundException. 493 throwException(id); 494 495 // this is not used since the method above always throws 496 return null; 497 } 498 499 @Override 500 public String getString(int id) throws NotFoundException { 501 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 502 503 if (value != null && value.getSecond().getValue() != null) { 504 return value.getSecond().getValue(); 505 } 506 507 // id was not found or not resolved. Throw a NotFoundException. 508 throwException(id); 509 510 // this is not used since the method above always throws 511 return null; 512 } 513 514 @Override 515 public void getValue(int id, TypedValue outValue, boolean resolveRefs) 516 throws NotFoundException { 517 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 518 519 if (value != null) { 520 String v = value.getSecond().getValue(); 521 522 if (v != null) { 523 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 524 false /*requireUnit*/)) { 525 return; 526 } 527 528 // else it's a string 529 outValue.type = TypedValue.TYPE_STRING; 530 outValue.string = v; 531 return; 532 } 533 } 534 535 // id was not found or not resolved. Throw a NotFoundException. 536 throwException(id); 537 } 538 539 @Override 540 public void getValue(String name, TypedValue outValue, boolean resolveRefs) 541 throws NotFoundException { 542 throw new UnsupportedOperationException(); 543 } 544 545 @Override 546 public XmlResourceParser getXml(int id) throws NotFoundException { 547 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 548 549 if (value != null) { 550 String v = value.getSecond().getValue(); 551 552 if (v != null) { 553 // check this is a file 554 File f = new File(v); 555 if (f.isFile()) { 556 try { 557 XmlPullParser parser = ParserFactory.create(f); 558 559 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 560 } catch (XmlPullParserException e) { 561 NotFoundException newE = new NotFoundException(); 562 newE.initCause(e); 563 throw newE; 564 } catch (FileNotFoundException e) { 565 NotFoundException newE = new NotFoundException(); 566 newE.initCause(e); 567 throw newE; 568 } 569 } 570 } 571 } 572 573 // id was not found or not resolved. Throw a NotFoundException. 574 throwException(id); 575 576 // this is not used since the method above always throws 577 return null; 578 } 579 580 @Override 581 public XmlResourceParser loadXmlResourceParser(String file, int id, 582 int assetCookie, String type) throws NotFoundException { 583 // even though we know the XML file to load directly, we still need to resolve the 584 // id so that we can know if it's a platform or project resource. 585 // (mPlatformResouceFlag will get the result and will be used later). 586 getResourceValue(id, mPlatformResourceFlag); 587 588 File f = new File(file); 589 try { 590 XmlPullParser parser = ParserFactory.create(f); 591 592 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 593 } catch (XmlPullParserException e) { 594 NotFoundException newE = new NotFoundException(); 595 newE.initCause(e); 596 throw newE; 597 } catch (FileNotFoundException e) { 598 NotFoundException newE = new NotFoundException(); 599 newE.initCause(e); 600 throw newE; 601 } 602 } 603 604 605 @Override 606 public InputStream openRawResource(int id) throws NotFoundException { 607 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 608 609 if (value != null) { 610 String path = value.getSecond().getValue(); 611 612 if (path != null) { 613 // check this is a file 614 File f = new File(path); 615 if (f.isFile()) { 616 try { 617 // if it's a nine-patch return a custom input stream so that 618 // other methods (mainly bitmap factory) can detect it's a 9-patch 619 // and actually load it as a 9-patch instead of a normal bitmap 620 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 621 return new NinePatchInputStream(f); 622 } 623 return new FileInputStream(f); 624 } catch (FileNotFoundException e) { 625 NotFoundException newE = new NotFoundException(); 626 newE.initCause(e); 627 throw newE; 628 } 629 } 630 } 631 } 632 633 // id was not found or not resolved. Throw a NotFoundException. 634 throwException(id); 635 636 // this is not used since the method above always throws 637 return null; 638 } 639 640 @Override 641 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 642 getValue(id, value, true); 643 644 String path = value.string.toString(); 645 646 File f = new File(path); 647 if (f.isFile()) { 648 try { 649 // if it's a nine-patch return a custom input stream so that 650 // other methods (mainly bitmap factory) can detect it's a 9-patch 651 // and actually load it as a 9-patch instead of a normal bitmap 652 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 653 return new NinePatchInputStream(f); 654 } 655 return new FileInputStream(f); 656 } catch (FileNotFoundException e) { 657 NotFoundException exception = new NotFoundException(); 658 exception.initCause(e); 659 throw exception; 660 } 661 } 662 663 throw new NotFoundException(); 664 } 665 666 @Override 667 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 668 throw new UnsupportedOperationException(); 669 } 670 671 /** 672 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type. 673 * @param id the id of the resource 674 * @throws NotFoundException 675 */ 676 private void throwException(int id) throws NotFoundException { 677 // first get the String related to this id in the framework 678 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); 679 680 // if the name is unknown in the framework, get it from the custom view loader. 681 if (resourceInfo == null && mProjectCallback != null) { 682 resourceInfo = mProjectCallback.resolveResourceId(id); 683 } 684 685 String message = null; 686 if (resourceInfo != null) { 687 message = String.format( 688 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 689 resourceInfo.getFirst(), id, resourceInfo.getSecond()); 690 } else { 691 message = String.format( 692 "Could not resolve resource value: 0x%1$X.", id); 693 } 694 695 throw new NotFoundException(message); 696 } 697 } 698