1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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.ide.eclipse.adt.internal.project; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AndroidConstants; 21 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener; 22 import com.android.sdklib.SdkConstants; 23 import com.android.sdklib.xml.AndroidManifest; 24 25 import org.eclipse.core.resources.IFile; 26 import org.eclipse.core.resources.IMarker; 27 import org.eclipse.core.resources.IProject; 28 import org.eclipse.core.resources.IResource; 29 import org.eclipse.core.runtime.CoreException; 30 import org.eclipse.jdt.core.IJavaProject; 31 import org.xml.sax.Attributes; 32 import org.xml.sax.InputSource; 33 import org.xml.sax.Locator; 34 import org.xml.sax.SAXException; 35 import org.xml.sax.SAXParseException; 36 37 import java.io.File; 38 import java.io.FileNotFoundException; 39 import java.io.FileReader; 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.Set; 43 import java.util.TreeSet; 44 45 import javax.xml.parsers.ParserConfigurationException; 46 import javax.xml.parsers.SAXParser; 47 import javax.xml.parsers.SAXParserFactory; 48 49 public class AndroidManifestParser { 50 51 private final static int LEVEL_MANIFEST = 0; 52 private final static int LEVEL_APPLICATION = 1; 53 private final static int LEVEL_ACTIVITY = 2; 54 private final static int LEVEL_INTENT_FILTER = 3; 55 private final static int LEVEL_CATEGORY = 4; 56 57 private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ 58 private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ 59 60 /** 61 * Instrumentation info obtained from manifest 62 */ 63 public static class Instrumentation { 64 private final String mName; 65 private final String mTargetPackage; 66 Instrumentation(String name, String targetPackage)67 Instrumentation(String name, String targetPackage) { 68 mName = name; 69 mTargetPackage = targetPackage; 70 } 71 72 /** 73 * Returns the fully qualified instrumentation class name 74 */ getName()75 public String getName() { 76 return mName; 77 } 78 79 /** 80 * Returns the Android app package that is the target of this instrumentation 81 */ getTargetPackage()82 public String getTargetPackage() { 83 return mTargetPackage; 84 } 85 } 86 87 /** 88 * Activity info obtained from the manifest. 89 */ 90 public static class Activity { 91 private final String mName; 92 private final boolean mIsExported; 93 private boolean mHasAction = false; 94 private boolean mHasMainAction = false; 95 private boolean mHasLauncherCategory = false; 96 Activity(String name, boolean exported)97 public Activity(String name, boolean exported) { 98 mName = name; 99 mIsExported = exported; 100 } 101 getName()102 public String getName() { 103 return mName; 104 } 105 isExported()106 public boolean isExported() { 107 return mIsExported; 108 } 109 hasAction()110 public boolean hasAction() { 111 return mHasAction; 112 } 113 isHomeActivity()114 public boolean isHomeActivity() { 115 return mHasMainAction && mHasLauncherCategory; 116 } 117 setHasAction(boolean hasAction)118 void setHasAction(boolean hasAction) { 119 mHasAction = hasAction; 120 } 121 122 /** If the activity doesn't yet have a filter set for the launcher, this resets both 123 * flags. This is to handle multiple intent-filters where one could have the valid 124 * action, and another one of the valid category. 125 */ resetIntentFilter()126 void resetIntentFilter() { 127 if (isHomeActivity() == false) { 128 mHasMainAction = mHasLauncherCategory = false; 129 } 130 } 131 setHasMainAction(boolean hasMainAction)132 void setHasMainAction(boolean hasMainAction) { 133 mHasMainAction = hasMainAction; 134 } 135 setHasLauncherCategory(boolean hasLauncherCategory)136 void setHasLauncherCategory(boolean hasLauncherCategory) { 137 mHasLauncherCategory = hasLauncherCategory; 138 } 139 } 140 141 /** 142 * XML error & data handler used when parsing the AndroidManifest.xml file. 143 * <p/> 144 * This serves both as an {@link XmlErrorHandler} to report errors and as a data repository 145 * to collect data from the manifest. 146 */ 147 private static class ManifestHandler extends XmlErrorHandler { 148 149 //--- data read from the parsing 150 151 /** Application package */ 152 private String mPackage; 153 /** List of all activities */ 154 private final ArrayList<Activity> mActivities = new ArrayList<Activity>(); 155 /** Launcher activity */ 156 private Activity mLauncherActivity = null; 157 /** list of process names declared by the manifest */ 158 private Set<String> mProcesses = null; 159 /** debuggable attribute value. If null, the attribute is not present. */ 160 private Boolean mDebuggable = null; 161 /** API level requirement. if null the attribute was not present. */ 162 private String mApiLevelRequirement = null; 163 /** List of all instrumentations declared by the manifest */ 164 private final ArrayList<Instrumentation> mInstrumentations = 165 new ArrayList<Instrumentation>(); 166 /** List of all libraries in use declared by the manifest */ 167 private final ArrayList<String> mLibraries = new ArrayList<String>(); 168 169 //--- temporary data/flags used during parsing 170 private IJavaProject mJavaProject; 171 private boolean mGatherData = false; 172 private boolean mMarkErrors = false; 173 private int mCurrentLevel = 0; 174 private int mValidLevel = 0; 175 private Activity mCurrentActivity = null; 176 private Locator mLocator; 177 178 /** 179 * Creates a new {@link ManifestHandler}, which is also an {@link XmlErrorHandler}. 180 * 181 * @param manifestFile The manifest file being parsed. Can be null. 182 * @param errorListener An optional error listener. 183 * @param gatherData True if data should be gathered. 184 * @param javaProject The java project holding the manifest file. Can be null. 185 * @param markErrors True if errors should be marked as Eclipse Markers on the resource. 186 */ ManifestHandler(IFile manifestFile, XmlErrorListener errorListener, boolean gatherData, IJavaProject javaProject, boolean markErrors)187 ManifestHandler(IFile manifestFile, XmlErrorListener errorListener, 188 boolean gatherData, IJavaProject javaProject, boolean markErrors) { 189 super(manifestFile, errorListener); 190 mGatherData = gatherData; 191 mJavaProject = javaProject; 192 mMarkErrors = markErrors; 193 } 194 195 /** 196 * Returns the package defined in the manifest, if found. 197 * @return The package name or null if not found. 198 */ getPackage()199 String getPackage() { 200 return mPackage; 201 } 202 203 /** 204 * Returns the list of activities found in the manifest. 205 * @return An array of fully qualified class names, or empty if no activity were found. 206 */ getActivities()207 Activity[] getActivities() { 208 return mActivities.toArray(new Activity[mActivities.size()]); 209 } 210 211 /** 212 * Returns the name of one activity found in the manifest, that is configured to show 213 * up in the HOME screen. 214 * @return the fully qualified name of a HOME activity or null if none were found. 215 */ getLauncherActivity()216 Activity getLauncherActivity() { 217 return mLauncherActivity; 218 } 219 220 /** 221 * Returns the list of process names declared by the manifest. 222 */ getProcesses()223 String[] getProcesses() { 224 if (mProcesses != null) { 225 return mProcesses.toArray(new String[mProcesses.size()]); 226 } 227 228 return new String[0]; 229 } 230 231 /** 232 * Returns the <code>debuggable</code> attribute value or null if it is not set. 233 */ getDebuggable()234 Boolean getDebuggable() { 235 return mDebuggable; 236 } 237 238 /** 239 * Returns the <code>minSdkVersion</code> attribute, or null if it's not set. 240 */ getApiLevelRequirement()241 String getApiLevelRequirement() { 242 return mApiLevelRequirement; 243 } 244 245 /** 246 * Returns the list of instrumentations found in the manifest. 247 * @return An array of {@link Instrumentation}, or empty if no instrumentations were 248 * found. 249 */ getInstrumentations()250 Instrumentation[] getInstrumentations() { 251 return mInstrumentations.toArray(new Instrumentation[mInstrumentations.size()]); 252 } 253 254 /** 255 * Returns the list of libraries in use found in the manifest. 256 * @return An array of library names, or empty if no libraries were found. 257 */ getUsesLibraries()258 String[] getUsesLibraries() { 259 return mLibraries.toArray(new String[mLibraries.size()]); 260 } 261 262 /* (non-Javadoc) 263 * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) 264 */ 265 @Override setDocumentLocator(Locator locator)266 public void setDocumentLocator(Locator locator) { 267 mLocator = locator; 268 super.setDocumentLocator(locator); 269 } 270 271 /* (non-Javadoc) 272 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, 273 * java.lang.String, org.xml.sax.Attributes) 274 */ 275 @Override startElement(String uri, String localName, String name, Attributes attributes)276 public void startElement(String uri, String localName, String name, Attributes attributes) 277 throws SAXException { 278 try { 279 if (mGatherData == false) { 280 return; 281 } 282 283 // if we're at a valid level 284 if (mValidLevel == mCurrentLevel) { 285 String value; 286 switch (mValidLevel) { 287 case LEVEL_MANIFEST: 288 if (AndroidManifest.NODE_MANIFEST.equals(localName)) { 289 // lets get the package name. 290 mPackage = getAttributeValue(attributes, 291 AndroidManifest.ATTRIBUTE_PACKAGE, 292 false /* hasNamespace */); 293 mValidLevel++; 294 } 295 break; 296 case LEVEL_APPLICATION: 297 if (AndroidManifest.NODE_APPLICATION.equals(localName)) { 298 value = getAttributeValue(attributes, 299 AndroidManifest.ATTRIBUTE_PROCESS, 300 true /* hasNamespace */); 301 if (value != null) { 302 addProcessName(value); 303 } 304 305 value = getAttributeValue(attributes, 306 AndroidManifest.ATTRIBUTE_DEBUGGABLE, 307 true /* hasNamespace*/); 308 if (value != null) { 309 mDebuggable = Boolean.parseBoolean(value); 310 } 311 312 mValidLevel++; 313 } else if (AndroidManifest.NODE_USES_SDK.equals(localName)) { 314 mApiLevelRequirement = getAttributeValue(attributes, 315 AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, 316 true /* hasNamespace */); 317 } else if (AndroidManifest.NODE_INSTRUMENTATION.equals(localName)) { 318 processInstrumentationNode(attributes); 319 } 320 break; 321 case LEVEL_ACTIVITY: 322 if (AndroidManifest.NODE_ACTIVITY.equals(localName)) { 323 processActivityNode(attributes); 324 mValidLevel++; 325 } else if (AndroidManifest.NODE_SERVICE.equals(localName)) { 326 processNode(attributes, AndroidConstants.CLASS_SERVICE); 327 mValidLevel++; 328 } else if (AndroidManifest.NODE_RECEIVER.equals(localName)) { 329 processNode(attributes, AndroidConstants.CLASS_BROADCASTRECEIVER); 330 mValidLevel++; 331 } else if (AndroidManifest.NODE_PROVIDER.equals(localName)) { 332 processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER); 333 mValidLevel++; 334 } else if (AndroidManifest.NODE_USES_LIBRARY.equals(localName)) { 335 value = getAttributeValue(attributes, 336 AndroidManifest.ATTRIBUTE_NAME, 337 true /* hasNamespace */); 338 if (value != null) { 339 mLibraries.add(value); 340 } 341 } 342 break; 343 case LEVEL_INTENT_FILTER: 344 // only process this level if we are in an activity 345 if (mCurrentActivity != null && 346 AndroidManifest.NODE_INTENT.equals(localName)) { 347 mCurrentActivity.resetIntentFilter(); 348 mValidLevel++; 349 } 350 break; 351 case LEVEL_CATEGORY: 352 if (mCurrentActivity != null) { 353 if (AndroidManifest.NODE_ACTION.equals(localName)) { 354 // get the name attribute 355 String action = getAttributeValue(attributes, 356 AndroidManifest.ATTRIBUTE_NAME, 357 true /* hasNamespace */); 358 if (action != null) { 359 mCurrentActivity.setHasAction(true); 360 mCurrentActivity.setHasMainAction( 361 ACTION_MAIN.equals(action)); 362 } 363 } else if (AndroidManifest.NODE_CATEGORY.equals(localName)) { 364 String category = getAttributeValue(attributes, 365 AndroidManifest.ATTRIBUTE_NAME, 366 true /* hasNamespace */); 367 if (CATEGORY_LAUNCHER.equals(category)) { 368 mCurrentActivity.setHasLauncherCategory(true); 369 } 370 } 371 372 // no need to increase mValidLevel as we don't process anything 373 // below this level. 374 } 375 break; 376 } 377 } 378 379 mCurrentLevel++; 380 } finally { 381 super.startElement(uri, localName, name, attributes); 382 } 383 } 384 385 /* (non-Javadoc) 386 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, 387 * java.lang.String) 388 */ 389 @Override endElement(String uri, String localName, String name)390 public void endElement(String uri, String localName, String name) throws SAXException { 391 try { 392 if (mGatherData == false) { 393 return; 394 } 395 396 // decrement the levels. 397 if (mValidLevel == mCurrentLevel) { 398 mValidLevel--; 399 } 400 mCurrentLevel--; 401 402 // if we're at a valid level 403 // process the end of the element 404 if (mValidLevel == mCurrentLevel) { 405 switch (mValidLevel) { 406 case LEVEL_ACTIVITY: 407 mCurrentActivity = null; 408 break; 409 case LEVEL_INTENT_FILTER: 410 // if we found both a main action and a launcher category, this is our 411 // launcher activity! 412 if (mLauncherActivity == null && 413 mCurrentActivity != null && 414 mCurrentActivity.isHomeActivity() && 415 mCurrentActivity.isExported()) { 416 mLauncherActivity = mCurrentActivity; 417 } 418 break; 419 default: 420 break; 421 } 422 423 } 424 } finally { 425 super.endElement(uri, localName, name); 426 } 427 } 428 429 /* (non-Javadoc) 430 * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) 431 */ 432 @Override error(SAXParseException e)433 public void error(SAXParseException e) { 434 if (mMarkErrors) { 435 handleError(e, e.getLineNumber()); 436 } 437 } 438 439 /* (non-Javadoc) 440 * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) 441 */ 442 @Override fatalError(SAXParseException e)443 public void fatalError(SAXParseException e) { 444 if (mMarkErrors) { 445 handleError(e, e.getLineNumber()); 446 } 447 } 448 449 /* (non-Javadoc) 450 * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) 451 */ 452 @Override warning(SAXParseException e)453 public void warning(SAXParseException e) throws SAXException { 454 if (mMarkErrors) { 455 super.warning(e); 456 } 457 } 458 459 /** 460 * Processes the activity node. 461 * @param attributes the attributes for the activity node. 462 */ processActivityNode(Attributes attributes)463 private void processActivityNode(Attributes attributes) { 464 // lets get the activity name, and add it to the list 465 String activityName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, 466 true /* hasNamespace */); 467 if (activityName != null) { 468 activityName = AndroidManifest.combinePackageAndClassName(mPackage, activityName); 469 470 // get the exported flag. 471 String exportedStr = getAttributeValue(attributes, 472 AndroidManifest.ATTRIBUTE_EXPORTED, true); 473 boolean exported = exportedStr == null || 474 exportedStr.toLowerCase().equals("true"); // $NON-NLS-1$ 475 mCurrentActivity = new Activity(activityName, exported); 476 mActivities.add(mCurrentActivity); 477 478 if (mMarkErrors) { 479 checkClass(activityName, AndroidConstants.CLASS_ACTIVITY, 480 true /* testVisibility */); 481 } 482 } else { 483 // no activity found! Aapt will output an error, 484 // so we don't have to do anything 485 mCurrentActivity = null; 486 } 487 488 String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, 489 true /* hasNamespace */); 490 if (processName != null) { 491 addProcessName(processName); 492 } 493 } 494 495 /** 496 * Processes the service/receiver/provider nodes. 497 * @param attributes the attributes for the activity node. 498 * @param superClassName the fully qualified name of the super class that this 499 * node is representing 500 */ processNode(Attributes attributes, String superClassName)501 private void processNode(Attributes attributes, String superClassName) { 502 // lets get the class name, and check it if required. 503 String serviceName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, 504 true /* hasNamespace */); 505 if (serviceName != null) { 506 serviceName = AndroidManifest.combinePackageAndClassName(mPackage, serviceName); 507 508 if (mMarkErrors) { 509 checkClass(serviceName, superClassName, false /* testVisibility */); 510 } 511 } 512 513 String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, 514 true /* hasNamespace */); 515 if (processName != null) { 516 addProcessName(processName); 517 } 518 } 519 520 /** 521 * Processes the instrumentation nodes. 522 * @param attributes the attributes for the activity node. 523 * node is representing 524 */ processInstrumentationNode(Attributes attributes)525 private void processInstrumentationNode(Attributes attributes) { 526 // lets get the class name, and check it if required. 527 String instrumentationName = getAttributeValue(attributes, 528 AndroidManifest.ATTRIBUTE_NAME, 529 true /* hasNamespace */); 530 if (instrumentationName != null) { 531 String instrClassName = AndroidManifest.combinePackageAndClassName(mPackage, 532 instrumentationName); 533 String targetPackage = getAttributeValue(attributes, 534 AndroidManifest.ATTRIBUTE_TARGET_PACKAGE, 535 true /* hasNamespace */); 536 mInstrumentations.add(new Instrumentation(instrClassName, targetPackage)); 537 if (mMarkErrors) { 538 checkClass(instrClassName, AndroidConstants.CLASS_INSTRUMENTATION, 539 true /* testVisibility */); 540 } 541 } 542 } 543 544 /** 545 * Checks that a class is valid and can be used in the Android Manifest. 546 * <p/> 547 * Errors are put as {@link IMarker} on the manifest file. 548 * @param className the fully qualified name of the class to test. 549 * @param superClassName the fully qualified name of the class it is supposed to extend. 550 * @param testVisibility if <code>true</code>, the method will check the visibility of 551 * the class or of its constructors. 552 */ checkClass(String className, String superClassName, boolean testVisibility)553 private void checkClass(String className, String superClassName, boolean testVisibility) { 554 if (mJavaProject == null) { 555 return; 556 } 557 // we need to check the validity of the activity. 558 String result = BaseProjectHelper.testClassForManifest(mJavaProject, 559 className, superClassName, testVisibility); 560 if (result != BaseProjectHelper.TEST_CLASS_OK) { 561 // get the line number 562 int line = mLocator.getLineNumber(); 563 564 // mark the file 565 IMarker marker = BaseProjectHelper.addMarker(getFile(), 566 AndroidConstants.MARKER_ANDROID, 567 result, line, IMarker.SEVERITY_ERROR); 568 569 // add custom attributes to be used by the manifest editor. 570 if (marker != null) { 571 try { 572 marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE, 573 AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY); 574 marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className); 575 } catch (CoreException e) { 576 } 577 } 578 } 579 } 580 581 /** 582 * Searches through the attributes list for a particular one and returns its value. 583 * @param attributes the attribute list to search through 584 * @param attributeName the name of the attribute to look for. 585 * @param hasNamespace Indicates whether the attribute has an android namespace. 586 * @return a String with the value or null if the attribute was not found. 587 * @see SdkConstants#NS_RESOURCES 588 */ getAttributeValue(Attributes attributes, String attributeName, boolean hasNamespace)589 private String getAttributeValue(Attributes attributes, String attributeName, 590 boolean hasNamespace) { 591 int count = attributes.getLength(); 592 for (int i = 0 ; i < count ; i++) { 593 if (attributeName.equals(attributes.getLocalName(i)) && 594 ((hasNamespace && 595 SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) || 596 (hasNamespace == false && attributes.getURI(i).length() == 0))) { 597 return attributes.getValue(i); 598 } 599 } 600 601 return null; 602 } 603 addProcessName(String processName)604 private void addProcessName(String processName) { 605 if (mProcesses == null) { 606 mProcesses = new TreeSet<String>(); 607 } 608 609 mProcesses.add(processName); 610 } 611 } 612 613 private static SAXParserFactory sParserFactory; 614 615 private final String mJavaPackage; 616 private final Activity[] mActivities; 617 private final Activity mLauncherActivity; 618 private final String[] mProcesses; 619 private final Boolean mDebuggable; 620 private final String mApiLevelRequirement; 621 private final Instrumentation[] mInstrumentations; 622 private final String[] mLibraries; 623 624 static { 625 sParserFactory = SAXParserFactory.newInstance(); 626 sParserFactory.setNamespaceAware(true); 627 } 628 629 /** 630 * Parses the Android Manifest, and returns an object containing the result of the parsing. 631 * <p/> 632 * This method is useful to parse a specific {@link IFile} in a Java project. 633 * <p/> 634 * If you only want to gather data, consider {@link #parseForData(IFile)} instead. 635 * 636 * @param javaProject The java project. 637 * @param manifestFile the {@link IFile} representing the manifest file. 638 * @param errorListener 639 * @param gatherData indicates whether the parsing will extract data from the manifest. 640 * @param markErrors indicates whether the error found during parsing should put a 641 * marker on the file. For class validation errors to put a marker, <code>gatherData</code> 642 * must be set to <code>true</code> 643 * @return an {@link AndroidManifestParser} or null if the parsing failed. 644 * @throws CoreException 645 */ parse( IJavaProject javaProject, IFile manifestFile, XmlErrorListener errorListener, boolean gatherData, boolean markErrors)646 public static AndroidManifestParser parse( 647 IJavaProject javaProject, 648 IFile manifestFile, 649 XmlErrorListener errorListener, 650 boolean gatherData, 651 boolean markErrors) 652 throws CoreException { 653 try { 654 if (manifestFile != null) { 655 SAXParser parser = sParserFactory.newSAXParser(); 656 657 ManifestHandler manifestHandler = new ManifestHandler(manifestFile, 658 errorListener, gatherData, javaProject, markErrors); 659 parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); 660 661 // get the result from the handler 662 return new AndroidManifestParser(manifestHandler.getPackage(), 663 manifestHandler.getActivities(), 664 manifestHandler.getLauncherActivity(), 665 manifestHandler.getProcesses(), 666 manifestHandler.getDebuggable(), 667 manifestHandler.getApiLevelRequirement(), 668 manifestHandler.getInstrumentations(), 669 manifestHandler.getUsesLibraries()); 670 } 671 } catch (ParserConfigurationException e) { 672 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 673 "Bad parser configuration for %s: %s", 674 manifestFile.getFullPath(), 675 e.getMessage()); 676 } catch (SAXException e) { 677 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 678 "Parser exception for %s: %s", 679 manifestFile.getFullPath(), 680 e.getMessage()); 681 } catch (IOException e) { 682 // Don't log a console error when failing to read a non-existing file 683 if (!(e instanceof FileNotFoundException)) { 684 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 685 "I/O error for %s: %s", 686 manifestFile.getFullPath(), 687 e.getMessage()); 688 } 689 } 690 691 return null; 692 } 693 694 /** 695 * Parses the Android Manifest, and returns an object containing the result of the parsing. 696 * <p/> 697 * This version parses a real {@link File} file given by an actual path, which is useful for 698 * parsing a file that is not part of an Eclipse Java project. 699 * <p/> 700 * It assumes errors cannot be marked on the file and that data gathering is enabled. 701 * 702 * @param manifestFile the manifest file to parse. 703 * @return an {@link AndroidManifestParser} or null if the parsing failed. 704 * @throws CoreException 705 */ parse(File manifestFile)706 private static AndroidManifestParser parse(File manifestFile) 707 throws CoreException { 708 try { 709 SAXParser parser = sParserFactory.newSAXParser(); 710 711 ManifestHandler manifestHandler = new ManifestHandler( 712 null, //manifestFile 713 null, //errorListener 714 true, //gatherData 715 null, //javaProject 716 false //markErrors 717 ); 718 719 parser.parse(new InputSource(new FileReader(manifestFile)), manifestHandler); 720 721 // get the result from the handler 722 723 return new AndroidManifestParser(manifestHandler.getPackage(), 724 manifestHandler.getActivities(), 725 manifestHandler.getLauncherActivity(), 726 manifestHandler.getProcesses(), 727 manifestHandler.getDebuggable(), 728 manifestHandler.getApiLevelRequirement(), 729 manifestHandler.getInstrumentations(), 730 manifestHandler.getUsesLibraries()); 731 } catch (ParserConfigurationException e) { 732 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 733 "Bad parser configuration for %s: %s", 734 manifestFile.getAbsolutePath(), 735 e.getMessage()); 736 } catch (SAXException e) { 737 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 738 "Parser exception for %s: %s", 739 manifestFile.getAbsolutePath(), 740 e.getMessage()); 741 } catch (IOException e) { 742 // Don't log a console error when failing to read a non-existing file 743 if (!(e instanceof FileNotFoundException)) { 744 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 745 "I/O error for %s: %s", 746 manifestFile.getAbsolutePath(), 747 e.getMessage()); 748 } 749 } 750 751 return null; 752 } 753 754 /** 755 * Parses the Android Manifest for the specified project, and returns an object containing 756 * the result of the parsing. 757 * @param javaProject The java project. Required if <var>markErrors</var> is <code>true</code> 758 * @param errorListener the {@link XmlErrorListener} object being notified of the presence 759 * of errors. Optional. 760 * @param gatherData indicates whether the parsing will extract data from the manifest. 761 * @param markErrors indicates whether the error found during parsing should put a 762 * marker on the file. For class validation errors to put a marker, <code>gatherData</code> 763 * must be set to <code>true</code> 764 * @return an {@link AndroidManifestParser} or null if the parsing failed. 765 * @throws CoreException 766 */ parse( IJavaProject javaProject, XmlErrorListener errorListener, boolean gatherData, boolean markErrors)767 public static AndroidManifestParser parse( 768 IJavaProject javaProject, 769 XmlErrorListener errorListener, 770 boolean gatherData, 771 boolean markErrors) 772 throws CoreException { 773 774 IFile manifestFile = getManifest(javaProject.getProject()); 775 776 try { 777 SAXParser parser = sParserFactory.newSAXParser(); 778 779 if (manifestFile != null) { 780 ManifestHandler manifestHandler = new ManifestHandler(manifestFile, 781 errorListener, gatherData, javaProject, markErrors); 782 783 parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); 784 785 // get the result from the handler 786 return new AndroidManifestParser(manifestHandler.getPackage(), 787 manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), 788 manifestHandler.getProcesses(), manifestHandler.getDebuggable(), 789 manifestHandler.getApiLevelRequirement(), 790 manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries()); 791 } 792 } catch (ParserConfigurationException e) { 793 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 794 "Bad parser configuration for %s", manifestFile.getFullPath()); 795 } catch (SAXException e) { 796 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 797 "Parser exception for %s", manifestFile.getFullPath()); 798 } catch (IOException e) { 799 AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), 800 "I/O error for %s", manifestFile.getFullPath()); 801 } 802 803 return null; 804 } 805 806 /** 807 * Parses the manifest file, collects data, and checks for errors. 808 * @param javaProject The java project. Required. 809 * @param manifestFile The manifest file to parse. 810 * @param errorListener the {@link XmlErrorListener} object being notified of the presence 811 * of errors. Optional. 812 * @return an {@link AndroidManifestParser} or null if the parsing failed. 813 * @throws CoreException 814 */ parseForError(IJavaProject javaProject, IFile manifestFile, XmlErrorListener errorListener)815 public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile, 816 XmlErrorListener errorListener) throws CoreException { 817 return parse(javaProject, manifestFile, errorListener, true, true); 818 } 819 820 /** 821 * Parses the manifest file, and collects data. 822 * @param manifestFile The manifest file to parse. 823 * @return an {@link AndroidManifestParser} or null if the parsing failed. 824 * @throws CoreException for example the file does not exist in the workspace or 825 * the workspace needs to be refreshed. 826 */ parseForData(IFile manifestFile)827 public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException { 828 return parse(null /* javaProject */, manifestFile, null /* errorListener */, 829 true /* gatherData */, false /* markErrors */); 830 } 831 832 /** 833 * Parses the manifest file, and collects data. 834 * 835 * @param osManifestFilePath The OS path of the manifest file to parse. 836 * @return an {@link AndroidManifestParser} or null if the parsing failed. 837 */ parseForData(String osManifestFilePath)838 public static AndroidManifestParser parseForData(String osManifestFilePath) { 839 try { 840 return parse(new File(osManifestFilePath)); 841 } catch (CoreException e) { 842 // Ignore workspace errors (unlikely to happen since this parses an actual file, 843 // not a workspace resource). 844 return null; 845 } 846 } 847 848 /** 849 * Returns the package defined in the manifest, if found. 850 * @return The package name or null if not found. 851 */ getPackage()852 public String getPackage() { 853 return mJavaPackage; 854 } 855 856 /** 857 * Returns the list of activities found in the manifest. 858 * @return An array of {@link Activity}, or empty if no activity were found. 859 */ getActivities()860 public Activity[] getActivities() { 861 return mActivities; 862 } 863 864 /** 865 * Returns the name of one activity found in the manifest, that is configured to show 866 * up in the HOME screen. 867 * @return The {@link Activity} representing a HOME activity or null if none were found. 868 */ getLauncherActivity()869 public Activity getLauncherActivity() { 870 return mLauncherActivity; 871 } 872 873 /** 874 * Returns the list of process names declared by the manifest. 875 */ getProcesses()876 public String[] getProcesses() { 877 return mProcesses; 878 } 879 880 /** 881 * Returns the debuggable attribute value or <code>null</code> if it is not set. 882 */ getDebuggable()883 public Boolean getDebuggable() { 884 return mDebuggable; 885 } 886 887 /** 888 * Returns the <code>minSdkVersion</code> attribute, or null if it's not set. 889 */ getApiLevelRequirement()890 public String getApiLevelRequirement() { 891 return mApiLevelRequirement; 892 } 893 894 /** 895 * Returns the list of instrumentations found in the manifest. 896 * @return An array of {@link Instrumentation}, or empty if no instrumentations were found. 897 */ getInstrumentations()898 public Instrumentation[] getInstrumentations() { 899 return mInstrumentations; 900 } 901 902 /** 903 * Returns the list of libraries in use found in the manifest. 904 * @return An array of library names, or empty if no uses-library declarations were found. 905 */ getUsesLibraries()906 public String[] getUsesLibraries() { 907 return mLibraries; 908 } 909 910 911 /** 912 * Private constructor to enforce using 913 * {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)}, 914 * {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}, 915 * or {@link #parseForError(IJavaProject, IFile, XmlErrorListener)} to get an 916 * {@link AndroidManifestParser} object. 917 * @param javaPackage the package parsed from the manifest. 918 * @param activities the list of activities parsed from the manifest. 919 * @param launcherActivity the launcher activity parser from the manifest. 920 * @param processes the list of custom processes declared in the manifest. 921 * @param debuggable the debuggable attribute, or null if not set. 922 * @param apiLevelRequirement the minSdkVersion attribute value or null if not set. 923 * @param instrumentations the list of instrumentations parsed from the manifest. 924 * @param libraries the list of libraries in use parsed from the manifest. 925 */ AndroidManifestParser(String javaPackage, Activity[] activities, Activity launcherActivity, String[] processes, Boolean debuggable, String apiLevelRequirement, Instrumentation[] instrumentations, String[] libraries)926 private AndroidManifestParser(String javaPackage, Activity[] activities, 927 Activity launcherActivity, String[] processes, Boolean debuggable, 928 String apiLevelRequirement, Instrumentation[] instrumentations, String[] libraries) { 929 mJavaPackage = javaPackage; 930 mActivities = activities; 931 mLauncherActivity = launcherActivity; 932 mProcesses = processes; 933 mDebuggable = debuggable; 934 mApiLevelRequirement = apiLevelRequirement; 935 mInstrumentations = instrumentations; 936 mLibraries = libraries; 937 } 938 939 /** 940 * Returns an IFile object representing the manifest for the specified 941 * project. 942 * 943 * @param project The project containing the manifest file. 944 * @return An IFile object pointing to the manifest or null if the manifest 945 * is missing. 946 */ getManifest(IProject project)947 public static IFile getManifest(IProject project) { 948 IResource r = project.findMember(AndroidConstants.WS_SEP 949 + AndroidConstants.FN_ANDROID_MANIFEST); 950 951 if (r == null || r.exists() == false || (r instanceof IFile) == false) { 952 return null; 953 } 954 return (IFile) r; 955 } 956 957 /** 958 * Given a fully qualified activity name (e.g. com.foo.test.MyClass) and given a project 959 * package base name (e.g. com.foo), returns the relative activity name that would be used 960 * the "name" attribute of an "activity" element. 961 * 962 * @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass" 963 * @param packageName The project base package name, e.g. "com.foo" 964 * @return The relative activity name if it can be computed or the original fullActivityName. 965 */ extractActivityName(String fullActivityName, String packageName)966 public static String extractActivityName(String fullActivityName, String packageName) { 967 if (packageName != null && fullActivityName != null) { 968 if (packageName.length() > 0 && fullActivityName.startsWith(packageName)) { 969 String name = fullActivityName.substring(packageName.length()); 970 if (name.length() > 0 && name.charAt(0) == '.') { 971 return name; 972 } 973 } 974 } 975 976 return fullActivityName; 977 } 978 } 979