BridgeResources.java revision 162c1dcd5e635d17d8425936d7729d0ae5ed1a62
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 17package android.content.res; 18 19import com.android.ide.common.rendering.api.IProjectCallback; 20import com.android.ide.common.rendering.api.LayoutLog; 21import com.android.ide.common.rendering.api.ResourceValue; 22import com.android.layoutlib.bridge.Bridge; 23import com.android.layoutlib.bridge.BridgeConstants; 24import com.android.layoutlib.bridge.android.BridgeContext; 25import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 26import com.android.layoutlib.bridge.impl.ParserFactory; 27import com.android.layoutlib.bridge.impl.ResourceHelper; 28import com.android.ninepatch.NinePatch; 29import com.android.resources.ResourceType; 30import com.android.util.Pair; 31 32import org.xmlpull.v1.XmlPullParser; 33import org.xmlpull.v1.XmlPullParserException; 34 35import android.graphics.drawable.Drawable; 36import android.util.AttributeSet; 37import android.util.DisplayMetrics; 38import android.util.TypedValue; 39import android.view.ViewGroup.LayoutParams; 40 41import java.io.File; 42import java.io.FileInputStream; 43import java.io.FileNotFoundException; 44import java.io.InputStream; 45 46/** 47 * 48 */ 49public 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 return value.getSecond().getValue(); 218 } 219 220 // id was not found or not resolved. Throw a NotFoundException. 221 throwException(id); 222 223 // this is not used since the method above always throws 224 return null; 225 } 226 227 @Override 228 public XmlResourceParser getLayout(int id) throws NotFoundException { 229 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); 230 231 if (v != null) { 232 ResourceValue value = v.getSecond(); 233 XmlPullParser parser = null; 234 235 try { 236 // check if the current parser can provide us with a custom parser. 237 if (mPlatformResourceFlag[0] == false) { 238 parser = mProjectCallback.getParser(value); 239 } 240 241 // create a new one manually if needed. 242 if (parser == null) { 243 File xml = new File(value.getValue()); 244 if (xml.isFile()) { 245 // we need to create a pull parser around the layout XML file, and then 246 // give that to our XmlBlockParser 247 parser = ParserFactory.create(xml); 248 } 249 } 250 251 if (parser != null) { 252 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 253 } 254 } catch (XmlPullParserException e) { 255 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 256 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 257 // we'll return null below. 258 } catch (FileNotFoundException e) { 259 // this shouldn't happen since we check above. 260 } 261 262 } 263 264 // id was not found or not resolved. Throw a NotFoundException. 265 throwException(id); 266 267 // this is not used since the method above always throws 268 return null; 269 } 270 271 @Override 272 public XmlResourceParser getAnimation(int id) throws NotFoundException { 273 Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); 274 275 if (v != null) { 276 ResourceValue value = v.getSecond(); 277 XmlPullParser parser = null; 278 279 try { 280 File xml = new File(value.getValue()); 281 if (xml.isFile()) { 282 // we need to create a pull parser around the layout XML file, and then 283 // give that to our XmlBlockParser 284 parser = ParserFactory.create(xml); 285 286 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 287 } 288 } catch (XmlPullParserException e) { 289 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 290 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 291 // we'll return null below. 292 } catch (FileNotFoundException e) { 293 // this shouldn't happen since we check above. 294 } 295 296 } 297 298 // id was not found or not resolved. Throw a NotFoundException. 299 throwException(id); 300 301 // this is not used since the method above always throws 302 return null; 303 } 304 305 @Override 306 public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { 307 return mContext.obtainStyledAttributes(set, attrs); 308 } 309 310 @Override 311 public TypedArray obtainTypedArray(int id) throws NotFoundException { 312 throw new UnsupportedOperationException(); 313 } 314 315 316 @Override 317 public float getDimension(int id) throws NotFoundException { 318 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 319 320 if (value != null) { 321 String v = value.getSecond().getValue(); 322 323 if (v != null) { 324 if (v.equals(BridgeConstants.MATCH_PARENT) || 325 v.equals(BridgeConstants.FILL_PARENT)) { 326 return LayoutParams.MATCH_PARENT; 327 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { 328 return LayoutParams.WRAP_CONTENT; 329 } 330 331 if (ResourceHelper.parseFloatAttribute( 332 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 333 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 334 return mTmpValue.getDimension(getDisplayMetrics()); 335 } 336 } 337 } 338 339 // id was not found or not resolved. Throw a NotFoundException. 340 throwException(id); 341 342 // this is not used since the method above always throws 343 return 0; 344 } 345 346 @Override 347 public int getDimensionPixelOffset(int id) throws NotFoundException { 348 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 349 350 if (value != null) { 351 String v = value.getSecond().getValue(); 352 353 if (v != null) { 354 if (ResourceHelper.parseFloatAttribute( 355 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 356 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 357 return TypedValue.complexToDimensionPixelOffset(mTmpValue.data, 358 getDisplayMetrics()); 359 } 360 } 361 } 362 363 // id was not found or not resolved. Throw a NotFoundException. 364 throwException(id); 365 366 // this is not used since the method above always throws 367 return 0; 368 } 369 370 @Override 371 public int getDimensionPixelSize(int id) throws NotFoundException { 372 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 373 374 if (value != null) { 375 String v = value.getSecond().getValue(); 376 377 if (v != null) { 378 if (ResourceHelper.parseFloatAttribute( 379 value.getFirst(), v, mTmpValue, true /*requireUnit*/) && 380 mTmpValue.type == TypedValue.TYPE_DIMENSION) { 381 return TypedValue.complexToDimensionPixelSize(mTmpValue.data, 382 getDisplayMetrics()); 383 } 384 } 385 } 386 387 // id was not found or not resolved. Throw a NotFoundException. 388 throwException(id); 389 390 // this is not used since the method above always throws 391 return 0; 392 } 393 394 @Override 395 public int getInteger(int id) throws NotFoundException { 396 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 397 398 if (value != null && value.getSecond().getValue() != null) { 399 String v = value.getSecond().getValue(); 400 int radix = 10; 401 if (v.startsWith("0x")) { 402 v = v.substring(2); 403 radix = 16; 404 } 405 try { 406 return Integer.parseInt(v, radix); 407 } catch (NumberFormatException e) { 408 // return exception below 409 } 410 } 411 412 // id was not found or not resolved. Throw a NotFoundException. 413 throwException(id); 414 415 // this is not used since the method above always throws 416 return 0; 417 } 418 419 @Override 420 public boolean getBoolean(int id) throws NotFoundException { 421 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 422 423 if (value != null && value.getSecond().getValue() != null) { 424 String v = value.getSecond().getValue(); 425 return Boolean.parseBoolean(v); 426 } 427 428 // id was not found or not resolved. Throw a NotFoundException. 429 throwException(id); 430 431 // this is not used since the method above always throws 432 return false; 433 } 434 435 @Override 436 public String getResourceEntryName(int resid) throws NotFoundException { 437 throw new UnsupportedOperationException(); 438 } 439 440 @Override 441 public String getResourceName(int resid) throws NotFoundException { 442 throw new UnsupportedOperationException(); 443 } 444 445 @Override 446 public String getResourceTypeName(int resid) throws NotFoundException { 447 throw new UnsupportedOperationException(); 448 } 449 450 @Override 451 public String getString(int id, Object... formatArgs) throws NotFoundException { 452 String s = getString(id); 453 if (s != null) { 454 return String.format(s, formatArgs); 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 466 public String getString(int id) throws NotFoundException { 467 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 468 469 if (value != null && value.getSecond().getValue() != null) { 470 return value.getSecond().getValue(); 471 } 472 473 // id was not found or not resolved. Throw a NotFoundException. 474 throwException(id); 475 476 // this is not used since the method above always throws 477 return null; 478 } 479 480 @Override 481 public void getValue(int id, TypedValue outValue, boolean resolveRefs) 482 throws NotFoundException { 483 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 484 485 if (value != null) { 486 String v = value.getSecond().getValue(); 487 488 if (v != null) { 489 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 490 false /*requireUnit*/)) { 491 return; 492 } 493 494 // else it's a string 495 outValue.type = TypedValue.TYPE_STRING; 496 outValue.string = v; 497 return; 498 } 499 } 500 501 // id was not found or not resolved. Throw a NotFoundException. 502 throwException(id); 503 } 504 505 @Override 506 public void getValue(String name, TypedValue outValue, boolean resolveRefs) 507 throws NotFoundException { 508 throw new UnsupportedOperationException(); 509 } 510 511 @Override 512 public XmlResourceParser getXml(int id) throws NotFoundException { 513 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 514 515 if (value != null) { 516 String v = value.getSecond().getValue(); 517 518 if (v != null) { 519 // check this is a file 520 File f = new File(v); 521 if (f.isFile()) { 522 try { 523 XmlPullParser parser = ParserFactory.create(f); 524 525 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 526 } catch (XmlPullParserException e) { 527 NotFoundException newE = new NotFoundException(); 528 newE.initCause(e); 529 throw newE; 530 } catch (FileNotFoundException e) { 531 NotFoundException newE = new NotFoundException(); 532 newE.initCause(e); 533 throw newE; 534 } 535 } 536 } 537 } 538 539 // id was not found or not resolved. Throw a NotFoundException. 540 throwException(id); 541 542 // this is not used since the method above always throws 543 return null; 544 } 545 546 @Override 547 public XmlResourceParser loadXmlResourceParser(String file, int id, 548 int assetCookie, String type) throws NotFoundException { 549 // even though we know the XML file to load directly, we still need to resolve the 550 // id so that we can know if it's a platform or project resource. 551 // (mPlatformResouceFlag will get the result and will be used later). 552 getResourceValue(id, mPlatformResourceFlag); 553 554 File f = new File(file); 555 try { 556 XmlPullParser parser = ParserFactory.create(f); 557 558 return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); 559 } catch (XmlPullParserException e) { 560 NotFoundException newE = new NotFoundException(); 561 newE.initCause(e); 562 throw newE; 563 } catch (FileNotFoundException e) { 564 NotFoundException newE = new NotFoundException(); 565 newE.initCause(e); 566 throw newE; 567 } 568 } 569 570 571 @Override 572 public InputStream openRawResource(int id) throws NotFoundException { 573 Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); 574 575 if (value != null) { 576 String path = value.getSecond().getValue(); 577 578 if (path != null) { 579 // check this is a file 580 File f = new File(path); 581 if (f.isFile()) { 582 try { 583 // if it's a nine-patch return a custom input stream so that 584 // other methods (mainly bitmap factory) can detect it's a 9-patch 585 // and actually load it as a 9-patch instead of a normal bitmap 586 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 587 return new NinePatchInputStream(f); 588 } 589 return new FileInputStream(f); 590 } catch (FileNotFoundException e) { 591 NotFoundException newE = new NotFoundException(); 592 newE.initCause(e); 593 throw newE; 594 } 595 } 596 } 597 } 598 599 // id was not found or not resolved. Throw a NotFoundException. 600 throwException(id); 601 602 // this is not used since the method above always throws 603 return null; 604 } 605 606 @Override 607 public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { 608 getValue(id, value, true); 609 610 String path = value.string.toString(); 611 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 exception = new NotFoundException(); 624 exception.initCause(e); 625 throw exception; 626 } 627 } 628 629 throw new NotFoundException(); 630 } 631 632 @Override 633 public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { 634 throw new UnsupportedOperationException(); 635 } 636 637 /** 638 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type. 639 * @param id the id of the resource 640 * @throws NotFoundException 641 */ 642 private void throwException(int id) throws NotFoundException { 643 // first get the String related to this id in the framework 644 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); 645 646 // if the name is unknown in the framework, get it from the custom view loader. 647 if (resourceInfo == null && mProjectCallback != null) { 648 resourceInfo = mProjectCallback.resolveResourceId(id); 649 } 650 651 String message = null; 652 if (resourceInfo != null) { 653 message = String.format( 654 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 655 resourceInfo.getFirst(), id, resourceInfo.getSecond()); 656 } else { 657 message = String.format( 658 "Could not resolve resource value: 0x%1$X.", id); 659 } 660 661 throw new NotFoundException(message); 662 } 663} 664