Skip to content

Commit 499d2b3

Browse files
committed
Polishing custom lint rules sample naming, comments, and use of LintUtils.
- Using Collections.singleton(List) where appropriate - Clarify meaning of 'main activity' - Use LintUtils - Formatting Change-Id: Iab1a82f619e103cf6401f68c36704402f6c87252
1 parent 09b1df9 commit 499d2b3

File tree

6 files changed

+73
-74
lines changed

6 files changed

+73
-74
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Custom Lint Rules
44
The [Android `lint` tool](http://developer.android.com/tools/help/lint.html) is a static code
55
analysis tool that checks your Android project source files for potential bugs and optimization
66
improvements for correctness, security, performance, usability, accessibility, and
7-
internationalization. Lint comes with over 100 checks, however it can be extended with additional
7+
internationalization. Lint comes with over 200 checks, however it can be extended with additional
88
custom rules.
99

1010
**NOTE: The lint API is not a final API; if you rely on this be prepared
@@ -42,7 +42,7 @@ Getting Started
4242

4343
##### Run lint
4444

45-
`lint`
45+
`./gradlew lint`
4646

4747
> Note: If you can't run `lint` directly, you may want to include android tools `PATH` in your
4848
`~/.bash_profile`.

gradle/wrapper/gradle-wrapper.jar

2.31 KB
Binary file not shown.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Wed Apr 10 15:27:10 PDT 2013
1+
#Fri Aug 21 14:39:26 PDT 2015
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

src/main/java/com/example/google/lint/MainActivityDetector.java

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.android.tools.lint.detector.api.Detector;
2323
import com.android.tools.lint.detector.api.Implementation;
2424
import com.android.tools.lint.detector.api.Issue;
25+
import com.android.tools.lint.detector.api.LintUtils;
2526
import com.android.tools.lint.detector.api.Location;
2627
import com.android.tools.lint.detector.api.ResourceXmlDetector;
2728
import com.android.tools.lint.detector.api.Scope;
@@ -30,10 +31,10 @@
3031

3132
import org.w3c.dom.Element;
3233
import org.w3c.dom.Node;
33-
import org.w3c.dom.NodeList;
3434

35-
import java.util.Arrays;
35+
import java.io.File;
3636
import java.util.Collection;
37+
import java.util.Collections;
3738
import java.util.EnumSet;
3839

3940
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
@@ -44,18 +45,20 @@
4445
import static com.android.SdkConstants.TAG_INTENT_FILTER;
4546
import static com.android.xml.AndroidManifest.NODE_ACTION;
4647
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;
4750

4851
/**
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>.
5053
* <p/>
5154
* <b>NOTE: This is not a final API; if you rely on this be prepared
5255
* to adjust your code for the next tools release.</b>
5356
*/
5457
public class MainActivityDetector extends ResourceXmlDetector implements Detector.XmlScanner {
5558
public static final Issue ISSUE = Issue.create(
5659
"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.",
5962
Category.CORRECTNESS,
6063
8,
6164
Severity.ERROR,
@@ -64,102 +67,103 @@ public class MainActivityDetector extends ResourceXmlDetector implements Detecto
6467
EnumSet.of(Scope.MANIFEST)));
6568

6669
/**
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.
6871
*/
6972
private boolean mHasActivity;
7073
/**
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.
7275
*/
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+
}
7487

7588
@Override
7689
public Collection<String> getApplicableElements() {
77-
return Arrays.asList(
78-
TAG_ACTIVITY
79-
);
90+
return Collections.singleton(TAG_ACTIVITY);
8091
}
8192

8293
@Override
83-
public void beforeCheckFile(@NonNull Context context) {
94+
public void beforeCheckProject(@NonNull Context context) {
8495
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+
}
86117
}
87118

88119
@Override
89120
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);
101124
}
102125
}
103126

104127
@Override
105128
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.
108131
mHasActivity = true;
109132
if (isMainActivity(activityElement)) {
110-
mHasMainActivity = true;
133+
mHasLauncherActivity = true;
111134
}
112135
}
113136

114137
/**
115-
* Returns true if the XML node is a main activity.
116-
* <p/>
117-
* A main activity is an <code>&lt;activity&gt;</code> node with an <code>&lt;
118-
* intent-filter&gt;</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.
124139
*
125140
* @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.
127142
*/
128143
private boolean isMainActivity(Node activityNode) {
129144
if (TAG_ACTIVITY.equals(activityNode.getNodeName())) {
130145
// 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)) {
134147
if (TAG_INTENT_FILTER.equals(activityChild.getNodeName())) {
135-
NodeList intentFilterChildren = activityChild.getChildNodes();
136-
137148
// Check for these children nodes:
138149
//
139150
// <category android:name="android.intent.category.LAUNCHER" />
140151
// <action android:name="android.intent.action.MAIN" />
141152
boolean hasLauncherCategory = false;
142153
boolean hasMainAction = false;
143154

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;
154161
}
155162
// 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;
163167
}
164168
}
165169

src/main/java/com/example/google/lint/MyIssueRegistry.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
public class MyIssueRegistry extends IssueRegistry {
3030
@Override
3131
public List<Issue> getIssues() {
32-
return Collections.singletonList(
33-
MainActivityDetector.ISSUE
34-
);
32+
return Collections.singletonList(MainActivityDetector.ISSUE);
3533
}
3634
}

src/test/java/com/example/google/lint/MainActivityDetectorTest.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.android.tools.lint.detector.api.Issue;
2424
import com.android.tools.lint.detector.api.Project;
2525

26-
import java.util.Arrays;
2726
import java.util.Collections;
2827
import java.util.HashSet;
2928
import java.util.List;
@@ -48,9 +47,7 @@ protected Detector getDetector() {
4847

4948
@Override
5049
protected List<Issue> getIssues() {
51-
return Arrays.asList(
52-
MainActivityDetector.ISSUE
53-
);
50+
return Collections.singletonList(MainActivityDetector.ISSUE);
5451
}
5552

5653
/**
@@ -68,7 +65,7 @@ public boolean isEnabled(@NonNull Issue issue) {
6865
}
6966

7067
/**
71-
* Test that a manifest with a main activity has no warnings.
68+
* Test that a manifest with an activity with a launcher intent has no warnings.
7269
*/
7370
public void testHasMainActivity() throws Exception {
7471
mEnabled = Collections.singleton(MainActivityDetector.ISSUE);
@@ -95,12 +92,12 @@ public void testHasMainActivity() throws Exception {
9592
}
9693

9794
/**
98-
* Test that a manifest <em>without</em> a main activity reports an error.
95+
* Test that a manifest <em>without</em> an activity with a launcher intent reports an error.
9996
*/
10097
public void testMissingMainActivity() throws Exception {
10198
mEnabled = Collections.singleton(MainActivityDetector.ISSUE);
102-
String expected = "AndroidManifest.xml: Error: Expecting AndroidManifest.xml to have a " +
103-
"main activity. [MainActivityDetector]\n" +
99+
String expected = "AndroidManifest.xml: Error: Expecting AndroidManifest.xml to have an " +
100+
"activity with a launcher intent. [MainActivityDetector]\n" +
104101
"1 errors, 0 warnings\n";
105102
String result = lintProject(xml(FN_ANDROID_MANIFEST_XML, "" +
106103
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +

0 commit comments

Comments
 (0)