1 /* 2 * Copyright (C) 2007 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 com.android.sdklib.xml; 18 19 import com.android.io.IAbstractFile; 20 import com.android.io.IAbstractFolder; 21 import com.android.io.StreamException; 22 import com.android.resources.Keyboard; 23 import com.android.resources.Navigation; 24 import com.android.resources.TouchScreen; 25 import com.android.sdklib.SdkConstants; 26 import com.android.sdklib.xml.ManifestData.Activity; 27 import com.android.sdklib.xml.ManifestData.Instrumentation; 28 import com.android.sdklib.xml.ManifestData.SupportsScreens; 29 import com.android.sdklib.xml.ManifestData.UsesConfiguration; 30 import com.android.sdklib.xml.ManifestData.UsesFeature; 31 import com.android.sdklib.xml.ManifestData.UsesLibrary; 32 33 import org.xml.sax.Attributes; 34 import org.xml.sax.ErrorHandler; 35 import org.xml.sax.InputSource; 36 import org.xml.sax.Locator; 37 import org.xml.sax.SAXException; 38 import org.xml.sax.SAXParseException; 39 import org.xml.sax.helpers.DefaultHandler; 40 41 import java.io.FileNotFoundException; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.util.Locale; 45 46 import javax.xml.parsers.ParserConfigurationException; 47 import javax.xml.parsers.SAXParser; 48 import javax.xml.parsers.SAXParserFactory; 49 50 public class AndroidManifestParser { 51 52 private final static int LEVEL_TOP = 0; 53 private final static int LEVEL_INSIDE_MANIFEST = 1; 54 private final static int LEVEL_INSIDE_APPLICATION = 2; 55 private final static int LEVEL_INSIDE_APP_COMPONENT = 3; 56 private final static int LEVEL_INSIDE_INTENT_FILTER = 4; 57 58 private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ 59 private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ 60 61 public interface ManifestErrorHandler extends ErrorHandler { 62 /** 63 * Handles a parsing error and an optional line number. 64 */ 65 void handleError(Exception exception, int lineNumber); 66 67 /** 68 * Checks that a class is valid and can be used in the Android Manifest. 69 * <p/> 70 * Errors are put as {@code org.eclipse.core.resources.IMarker} on the manifest file. 71 * 72 * @param locator 73 * @param className the fully qualified name of the class to test. 74 * @param superClassName the fully qualified name of the class it is supposed to extend. 75 * @param testVisibility if <code>true</code>, the method will check the visibility of 76 * the class or of its constructors. 77 */ 78 void checkClass(Locator locator, String className, String superClassName, 79 boolean testVisibility); 80 } 81 82 /** 83 * XML error & data handler used when parsing the AndroidManifest.xml file. 84 * <p/> 85 * During parsing this will fill up the {@link ManifestData} object given to the constructor 86 * and call out errors to the given {@link ManifestErrorHandler}. 87 */ 88 private static class ManifestHandler extends DefaultHandler { 89 90 //--- temporary data/flags used during parsing 91 private final ManifestData mManifestData; 92 private final ManifestErrorHandler mErrorHandler; 93 private int mCurrentLevel = 0; 94 private int mValidLevel = 0; 95 private Activity mCurrentActivity = null; 96 private Locator mLocator; 97 98 /** 99 * Creates a new {@link ManifestHandler}. 100 * 101 * @param manifestFile The manifest file being parsed. Can be null. 102 * @param manifestData Class containing the manifest info obtained during the parsing. 103 * @param errorHandler An optional error handler. 104 */ 105 ManifestHandler(IAbstractFile manifestFile, ManifestData manifestData, 106 ManifestErrorHandler errorHandler) { 107 super(); 108 mManifestData = manifestData; 109 mErrorHandler = errorHandler; 110 } 111 112 /* (non-Javadoc) 113 * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) 114 */ 115 @Override 116 public void setDocumentLocator(Locator locator) { 117 mLocator = locator; 118 super.setDocumentLocator(locator); 119 } 120 121 /* (non-Javadoc) 122 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, 123 * java.lang.String, org.xml.sax.Attributes) 124 */ 125 @Override 126 public void startElement(String uri, String localName, String name, Attributes attributes) 127 throws SAXException { 128 try { 129 if (mManifestData == null) { 130 return; 131 } 132 133 // if we're at a valid level 134 if (mValidLevel == mCurrentLevel) { 135 String value; 136 switch (mValidLevel) { 137 case LEVEL_TOP: 138 if (AndroidManifest.NODE_MANIFEST.equals(localName)) { 139 // lets get the package name. 140 mManifestData.mPackage = getAttributeValue(attributes, 141 AndroidManifest.ATTRIBUTE_PACKAGE, 142 false /* hasNamespace */); 143 144 // and the versionCode 145 String tmp = getAttributeValue(attributes, 146 AndroidManifest.ATTRIBUTE_VERSIONCODE, true); 147 if (tmp != null) { 148 try { 149 mManifestData.mVersionCode = Integer.valueOf(tmp); 150 } catch (NumberFormatException e) { 151 // keep null in the field. 152 } 153 } 154 mValidLevel++; 155 } 156 break; 157 case LEVEL_INSIDE_MANIFEST: 158 if (AndroidManifest.NODE_APPLICATION.equals(localName)) { 159 value = getAttributeValue(attributes, 160 AndroidManifest.ATTRIBUTE_PROCESS, 161 true /* hasNamespace */); 162 if (value != null) { 163 mManifestData.addProcessName(value); 164 } 165 166 value = getAttributeValue(attributes, 167 AndroidManifest.ATTRIBUTE_DEBUGGABLE, 168 true /* hasNamespace*/); 169 if (value != null) { 170 mManifestData.mDebuggable = Boolean.parseBoolean(value); 171 } 172 173 mValidLevel++; 174 } else if (AndroidManifest.NODE_USES_SDK.equals(localName)) { 175 mManifestData.setMinSdkVersionString(getAttributeValue(attributes, 176 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 177 true /* hasNamespace */)); 178 mManifestData.setTargetSdkVersionString(getAttributeValue(attributes, 179 AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION, 180 true /* hasNamespace */)); 181 } else if (AndroidManifest.NODE_INSTRUMENTATION.equals(localName)) { 182 processInstrumentationNode(attributes); 183 184 } else if (AndroidManifest.NODE_SUPPORTS_SCREENS.equals(localName)) { 185 processSupportsScreensNode(attributes); 186 187 } else if (AndroidManifest.NODE_USES_CONFIGURATION.equals(localName)) { 188 processUsesConfiguration(attributes); 189 190 } else if (AndroidManifest.NODE_USES_FEATURE.equals(localName)) { 191 UsesFeature feature = new UsesFeature(); 192 193 // get the name 194 value = getAttributeValue(attributes, 195 AndroidManifest.ATTRIBUTE_NAME, 196 true /* hasNamespace */); 197 if (value != null) { 198 feature.mName = value; 199 } 200 201 // read the required attribute 202 value = getAttributeValue(attributes, 203 AndroidManifest.ATTRIBUTE_REQUIRED, 204 true /*hasNamespace*/); 205 if (value != null) { 206 Boolean b = Boolean.valueOf(value); 207 if (b != null) { 208 feature.mRequired = b; 209 } 210 } 211 212 // read the gl es attribute 213 value = getAttributeValue(attributes, 214 AndroidManifest.ATTRIBUTE_GLESVERSION, 215 true /*hasNamespace*/); 216 if (value != null) { 217 try { 218 int version = Integer.decode(value); 219 feature.mGlEsVersion = version; 220 } catch (NumberFormatException e) { 221 // ignore 222 } 223 224 } 225 226 mManifestData.mFeatures.add(feature); 227 } 228 break; 229 case LEVEL_INSIDE_APPLICATION: 230 if (AndroidManifest.NODE_ACTIVITY.equals(localName)) { 231 processActivityNode(attributes); 232 mValidLevel++; 233 } else if (AndroidManifest.NODE_SERVICE.equals(localName)) { 234 processNode(attributes, SdkConstants.CLASS_SERVICE); 235 mValidLevel++; 236 } else if (AndroidManifest.NODE_RECEIVER.equals(localName)) { 237 processNode(attributes, SdkConstants.CLASS_BROADCASTRECEIVER); 238 mValidLevel++; 239 } else if (AndroidManifest.NODE_PROVIDER.equals(localName)) { 240 processNode(attributes, SdkConstants.CLASS_CONTENTPROVIDER); 241 mValidLevel++; 242 } else if (AndroidManifest.NODE_USES_LIBRARY.equals(localName)) { 243 value = getAttributeValue(attributes, 244 AndroidManifest.ATTRIBUTE_NAME, 245 true /* hasNamespace */); 246 if (value != null) { 247 UsesLibrary library = new UsesLibrary(); 248 library.mName = value; 249 250 // read the required attribute 251 value = getAttributeValue(attributes, 252 AndroidManifest.ATTRIBUTE_REQUIRED, 253 true /*hasNamespace*/); 254 if (value != null) { 255 Boolean b = Boolean.valueOf(value); 256 if (b != null) { 257 library.mRequired = b; 258 } 259 } 260 261 mManifestData.mLibraries.add(library); 262 } 263 } 264 break; 265 case LEVEL_INSIDE_APP_COMPONENT: 266 // only process this level if we are in an activity 267 if (mCurrentActivity != null && 268 AndroidManifest.NODE_INTENT.equals(localName)) { 269 mCurrentActivity.resetIntentFilter(); 270 mValidLevel++; 271 } 272 break; 273 case LEVEL_INSIDE_INTENT_FILTER: 274 if (mCurrentActivity != null) { 275 if (AndroidManifest.NODE_ACTION.equals(localName)) { 276 // get the name attribute 277 String action = getAttributeValue(attributes, 278 AndroidManifest.ATTRIBUTE_NAME, 279 true /* hasNamespace */); 280 if (action != null) { 281 mCurrentActivity.setHasAction(true); 282 mCurrentActivity.setHasMainAction( 283 ACTION_MAIN.equals(action)); 284 } 285 } else if (AndroidManifest.NODE_CATEGORY.equals(localName)) { 286 String category = getAttributeValue(attributes, 287 AndroidManifest.ATTRIBUTE_NAME, 288 true /* hasNamespace */); 289 if (CATEGORY_LAUNCHER.equals(category)) { 290 mCurrentActivity.setHasLauncherCategory(true); 291 } 292 } 293 294 // no need to increase mValidLevel as we don't process anything 295 // below this level. 296 } 297 break; 298 } 299 } 300 301 mCurrentLevel++; 302 } finally { 303 super.startElement(uri, localName, name, attributes); 304 } 305 } 306 307 /* (non-Javadoc) 308 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, 309 * java.lang.String) 310 */ 311 @Override 312 public void endElement(String uri, String localName, String name) throws SAXException { 313 try { 314 if (mManifestData == null) { 315 return; 316 } 317 318 // decrement the levels. 319 if (mValidLevel == mCurrentLevel) { 320 mValidLevel--; 321 } 322 mCurrentLevel--; 323 324 // if we're at a valid level 325 // process the end of the element 326 if (mValidLevel == mCurrentLevel) { 327 switch (mValidLevel) { 328 case LEVEL_INSIDE_APPLICATION: 329 mCurrentActivity = null; 330 break; 331 case LEVEL_INSIDE_APP_COMPONENT: 332 // if we found both a main action and a launcher category, this is our 333 // launcher activity! 334 if (mManifestData.mLauncherActivity == null && 335 mCurrentActivity != null && 336 mCurrentActivity.isHomeActivity() && 337 mCurrentActivity.isExported()) { 338 mManifestData.mLauncherActivity = mCurrentActivity; 339 } 340 break; 341 default: 342 break; 343 } 344 345 } 346 } finally { 347 super.endElement(uri, localName, name); 348 } 349 } 350 351 /* (non-Javadoc) 352 * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) 353 */ 354 @Override 355 public void error(SAXParseException e) { 356 if (mErrorHandler != null) { 357 mErrorHandler.handleError(e, e.getLineNumber()); 358 } 359 } 360 361 /* (non-Javadoc) 362 * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) 363 */ 364 @Override 365 public void fatalError(SAXParseException e) { 366 if (mErrorHandler != null) { 367 mErrorHandler.handleError(e, e.getLineNumber()); 368 } 369 } 370 371 /* (non-Javadoc) 372 * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) 373 */ 374 @Override 375 public void warning(SAXParseException e) throws SAXException { 376 if (mErrorHandler != null) { 377 mErrorHandler.warning(e); 378 } 379 } 380 381 /** 382 * Processes the activity node. 383 * @param attributes the attributes for the activity node. 384 */ 385 private void processActivityNode(Attributes attributes) { 386 // lets get the activity name, and add it to the list 387 String activityName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, 388 true /* hasNamespace */); 389 if (activityName != null) { 390 activityName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage, 391 activityName); 392 393 // get the exported flag. 394 String exportedStr = getAttributeValue(attributes, 395 AndroidManifest.ATTRIBUTE_EXPORTED, true); 396 boolean exported = exportedStr == null || 397 exportedStr.toLowerCase(Locale.US).equals("true"); //$NON-NLS-1$ 398 mCurrentActivity = new Activity(activityName, exported); 399 mManifestData.mActivities.add(mCurrentActivity); 400 401 if (mErrorHandler != null) { 402 mErrorHandler.checkClass(mLocator, activityName, SdkConstants.CLASS_ACTIVITY, 403 true /* testVisibility */); 404 } 405 } else { 406 // no activity found! Aapt will output an error, 407 // so we don't have to do anything 408 mCurrentActivity = null; 409 } 410 411 String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, 412 true /* hasNamespace */); 413 if (processName != null) { 414 mManifestData.addProcessName(processName); 415 } 416 } 417 418 /** 419 * Processes the service/receiver/provider nodes. 420 * @param attributes the attributes for the activity node. 421 * @param superClassName the fully qualified name of the super class that this 422 * node is representing 423 */ 424 private void processNode(Attributes attributes, String superClassName) { 425 // lets get the class name, and check it if required. 426 String serviceName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, 427 true /* hasNamespace */); 428 if (serviceName != null) { 429 serviceName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage, 430 serviceName); 431 432 if (mErrorHandler != null) { 433 mErrorHandler.checkClass(mLocator, serviceName, superClassName, 434 false /* testVisibility */); 435 } 436 } 437 438 String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, 439 true /* hasNamespace */); 440 if (processName != null) { 441 mManifestData.addProcessName(processName); 442 } 443 } 444 445 /** 446 * Processes the instrumentation node. 447 * @param attributes the attributes for the instrumentation node. 448 */ 449 private void processInstrumentationNode(Attributes attributes) { 450 // lets get the class name, and check it if required. 451 String instrumentationName = getAttributeValue(attributes, 452 AndroidManifest.ATTRIBUTE_NAME, 453 true /* hasNamespace */); 454 if (instrumentationName != null) { 455 String instrClassName = AndroidManifest.combinePackageAndClassName( 456 mManifestData.mPackage, instrumentationName); 457 String targetPackage = getAttributeValue(attributes, 458 AndroidManifest.ATTRIBUTE_TARGET_PACKAGE, 459 true /* hasNamespace */); 460 mManifestData.mInstrumentations.add( 461 new Instrumentation(instrClassName, targetPackage)); 462 if (mErrorHandler != null) { 463 mErrorHandler.checkClass(mLocator, instrClassName, 464 SdkConstants.CLASS_INSTRUMENTATION, true /* testVisibility */); 465 } 466 } 467 } 468 469 /** 470 * Processes the supports-screens node. 471 * @param attributes the attributes for the supports-screens node. 472 */ 473 private void processSupportsScreensNode(Attributes attributes) { 474 mManifestData.mSupportsScreensFromManifest = new SupportsScreens(); 475 476 mManifestData.mSupportsScreensFromManifest.setResizeable(getAttributeBooleanValue( 477 attributes, AndroidManifest.ATTRIBUTE_RESIZEABLE, true /*hasNamespace*/)); 478 479 mManifestData.mSupportsScreensFromManifest.setAnyDensity(getAttributeBooleanValue( 480 attributes, AndroidManifest.ATTRIBUTE_ANYDENSITY, true /*hasNamespace*/)); 481 482 mManifestData.mSupportsScreensFromManifest.setSmallScreens(getAttributeBooleanValue( 483 attributes, AndroidManifest.ATTRIBUTE_SMALLSCREENS, true /*hasNamespace*/)); 484 485 mManifestData.mSupportsScreensFromManifest.setNormalScreens(getAttributeBooleanValue( 486 attributes, AndroidManifest.ATTRIBUTE_NORMALSCREENS, true /*hasNamespace*/)); 487 488 mManifestData.mSupportsScreensFromManifest.setLargeScreens(getAttributeBooleanValue( 489 attributes, AndroidManifest.ATTRIBUTE_LARGESCREENS, true /*hasNamespace*/)); 490 } 491 492 /** 493 * Processes the supports-screens node. 494 * @param attributes the attributes for the supports-screens node. 495 */ 496 private void processUsesConfiguration(Attributes attributes) { 497 mManifestData.mUsesConfiguration = new UsesConfiguration(); 498 499 mManifestData.mUsesConfiguration.mReqFiveWayNav = getAttributeBooleanValue( 500 attributes, 501 AndroidManifest.ATTRIBUTE_REQ_5WAYNAV, true /*hasNamespace*/); 502 mManifestData.mUsesConfiguration.mReqNavigation = Navigation.getEnum( 503 getAttributeValue(attributes, 504 AndroidManifest.ATTRIBUTE_REQ_NAVIGATION, true /*hasNamespace*/)); 505 mManifestData.mUsesConfiguration.mReqHardKeyboard = getAttributeBooleanValue( 506 attributes, 507 AndroidManifest.ATTRIBUTE_REQ_HARDKEYBOARD, true /*hasNamespace*/); 508 mManifestData.mUsesConfiguration.mReqKeyboardType = Keyboard.getEnum( 509 getAttributeValue(attributes, 510 AndroidManifest.ATTRIBUTE_REQ_KEYBOARDTYPE, true /*hasNamespace*/)); 511 mManifestData.mUsesConfiguration.mReqTouchScreen = TouchScreen.getEnum( 512 getAttributeValue(attributes, 513 AndroidManifest.ATTRIBUTE_REQ_TOUCHSCREEN, true /*hasNamespace*/)); 514 } 515 516 /** 517 * Searches through the attributes list for a particular one and returns its value. 518 * @param attributes the attribute list to search through 519 * @param attributeName the name of the attribute to look for. 520 * @param hasNamespace Indicates whether the attribute has an android namespace. 521 * @return a String with the value or null if the attribute was not found. 522 * @see SdkConstants#NS_RESOURCES 523 */ 524 private String getAttributeValue(Attributes attributes, String attributeName, 525 boolean hasNamespace) { 526 int count = attributes.getLength(); 527 for (int i = 0 ; i < count ; i++) { 528 if (attributeName.equals(attributes.getLocalName(i)) && 529 ((hasNamespace && 530 SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) || 531 (hasNamespace == false && attributes.getURI(i).length() == 0))) { 532 return attributes.getValue(i); 533 } 534 } 535 536 return null; 537 } 538 539 /** 540 * Searches through the attributes list for a particular one and returns its value as a 541 * Boolean. If the attribute is not present, this will return null. 542 * @param attributes the attribute list to search through 543 * @param attributeName the name of the attribute to look for. 544 * @param hasNamespace Indicates whether the attribute has an android namespace. 545 * @return a String with the value or null if the attribute was not found. 546 * @see SdkConstants#NS_RESOURCES 547 */ 548 private Boolean getAttributeBooleanValue(Attributes attributes, String attributeName, 549 boolean hasNamespace) { 550 int count = attributes.getLength(); 551 for (int i = 0 ; i < count ; i++) { 552 if (attributeName.equals(attributes.getLocalName(i)) && 553 ((hasNamespace && 554 SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) || 555 (hasNamespace == false && attributes.getURI(i).length() == 0))) { 556 String attr = attributes.getValue(i); 557 if (attr != null) { 558 return Boolean.valueOf(attr); 559 } else { 560 return null; 561 } 562 } 563 } 564 565 return null; 566 } 567 568 } 569 570 private final static SAXParserFactory sParserFactory; 571 572 static { 573 sParserFactory = SAXParserFactory.newInstance(); 574 sParserFactory.setNamespaceAware(true); 575 } 576 577 /** 578 * Parses the Android Manifest, and returns a {@link ManifestData} object containing the 579 * result of the parsing. 580 * 581 * @param manifestFile the {@link IAbstractFile} representing the manifest file. 582 * @param gatherData indicates whether the parsing will extract data from the manifest. If false 583 * the method will always return null. 584 * @param errorHandler an optional errorHandler. 585 * @return A class containing the manifest info obtained during the parsing, or null on error. 586 * 587 * @throws StreamException 588 * @throws IOException 589 * @throws SAXException 590 * @throws ParserConfigurationException 591 */ 592 public static ManifestData parse( 593 IAbstractFile manifestFile, 594 boolean gatherData, 595 ManifestErrorHandler errorHandler) 596 throws SAXException, IOException, StreamException, ParserConfigurationException { 597 if (manifestFile != null) { 598 SAXParser parser = sParserFactory.newSAXParser(); 599 600 ManifestData data = null; 601 if (gatherData) { 602 data = new ManifestData(); 603 } 604 605 ManifestHandler manifestHandler = new ManifestHandler(manifestFile, 606 data, errorHandler); 607 parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); 608 609 return data; 610 } 611 612 return null; 613 } 614 615 /** 616 * Parses the Android Manifest, and returns an object containing the result of the parsing. 617 * 618 * <p/> 619 * This is the equivalent of calling <pre>parse(manifestFile, true, null)</pre> 620 * 621 * @param manifestFile the manifest file to parse. 622 * 623 * @throws ParserConfigurationException 624 * @throws StreamException 625 * @throws IOException 626 * @throws SAXException 627 */ 628 public static ManifestData parse(IAbstractFile manifestFile) 629 throws SAXException, IOException, StreamException, ParserConfigurationException { 630 return parse(manifestFile, true, null); 631 } 632 633 public static ManifestData parse(IAbstractFolder projectFolder) 634 throws SAXException, IOException, StreamException, ParserConfigurationException { 635 IAbstractFile manifestFile = AndroidManifest.getManifest(projectFolder); 636 if (manifestFile == null) { 637 throw new FileNotFoundException(); 638 } 639 640 return parse(manifestFile, true, null); 641 } 642 643 /** 644 * Parses the Android Manifest from an {@link InputStream}, and returns a {@link ManifestData} 645 * object containing the result of the parsing. 646 * 647 * @param manifestFileStream the {@link InputStream} representing the manifest file. 648 * @return A class containing the manifest info obtained during the parsing or null on error. 649 * 650 * @throws StreamException 651 * @throws IOException 652 * @throws SAXException 653 * @throws ParserConfigurationException 654 */ 655 public static ManifestData parse(InputStream manifestFileStream) 656 throws SAXException, IOException, StreamException, ParserConfigurationException { 657 if (manifestFileStream != null) { 658 SAXParser parser = sParserFactory.newSAXParser(); 659 660 ManifestData data = new ManifestData(); 661 662 ManifestHandler manifestHandler = new ManifestHandler(null, data, null); 663 parser.parse(new InputSource(manifestFileStream), manifestHandler); 664 665 return data; 666 } 667 668 return null; 669 } 670 } 671