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