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