22
22
import com .android .tools .lint .detector .api .Detector ;
23
23
import com .android .tools .lint .detector .api .Implementation ;
24
24
import com .android .tools .lint .detector .api .Issue ;
25
+ import com .android .tools .lint .detector .api .LintUtils ;
25
26
import com .android .tools .lint .detector .api .Location ;
26
27
import com .android .tools .lint .detector .api .ResourceXmlDetector ;
27
28
import com .android .tools .lint .detector .api .Scope ;
30
31
31
32
import org .w3c .dom .Element ;
32
33
import org .w3c .dom .Node ;
33
- import org .w3c .dom .NodeList ;
34
34
35
- import java .util . Arrays ;
35
+ import java .io . File ;
36
36
import java .util .Collection ;
37
+ import java .util .Collections ;
37
38
import java .util .EnumSet ;
38
39
39
40
import static com .android .SdkConstants .ANDROID_MANIFEST_XML ;
44
45
import static com .android .SdkConstants .TAG_INTENT_FILTER ;
45
46
import static com .android .xml .AndroidManifest .NODE_ACTION ;
46
47
import static com .android .xml .AndroidManifest .NODE_CATEGORY ;
48
+ import static com .example .google .lint .ManifestConstants .ACTION_NAME_MAIN ;
49
+ import static com .example .google .lint .ManifestConstants .CATEGORY_NAME_LAUNCHER ;
47
50
48
51
/**
49
- * Checks for a main activity in <code>AndroidManifest.xml</code>.
52
+ * Checks for an activity with a launcher intent in <code>AndroidManifest.xml</code>.
50
53
* <p/>
51
54
* <b>NOTE: This is not a final API; if you rely on this be prepared
52
55
* to adjust your code for the next tools release.</b>
53
56
*/
54
57
public class MainActivityDetector extends ResourceXmlDetector implements Detector .XmlScanner {
55
58
public static final Issue ISSUE = Issue .create (
56
59
"MainActivityDetector" ,
57
- "Checks for a main activity " ,
58
- "This app should have a main launcher activity ." ,
60
+ "Missing Launcher Activities " ,
61
+ "This app should have an activity with a launcher intent ." ,
59
62
Category .CORRECTNESS ,
60
63
8 ,
61
64
Severity .ERROR ,
@@ -64,102 +67,103 @@ public class MainActivityDetector extends ResourceXmlDetector implements Detecto
64
67
EnumSet .of (Scope .MANIFEST )));
65
68
66
69
/**
67
- * Will be <code>true</code> if the current file we're checking has at least one activity.
70
+ * This will be <code>true</code> if the current file we're checking has at least one activity.
68
71
*/
69
72
private boolean mHasActivity ;
70
73
/**
71
- * Will be <code>true</code> if the file has a main activity .
74
+ * This will be <code>true</code> if the file has an activity with a launcher intent .
72
75
*/
73
- private boolean mHasMainActivity ;
76
+ private boolean mHasLauncherActivity ;
77
+ /**
78
+ * The manifest file location for the main project, <code>null</code> if there is no manifest.
79
+ */
80
+ private Location mManifestLocation ;
81
+
82
+ /**
83
+ * No-args constructor used by the lint framework to instantiate the detector.
84
+ */
85
+ public MainActivityDetector () {
86
+ }
74
87
75
88
@ Override
76
89
public Collection <String > getApplicableElements () {
77
- return Arrays .asList (
78
- TAG_ACTIVITY
79
- );
90
+ return Collections .singleton (TAG_ACTIVITY );
80
91
}
81
92
82
93
@ Override
83
- public void beforeCheckFile (@ NonNull Context context ) {
94
+ public void beforeCheckProject (@ NonNull Context context ) {
84
95
mHasActivity = false ;
85
- mHasMainActivity = false ;
96
+ mHasLauncherActivity = false ;
97
+ mManifestLocation = null ;
98
+ }
99
+
100
+ @ Override
101
+ public void afterCheckProject (@ NonNull Context context ) {
102
+ // Don't report issues on libraries that may not have a launcher activity
103
+ if (context .getProject () == context .getMainProject ()
104
+ && !context .getMainProject ().isLibrary ()
105
+ && mManifestLocation != null ) {
106
+ if (!mHasActivity ) {
107
+ context .report (ISSUE , mManifestLocation ,
108
+ "Expecting " + ANDROID_MANIFEST_XML + " to have an <" + TAG_APPLICATION +
109
+ "> tag." );
110
+ } else if (!mHasLauncherActivity ) {
111
+ // Report the issue if the manifest file has no activity with a launcher intent.
112
+ context .report (ISSUE , mManifestLocation ,
113
+ "Expecting " + ANDROID_MANIFEST_XML +
114
+ " to have an activity with a launcher intent." );
115
+ }
116
+ }
86
117
}
87
118
88
119
@ Override
89
120
public void afterCheckFile (@ NonNull Context context ) {
90
- // Report an error if there are no <application> tags to check.
91
- // We assume the application tag is in the right place (i.e. have correct parent elements).
92
- Location location = Location .create (context .file );
93
- if (!mHasActivity ) {
94
- context .report (ISSUE , location ,
95
- "Expecting " + ANDROID_MANIFEST_XML + " to have an <" + TAG_APPLICATION +
96
- "> tag." );
97
- } else if (!mHasMainActivity ) {
98
- // Report the issue if the manifest file has no main activity.
99
- context .report (ISSUE , location ,
100
- "Expecting " + ANDROID_MANIFEST_XML + " to have a main activity." );
121
+ // Store a reference to the manifest file in case we need to report it's location.
122
+ if (context .getProject () == context .getMainProject ()) {
123
+ mManifestLocation = Location .create (context .file );
101
124
}
102
125
}
103
126
104
127
@ Override
105
128
public void visitElement (XmlContext context , Element activityElement ) {
106
- // Checks every activity under the <application> element and reports an error if there is
107
- // no main activity .
129
+ // Checks every activity and reports an error if there is no activity with a launcher
130
+ // intent .
108
131
mHasActivity = true ;
109
132
if (isMainActivity (activityElement )) {
110
- mHasMainActivity = true ;
133
+ mHasLauncherActivity = true ;
111
134
}
112
135
}
113
136
114
137
/**
115
- * Returns true if the XML node is a main activity.
116
- * <p/>
117
- * A main activity is an <code><activity></code> node with an <code><
118
- * intent-filter></code> that contains the following tags:
119
- * <p/>
120
- * <pre>
121
- * <category android:name="android.intent.category.LAUNCHER" />
122
- * <action android:name="android.intent.action.MAIN" />
123
- * </pre>
138
+ * Returns true if the XML node is an activity with a launcher intent.
124
139
*
125
140
* @param activityNode The node to check.
126
- * @return <code>true</code> if the node is a main activity .
141
+ * @return <code>true</code> if the node is an activity with a launcher intent .
127
142
*/
128
143
private boolean isMainActivity (Node activityNode ) {
129
144
if (TAG_ACTIVITY .equals (activityNode .getNodeName ())) {
130
145
// Loop through all <intent-filter> tags
131
- NodeList activityChildren = activityNode .getChildNodes ();
132
- for (int i = 0 ; i < activityChildren .getLength (); ++i ) {
133
- Node activityChild = activityChildren .item (i );
146
+ for (Element activityChild : LintUtils .getChildren (activityNode )) {
134
147
if (TAG_INTENT_FILTER .equals (activityChild .getNodeName ())) {
135
- NodeList intentFilterChildren = activityChild .getChildNodes ();
136
-
137
148
// Check for these children nodes:
138
149
//
139
150
// <category android:name="android.intent.category.LAUNCHER" />
140
151
// <action android:name="android.intent.action.MAIN" />
141
152
boolean hasLauncherCategory = false ;
142
153
boolean hasMainAction = false ;
143
154
144
- for (int j = 0 ; j < intentFilterChildren .getLength (); ++j ) {
145
- Node intentFilterChild = intentFilterChildren .item (j );
146
- // Check for category tag
147
- if (NODE_CATEGORY .equals (intentFilterChild .getNodeName ())) {
148
- Node categoryName = intentFilterChild .getAttributes ()
149
- .getNamedItemNS (ANDROID_URI , ATTR_NAME );
150
- if (categoryName != null && categoryName .getNodeValue ().equals (
151
- ManifestConstants .CATEGORY_NAME_LAUNCHER )) {
152
- hasLauncherCategory = true ;
153
- }
155
+ for (Element intentFilterChild : LintUtils .getChildren (activityChild )) {
156
+ // Check for category tag)
157
+ if (NODE_CATEGORY .equals (intentFilterChild .getNodeName ())
158
+ && CATEGORY_NAME_LAUNCHER .equals (
159
+ intentFilterChild .getAttributeNS (ANDROID_URI , ATTR_NAME ))) {
160
+ hasLauncherCategory = true ;
154
161
}
155
162
// Check for action tag
156
- if (NODE_ACTION .equals (intentFilterChild .getNodeName ())) {
157
- Node actionName = intentFilterChild .getAttributes ()
158
- .getNamedItemNS (ANDROID_URI , ATTR_NAME );
159
- if (actionName != null && actionName .getNodeValue ().equals (
160
- ManifestConstants .ACTION_NAME_MAIN )) {
161
- hasMainAction = true ;
162
- }
163
+ if (NODE_ACTION .equals (intentFilterChild .getNodeName ())
164
+ && ACTION_NAME_MAIN .equals (
165
+ intentFilterChild .getAttributeNS (ANDROID_URI , ATTR_NAME ))) {
166
+ hasMainAction = true ;
163
167
}
164
168
}
165
169
0 commit comments