Merge "37001: Lint check: non existing Activity/Service/Receiver"
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index ac09a1b..8915e81 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -54,7 +54,7 @@
     private static final List<Issue> sIssues;
 
     static {
-        final int initialCapacity = 106;
+        final int initialCapacity = 109;
         List<Issue> issues = new ArrayList<Issue>(initialCapacity);
 
         issues.add(AccessibilityDetector.ISSUE);
@@ -89,6 +89,9 @@
         issues.add(LocaleDetector.STRING_LOCALE);
         issues.add(LocaleDetector.DATE_FORMAT);
         issues.add(RegistrationDetector.ISSUE);
+        issues.add(MissingClassDetector.MISSING);
+        issues.add(MissingClassDetector.INSTANTIATABLE);
+        issues.add(MissingClassDetector.INNERCLASS);
         issues.add(HandlerDetector.ISSUE);
         issues.add(FragmentDetector.ISSUE);
         issues.add(TranslationDetector.EXTRA);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingClassDetector.java
new file mode 100644
index 0000000..8c80b68
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingClassDetector.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
+import static com.android.tools.lint.detector.api.LintConstants.ATTR_NAME;
+import static com.android.tools.lint.detector.api.LintConstants.ATTR_PACKAGE;
+import static com.android.tools.lint.detector.api.LintConstants.CONSTRUCTOR_NAME;
+import static com.android.tools.lint.detector.api.LintConstants.TAG_ACTIVITY;
+import static com.android.tools.lint.detector.api.LintConstants.TAG_APPLICATION;
+import static com.android.tools.lint.detector.api.LintConstants.TAG_PROVIDER;
+import static com.android.tools.lint.detector.api.LintConstants.TAG_RECEIVER;
+import static com.android.tools.lint.detector.api.LintConstants.TAG_SERVICE;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LayoutDetector;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Location.Handle;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.tools.lint.detector.api.XmlContext;
+import com.google.common.collect.Maps;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Checks to ensure that classes referenced in the manifest actually exist and are included
+ *
+ */
+public class MissingClassDetector extends LayoutDetector implements ClassScanner {
+    /** Manifest-referenced classes missing from the project or libraries */
+    public static final Issue MISSING = Issue.create(
+        "MissingRegistered", //$NON-NLS-1$
+        "Ensures that classes referenced in the manifest are present in the project or libraries",
+
+        "If a class is referenced in the manifest, it must also exist in the project (or in one " +
+        "of the libraries included by the project. This check helps uncover typos in " +
+        "registration names, or attempts to rename or move classes without updating the " +
+        "manifest file properly.",
+
+        Category.CORRECTNESS,
+        8,
+        Severity.ERROR,
+        MissingClassDetector.class,
+        EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE, Scope.JAVA_LIBRARIES)).setMoreInfo(
+        "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
+
+    /** Are activity, service, receiver etc subclasses instantiatable? */
+    public static final Issue INSTANTIATABLE = Issue.create(
+        "Instantiatable", //$NON-NLS-1$
+        "Ensures that classes registered in the manifest file are instantiatable",
+
+        "Activities, services, broadcast receivers etc. registered in the manifest file " +
+        "must be \"instiantable\" by the system, which means that the class must be " +
+        "public, it must have an empty public constructor, and if it's an inner class, " +
+        "it must be a static inner class.",
+
+        Category.CORRECTNESS,
+        6,
+        Severity.WARNING,
+        MissingClassDetector.class,
+        Scope.CLASS_FILE_SCOPE);
+
+    /** Is the right character used for inner class separators? */
+    public static final Issue INNERCLASS = Issue.create(
+        "InnerclassSeparator", //$NON-NLS-1$
+        "Ensures that inner classes are referenced using '$' instead of '.' in class names",
+
+        "When you reference an inner class in a manifest file, you must use '$' instead of '.' " +
+        "as the separator character, e.g. Outer$Inner instead of Outer.Inner.\n" +
+        "\n" +
+        "(If you get this warning for a class which is not actually an inner class, it's " +
+        "because you are using uppercase characters in your package name, which is not " +
+        "conventional.)",
+
+        Category.CORRECTNESS,
+        3,
+        Severity.WARNING,
+        MissingClassDetector.class,
+        Scope.MANIFEST_SCOPE);
+
+    private Map<String, Location.Handle> mReferencedClasses;
+
+    /** Constructs a new {@link MissingClassDetector} */
+    public MissingClassDetector() {
+    }
+
+    @Override
+    public @NonNull Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    // ---- Implements XmlScanner ----
+
+    @Override
+    public Collection<String> getApplicableElements() {
+        return Arrays.asList(
+                TAG_APPLICATION,
+                TAG_ACTIVITY,
+                TAG_SERVICE,
+                TAG_RECEIVER,
+                TAG_PROVIDER
+        );
+    }
+
+    @Override
+    public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
+
+        Element root = element.getOwnerDocument().getDocumentElement();
+        Attr classNameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+        if (classNameNode == null) {
+            return;
+        }
+        String className = classNameNode.getValue();
+        if (className.isEmpty()) {
+            return;
+        }
+
+        String pkg = root.getAttribute(ATTR_PACKAGE);
+        String fqcn;
+        if (className.startsWith(".")) { //$NON-NLS-1$
+            fqcn = pkg + className;
+        } else if (className.indexOf('.') == -1) {
+            // According to the <activity> manifest element documentation, this is not
+            // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
+            // but it appears in manifest files and appears to be supported by the runtime
+            // so handle this in code as well:
+            fqcn = pkg + '.' + className;
+        } else { // else: the class name is already a fully qualified class name
+            fqcn = className;
+        }
+
+        String signature = ClassContext.getInternalName(fqcn);
+        if (signature.isEmpty() || signature.startsWith(ANDROID_PKG_PREFIX)) {
+            return;
+        }
+
+        if (mReferencedClasses == null) {
+            mReferencedClasses = Maps.newHashMapWithExpectedSize(16);
+        }
+
+        Handle handle = context.parser.createLocationHandle(context, element);
+        mReferencedClasses.put(signature, handle);
+
+        if (signature.indexOf('$') != -1) {
+            if (className.indexOf('$') == -1 && className.indexOf('.', 1) > 0) {
+                boolean haveUpperCase = false;
+                for (int i = 0, n = pkg.length(); i < n; i++) {
+                    if (Character.isUpperCase(pkg.charAt(i))) {
+                        haveUpperCase = true;
+                        break;
+                    }
+                }
+                if (!haveUpperCase) {
+                    String message = String.format("Use '$' instead of '.' for inner classes " +
+                            "(or use only lowercase letters in package names)", className);
+                    Location location = context.getLocation(classNameNode);
+                    context.report(INNERCLASS, element, location, message, null);
+                }
+            }
+
+            // The internal name contains a $ which means it's an inner class.
+            // The conversion from fqcn to internal name is a bit ambiguous:
+            // "a.b.C.D" usually means "inner class D in class C in package a.b".
+            // However, it can (see issue 31592) also mean class D in package "a.b.C".
+            // To make sure we don't falsely complain that foo/Bar$Baz doesn't exist,
+            // in case the user has actually created a package named foo/Bar and a proper
+            // class named Baz, we register *both* into the reference map.
+            // When generating errors we'll look for these an rip them back out if
+            // it looks like one of the two variations have been seen.
+            signature = signature.replace('$', '/');
+            mReferencedClasses.put(signature, handle);
+        }
+    }
+
+    @Override
+    public void afterCheckProject(@NonNull Context context) {
+        if (!context.getProject().isLibrary() && mReferencedClasses != null &&
+                !mReferencedClasses.isEmpty()) {
+            List<String> classes = new ArrayList<String>(mReferencedClasses.keySet());
+            Collections.sort(classes);
+            for (String owner : classes) {
+                Location.Handle handle = mReferencedClasses.get(owner);
+                String fqcn = ClassContext.getFqcn(owner);
+
+                String signature = ClassContext.getInternalName(fqcn);
+                if (!signature.equals(owner)) {
+                    if (!mReferencedClasses.containsKey(signature)) {
+                        continue;
+                    }
+                } else {
+                    signature = signature.replace('$', '/');
+                    if (!mReferencedClasses.containsKey(signature)) {
+                        continue;
+                    }
+                }
+                mReferencedClasses.remove(owner);
+
+                String message = String.format(
+                        "Class referenced in the manifest, %1$s, was not found in the " +
+                        "project or the libraries", fqcn);
+                Location location = handle.resolve();
+                context.report(MISSING, location, message, null);
+            }
+        }
+    }
+
+    // ---- Implements ClassScanner ----
+
+    @Override
+    public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+        String curr = classNode.name;
+        if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) {
+            mReferencedClasses.remove(curr);
+
+            // Ensure that the class is public, non static and has a null constructor!
+
+            if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
+                context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+                        "This class should be public (%1$s)",
+                            ClassContext.createSignature(classNode.name, null, null)),
+                        null);
+                return;
+            }
+
+            if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
+                context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+                        "This inner class should be static (%1$s)",
+                            ClassContext.createSignature(classNode.name, null, null)),
+                        null);
+                return;
+            }
+
+            boolean hasDefaultConstructor = false;
+            @SuppressWarnings("rawtypes") // ASM API
+            List methodList = classNode.methods;
+            for (Object m : methodList) {
+                MethodNode method = (MethodNode) m;
+                if (method.name.equals(CONSTRUCTOR_NAME)) {
+                    if (method.desc.equals("()V")) { //$NON-NLS-1$
+                        // The constructor must be public
+                        if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
+                            hasDefaultConstructor = true;
+                        } else {
+                            context.report(INSTANTIATABLE, context.getLocation(method, classNode),
+                                    "The default constructor must be public",
+                                    null);
+                            // Also mark that we have a constructor so we don't complain again
+                            // below since we've already emitted a more specific error related
+                            // to the default constructor
+                            hasDefaultConstructor = true;
+                        }
+                    }
+                }
+            }
+
+            if (!hasDefaultConstructor) {
+                context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
+                        "This class should provide a default constructor (a public " +
+                        "constructor with no arguments) (%1$s)",
+                            ClassContext.createSignature(classNode.name, null, null)),
+                        null);
+            }
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RegistrationDetector.java
index b645253..5f4aff4 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/RegistrationDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RegistrationDetector.java
@@ -117,23 +117,29 @@
         }
     }
 
-    private static String getFqcn(Element element) {
-        StringBuilder sb = new StringBuilder();
+    /**
+     * Returns the fully qualified class name for a manifest entry element that
+     * specifies a name attribute
+     *
+     * @param element the element
+     * @return the fully qualified class name
+     */
+    @NonNull
+    private static String getFqcn(@NonNull Element element) {
         Element root = element.getOwnerDocument().getDocumentElement();
         String pkg = root.getAttribute(ATTR_PACKAGE);
         String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
         if (className.startsWith(".")) { //$NON-NLS-1$
-            sb.append(pkg);
+            return pkg + className;
         } else if (className.indexOf('.') == -1) {
             // According to the <activity> manifest element documentation, this is not
             // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
             // but it appears in manifest files and appears to be supported by the runtime
             // so handle this in code as well:
-            sb.append(pkg);
-            sb.append('.');
+            return pkg + '.' + className;
         } // else: the class name is already a fully qualified class name
-        sb.append(className);
-        return sb.toString();
+
+        return className;
     }
 
     // ---- Implements ClassScanner ----
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
index d644951..de5b65e 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -55,9 +55,19 @@
 public abstract class AbstractCheckTest extends TestCase {
     protected abstract Detector getDetector();
 
+    private Detector mDetector;
+
+    private Detector getDetectorInstance() {
+        if (mDetector == null) {
+            mDetector = getDetector();
+        }
+
+        return mDetector;
+    }
+
     protected List<Issue> getIssues() {
         List<Issue> issues = new ArrayList<Issue>();
-        Class<? extends Detector> detectorClass = getDetector().getClass();
+        Class<? extends Detector> detectorClass = getDetectorInstance().getClass();
         // Get the list of issues from the registry and filter out others, to make sure
         // issues are properly registered
         List<Issue> candidates = new BuiltinIssueRegistry().getIssues();
@@ -264,7 +274,7 @@
     }
 
     protected boolean isEnabled(Issue issue) {
-        Class<? extends Detector> detectorClass = getDetector().getClass();
+        Class<? extends Detector> detectorClass = getDetectorInstance().getClass();
         if (issue.getDetectorClass() == detectorClass) {
             return true;
         }
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingClassDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingClassDetectorTest.java
new file mode 100644
index 0000000..d8f18b6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingClassDetectorTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+import java.io.File;
+import java.util.Arrays;
+
+@SuppressWarnings("javadoc")
+public class MissingClassDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new MissingClassDetector();
+    }
+
+    public void test() throws Exception {
+        assertEquals(
+            "AndroidManifest.xml:13: Error: Class referenced in the manifest, test.pkg.TestProvider, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity android:name=\".TestProvider\" />\n" +
+            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.TestProvider2, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <service android:name=\"test.pkg.TestProvider2\" />\n" +
+            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "AndroidManifest.xml:15: Error: Class referenced in the manifest, test.pkg.TestService, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <provider android:name=\".TestService\" />\n" +
+            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "AndroidManifest.xml:16: Error: Class referenced in the manifest, test.pkg.OnClickActivity, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <receiver android:name=\"OnClickActivity\" />\n" +
+            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "AndroidManifest.xml:17: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <service android:name=\"TestReceiver\" />\n" +
+            "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "5 errors, 0 warnings\n",
+
+            lintProject(
+                "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath"
+            ));
+    }
+
+    public void testOkClasses() throws Exception {
+        assertEquals(
+            "No warnings.",
+
+            lintProject(
+                "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+                "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+                "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+                "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+                "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+                "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+                "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+                "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class",
+                "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java",
+                "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class"
+            ));
+    }
+
+    public void testOkLibraries() throws Exception {
+        assertEquals(
+            "No warnings.",
+
+            lintProject(
+                "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "bytecode/classes.jar=>libs/classes.jar"
+            ));
+    }
+
+    public void testLibraryProjects() throws Exception {
+        File master = getProjectDir("MasterProject",
+                // Master project
+                "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml",
+                "multiproject/main.properties=>project.properties",
+                "bytecode/.classpath=>.classpath"
+        );
+        File library = getProjectDir("LibraryProject",
+                // Library project
+                "multiproject/library-manifest.xml=>AndroidManifest.xml",
+                "multiproject/library.properties=>project.properties",
+                "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java",
+                "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class",
+                "bytecode/TestService.java.txt=>src/test/pkg/TestService.java",
+                "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class",
+                "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java",
+                "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class",
+                "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java",
+                "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class"
+                // Missing TestReceiver: Test should complain about just that class
+        );
+        assertEquals(
+           "MasterProject/AndroidManifest.xml:17: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" +
+           "        <service android:name=\"TestReceiver\" />\n" +
+           "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+           "1 errors, 0 warnings\n",
+
+           checkLint(Arrays.asList(master, library)));
+    }
+
+    public void testInnerClassStatic() throws Exception {
+        assertEquals(
+            "src/test/pkg/Foo.java:8: Warning: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" +
+            "    public class Baz extends Activity {\n" +
+            "    ^\n" +
+            "0 errors, 1 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifest.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Foo.java.txt=>src/test/pkg/Foo.java",
+                "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class",
+                "registration/Foo$Bar.class.data=>bin/classes/test/pkg/Foo$Bar.class",
+                "registration/Foo$Baz.class.data=>bin/classes/test/pkg/Foo$Baz.class"
+            ));
+    }
+
+    public void testInnerClassPublic() throws Exception {
+        assertEquals(
+            "src/test/pkg/Foo/Bar.java:6: Warning: The default constructor must be public [Instantiatable]\n" +
+            "    private Bar() {\n" +
+            "    ^\n" +
+            "0 errors, 1 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java",
+                "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class"
+            ));
+    }
+
+    public void testInnerClass() throws Exception {
+        assertEquals(
+            "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity\n" +
+            "        ^\n" +
+            "AndroidManifest.xml:23: Error: Class referenced in the manifest, test.pkg.Foo.Baz, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity\n" +
+            "        ^\n" +
+            "2 errors, 0 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifest.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Foo.java.txt=>src/test/pkg/Foo.java"
+            ));
+    }
+
+    public void testInnerClass2() throws Exception {
+        assertEquals(
+            "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity\n" +
+            "        ^\n" +
+            "1 errors, 0 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifestInner.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+            ));
+    }
+
+    public void testWrongSeparator1() throws Exception {
+        assertEquals(
+            "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity\n" +
+            "        ^\n" +
+            "1 errors, 0 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifestWrong.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+            ));
+    }
+
+    public void testWrongSeparator2() throws Exception {
+        assertEquals(
+            "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" +
+            "        <activity\n" +
+            "        ^\n" +
+            "AndroidManifest.xml:15: Warning: Use '$' instead of '.' for inner classes (or use only lowercase letters in package names) [InnerclassSeparator]\n" +
+            "            android:name=\".Foo.Bar\"\n" +
+            "            ~~~~~~~~~~~~~~~~~~~~~~~\n" +
+            "1 errors, 1 warnings\n",
+
+            lintProject(
+                "registration/AndroidManifestWrong2.xml=>AndroidManifest.xml",
+                "bytecode/.classpath=>.classpath",
+                "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java"
+            ));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classes.jar b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classes.jar
new file mode 100644
index 0000000..fa52dcf
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classes.jar
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifest.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifest.xml
new file mode 100644
index 0000000..0b07cdf
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".Foo$Bar"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".Foo$Baz"
+            android:label="@string/app_name" >
+        </activity>
+    </application>
+
+</manifest>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml
new file mode 100644
index 0000000..1b87d9d
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestInner.xml
@@ -0,0 +1,25 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg.Foo"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".Bar"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml
new file mode 100644
index 0000000..bfdcde0
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong.xml
@@ -0,0 +1,25 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg.Foo"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="test.pkg.Foo.Bar"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml
new file mode 100644
index 0000000..8773e0f
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/AndroidManifestWrong2.xml
@@ -0,0 +1,25 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.pkg"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="16" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".Foo.Bar"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.class.data
new file mode 100644
index 0000000..e51a0e8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.java.txt
new file mode 100644
index 0000000..b514f11
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Bar.java.txt
@@ -0,0 +1,8 @@
+package test.pkg.Foo;
+
+import android.app.Activity;
+
+public class Bar extends Activity {
+    private Bar() {
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data
new file mode 100644
index 0000000..da45ebb
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Bar.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data
new file mode 100644
index 0000000..aadd767
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo$Baz.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.class.data
new file mode 100644
index 0000000..aafcc73
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.java.txt
new file mode 100644
index 0000000..a647030
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/registration/Foo.java.txt
@@ -0,0 +1,10 @@
+package test.pkg;
+
+import android.app.Activity;
+
+public class Foo {
+    public static class Bar extends Activity {
+    }
+    public class Baz extends Activity {
+    }
+}