• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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