AI 143919: am: CL 143918 am: CL 143917 ADT Android JUnit: Change logic to provide an explicit project or package to run to the device InstrumentationTestRunner, instead of providing the potentially huge list of test classes. Discontinue support for running all tests in a source folder.
  Original author: brettchabot
  Merged from: //branches/cupcake/...
  Original author: android-build
  Merged from: //branches/donutburger/...

Automated import of CL 143919
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
index 9995426..9dd1d16 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
@@ -49,6 +49,7 @@
     private static final String LOG_ARG_NAME = "log";
     private static final String DEBUG_ARG_NAME = "debug";
     private static final String COVERAGE_ARG_NAME = "coverage";
+    private static final String PACKAGE_ARG_NAME = "package";
  
     /**
      * Creates a remote Android test runner.
@@ -146,6 +147,16 @@
     }
 
     /**
+     * Sets to run all tests in specified package
+     * Must be called before 'run'.
+     * 
+     * @param packageName fully qualified package name (eg x.y.z)
+     */
+    public void setTestPackageName(String packageName) {
+        addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
+    }
+
+    /**
      * Adds a argument to include in instrumentation command.
      * <p/>
      * Must be called before 'run'. If an argument with given name has already been provided, it's
diff --git a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
index 6a653ad..864e219 100644
--- a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
+++ b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
@@ -17,16 +17,17 @@
 package com.android.ddmlib.testrunner;
 
 import com.android.ddmlib.Client;
-import com.android.ddmlib.Device.DeviceState;
 import com.android.ddmlib.FileListingService;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.IShellOutputReceiver;
-import com.android.ddmlib.log.LogReceiver;
 import com.android.ddmlib.RawImage;
 import com.android.ddmlib.SyncService;
+import com.android.ddmlib.Device.DeviceState;
+import com.android.ddmlib.log.LogReceiver;
 
 import java.io.IOException;
 import java.util.Map;
+
 import junit.framework.TestCase;
 
 /**
@@ -81,6 +82,17 @@
     }
 
     /**
+     * Test the building of the instrumentation runner command with test package set.
+     */
+    public void testRunWithPackage() {
+        final String packageName = "foo.test";
+        mRunner.setTestPackageName(packageName);
+        mRunner.run(new EmptyListener());
+        assertStringsEquals(String.format("am instrument -w -r -e package %s %s/%s", packageName,
+                TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
+    }
+
+    /**
      * Test the building of the instrumentation runner command with extra argument added.
      */
     public void testRunWithAddInstrumentationArg() {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 4b9d3a0..8092f3a 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -43,7 +43,8 @@
  org.eclipse.jdt.junit,
  org.eclipse.jdt.junit.runtime,
  org.eclipse.ltk.core.refactoring,
- org.eclipse.ltk.ui.refactoring
+ org.eclipse.ltk.ui.refactoring,
+ org.eclipse.core.expressions
 Eclipse-LazyStart: true
 Export-Package: com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.tests",
  com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index a75b8b9..35ceba7 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -563,7 +563,7 @@
                       <adapt type="org.eclipse.jdt.core.IJavaElement">
                         <test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/>
                         <test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/>
-                        <test property="org.eclipse.jdt.junit.canLaunchAsJUnit" forcePluginActivation="true"/>
+                        <test property="com.android.ide.eclipse.adt.canLaunchAsJUnit"/>
                      </adapt>
                   </iterate>
                </with>
@@ -595,4 +595,14 @@
             id="com.android.ide.eclipse.adt.refactoring.extract.string">
       </contribution>
    </extension>
+    <extension
+         point="org.eclipse.core.expressions.propertyTesters">
+      <propertyTester
+            properties="isTest,canLaunchAsJUnit"
+            namespace="com.android.ide.eclipse.adt"
+            type="org.eclipse.core.runtime.IAdaptable"
+            class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitPropertyTester"
+            id="com.android.ide.eclipse.adt.AndroidJUnitPropertyTester">
+      </propertyTester>
+   </extension>
 </plugin>
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
index 747fcfe..9bcc63d 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
@@ -24,7 +24,6 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.debug.core.DebugException;
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchManager;
@@ -39,18 +38,15 @@
  */
 class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
 
-    private String mTestPackage;
-    private String mRunner;
+    private final AndroidJUnitLaunchInfo mLaunchInfo;
     
     /**
      * Creates a AndroidJUnitLaunchAction.
      * 
-     * @param testPackage the Android application package that contains the tests to run 
-     * @param runner the InstrumentationTestRunner that will execute the tests
+     * @param launchInfo the {@link AndroidJUnitLaunchInfo} for the JUnit run 
      */
-    public AndroidJUnitLaunchAction(String testPackage, String runner) {
-        mTestPackage = testPackage;
-        mRunner = runner;
+    public AndroidJUnitLaunchAction(AndroidJUnitLaunchInfo launchInfo) {
+        mLaunchInfo = launchInfo;
     }
     
     /**
@@ -60,17 +56,21 @@
      * @see IAndroidLaunchAction#doLaunchAction(DelayedLaunchInfo, IDevice)
      */
     public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
-        String msg = String.format("Launching instrumentation %s on device %s", mRunner,
-                device.getSerialNumber());
+        String msg = String.format("Launching instrumentation %s on device %s",
+                mLaunchInfo.getRunner(), device.getSerialNumber());
         AdtPlugin.printToConsole(info.getProject(), msg);
         
         try {
-           JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(info, device);
+           mLaunchInfo.setDebugMode(info.isDebugMode());
+           mLaunchInfo.setDevice(info.getDevice());
+           mLaunchInfo.setLaunch(info.getLaunch());
+           JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(mLaunchInfo);
            final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE : 
                ILaunchManager.RUN_MODE; 
+
            junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
                    info.getMonitor());
-           
+
            // TODO: need to add AMReceiver-type functionality somewhere
         } catch (CoreException e) {
             AdtPlugin.printErrorToConsole(info.getProject(), "Failed to launch test");
@@ -82,20 +82,18 @@
      * {@inheritDoc}
      */
     public String getLaunchDescription() {
-        return String.format("%s JUnit launch", mRunner);
+        return String.format("%s JUnit launch", mLaunchInfo.getRunner());
     }
 
     /**
      * Extends the JDT JUnit launch delegate to allow for JUnit UI reuse. 
      */
-    private class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
+    private static class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
         
-        private IDevice mDevice;
-        private DelayedLaunchInfo mLaunchInfo;
+        private AndroidJUnitLaunchInfo mLaunchInfo;
 
-        public JUnitLaunchDelegate(DelayedLaunchInfo info, IDevice device) {
-            mLaunchInfo = info;
-            mDevice = device;
+        public JUnitLaunchDelegate(AndroidJUnitLaunchInfo launchInfo) {
+            mLaunchInfo = launchInfo;
         }
 
         /* (non-Javadoc)
@@ -110,34 +108,28 @@
 
         /**
          * {@inheritDoc}
-         * @throws CoreException
          * @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
          */
         @Override
-        public String verifyMainTypeName(ILaunchConfiguration configuration) throws CoreException {
+        public String verifyMainTypeName(ILaunchConfiguration configuration) {
             return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
         }
 
         /**
          * Overrides parent to return a VM Runner implementation which launches a thread, rather
          * than a separate VM process
-         * @throws CoreException 
          */
         @Override
-        public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode) 
-                throws CoreException {
-            return new VMTestRunner(new AndroidJUnitLaunchInfo(mLaunchInfo.getProject(), 
-                    mTestPackage, mRunner, mLaunchInfo.isDebugMode(), mDevice));
+        public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode) {
+            return new VMTestRunner(mLaunchInfo);
         }
 
         /**
          * {@inheritDoc}
-         * @throws CoreException 
          * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
          */
         @Override
-        public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
-                throws CoreException {
+        public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) {
             return mLaunchInfo.getLaunch();
         }     
     }
@@ -161,7 +153,7 @@
                 IProgressMonitor monitor) throws CoreException {
             
             TestRunnerProcess runnerProcess = 
-                new TestRunnerProcess(config, launch, mJUnitInfo);
+                new TestRunnerProcess(config, mJUnitInfo);
             runnerProcess.start();
             launch.addProcess(runnerProcess);
         }
@@ -173,15 +165,12 @@
     private static class TestRunnerProcess extends Thread implements IProcess  {
 
         private final VMRunnerConfiguration mRunConfig;
-        private final ILaunch mLaunch;
         private final AndroidJUnitLaunchInfo mJUnitInfo;
         private RemoteAdtTestRunner mTestRunner = null;
         private boolean mIsTerminated = false;
         
-        TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch,
-                AndroidJUnitLaunchInfo info) {
+        TestRunnerProcess(VMRunnerConfiguration runConfig, AndroidJUnitLaunchInfo info) {
             mRunConfig = runConfig;
-            mLaunch = launch;
             mJUnitInfo = info;
         }
         
@@ -194,10 +183,9 @@
 
         /**
          * {@inheritDoc}
-         * @throws DebugException 
          * @see org.eclipse.debug.core.model.IProcess#getExitValue()
          */
-        public int getExitValue() throws DebugException {
+        public int getExitValue() {
             return 0;
         }
 
@@ -205,14 +193,14 @@
          * @see org.eclipse.debug.core.model.IProcess#getLabel()
          */
         public String getLabel() {
-            return mLaunch.getLaunchMode();
+            return mJUnitInfo.getLaunch().getLaunchMode();
         }
 
         /* (non-Javadoc)
          * @see org.eclipse.debug.core.model.IProcess#getLaunch()
          */
         public ILaunch getLaunch() {
-            return mLaunch;
+            return mJUnitInfo.getLaunch();
         }
 
         /* (non-Javadoc)
@@ -254,10 +242,9 @@
 
         /**
          * {@inheritDoc}
-         * @throws DebugException 
          * @see org.eclipse.debug.core.model.ITerminate#terminate()
          */
-        public void terminate() throws DebugException {
+        public void terminate() {
             if (mTestRunner != null) {
                 mTestRunner.terminate();
             }    
@@ -274,3 +261,4 @@
         }
     }
 }
+
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
index fa8e4b0..543daf0 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
@@ -22,6 +22,7 @@
 import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
 import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
 import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
+import com.android.ide.eclipse.adt.launch.junit.runtime.AndroidJUnitLaunchInfo;
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.AndroidManifestParser;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
@@ -32,8 +33,11 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
 import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
 
 /**
  * Run configuration that can execute JUnit tests on an Android platform.
@@ -47,6 +51,7 @@
 
     /** Launch config attribute that stores instrumentation runner. */
     static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$
+
     private static final String EMPTY_STRING = ""; //$NON-NLS-1$
 
     @Override
@@ -55,7 +60,7 @@
             AndroidLaunchConfiguration config, AndroidLaunchController controller,
             IFile applicationPackage, AndroidManifestParser manifestParser) {
 
-        String testPackage = manifestParser.getPackage();        
+        String appPackage = manifestParser.getPackage();        
         String runner = getRunner(project, configuration, manifestParser);
         if (runner == null) {
             AdtPlugin.displayError("Android Launch",
@@ -63,8 +68,13 @@
             androidLaunch.stopLaunch();
             return;
         }
+        AndroidJUnitLaunchInfo junitLaunchInfo = new AndroidJUnitLaunchInfo(project, appPackage, 
+                runner);
+        junitLaunchInfo.setTestClass(getTestClass(configuration));
+        junitLaunchInfo.setTestPackage(getTestPackage(configuration));
+        junitLaunchInfo.setTestMethod(getTestMethod(configuration));
 
-        IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(testPackage, runner);
+        IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(junitLaunchInfo);
 
         controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
                 manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
@@ -72,6 +82,49 @@
     }
     
     /**
+     * Returns the test package stored in the launch configuration, or <code>null</code> if not 
+     * specified.
+     * 
+     * @param configuration the {@link ILaunchConfiguration} to retrieve the test package info from
+     * @return the test package or <code>null</code>.
+     */
+    private String getTestPackage(ILaunchConfiguration configuration) {
+        // try to retrieve a package name from the JUnit container attribute
+        String containerHandle = getStringLaunchAttribute(
+                JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, configuration);
+        if (containerHandle != null && containerHandle.length() > 0) {
+            IJavaElement element = JavaCore.create(containerHandle);
+            // containerHandle could be a IProject, check if its a java package
+            if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
+                return element.getElementName();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the test class stored in the launch configuration.
+     *
+     * @param configuration the {@link ILaunchConfiguration} to retrieve the test class info from
+     * @return the test class. <code>null</code> if not specified.
+     */
+    private String getTestClass(ILaunchConfiguration configuration) {
+        return getStringLaunchAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
+                configuration);
+    }
+    
+    /**
+     * Returns the test method stored in the launch configuration.
+     *
+     * @param configuration the {@link ILaunchConfiguration} to retrieve the test method info from
+     * @return the test method. <code>null</code> if not specified.
+     */
+    private String getTestMethod(ILaunchConfiguration configuration) {
+        return getStringLaunchAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
+                configuration);
+    }
+
+    /**
      * Gets a instrumentation runner for the launch. 
      * <p/>
      * If a runner is stored in the given <code>configuration</code>, will return that.
@@ -114,11 +167,29 @@
     }
 
     private String getRunnerFromConfig(ILaunchConfiguration configuration) throws CoreException {
-        String runner = configuration.getAttribute(ATTR_INSTR_NAME, EMPTY_STRING);
-        if (runner.length() < 1) {
-            return null;
+        return getStringLaunchAttribute(ATTR_INSTR_NAME, configuration);
+    }
+    
+    /**
+     * Helper method to retrieve a string attribute from the launch configuration
+     * 
+     * @param attributeName name of the launch attribute
+     * @param configuration the {@link ILaunchConfiguration} to retrieve the attribute from
+     * @return the attribute's value. <code>null</code> if not found.
+     */
+    private String getStringLaunchAttribute(String attributeName,
+            ILaunchConfiguration configuration) {
+        try {
+            String attrValue = configuration.getAttribute(attributeName, EMPTY_STRING);
+            if (attrValue.length() < 1) {
+                return null;
+            }
+            return attrValue;
+        } catch (CoreException e) {
+            AdtPlugin.log(e, String.format("Error when retrieving launch info %1$s",  //$NON-NLS-1$
+                    attributeName));
         }
-        return runner;
+        return null;
     }
 
     /**
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
index eb57482..584d45e 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
@@ -18,6 +18,7 @@
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab;
 import com.android.ide.eclipse.common.AndroidConstants;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
 import com.android.ide.eclipse.common.project.ProjectChooserHelper;
 
 import org.eclipse.core.resources.IProject;
@@ -241,7 +242,7 @@
     private void createTestContainerSelectionGroup(Composite comp) {
         mTestContainerRadioButton = new Button(comp, SWT.RADIO);
         mTestContainerRadioButton.setText(
-                JUnitMessages.JUnitLaunchConfigurationTab_label_containerTest); 
+                "Run all tests in the selected project, or package"); 
         GridData gd = new GridData();
         gd.horizontalSpan = 3;
         mTestContainerRadioButton.setLayoutData(gd);
@@ -249,12 +250,12 @@
             public void widgetSelected(SelectionEvent e) {
                 if (mTestContainerRadioButton.getSelection()) {
                     testModeChanged();
-                }    
+                }
             }
             public void widgetDefaultSelected(SelectionEvent e) {
             }
         });
-        
+
         mContainerText = new Text(comp, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
         gd = new GridData(GridData.FILL_HORIZONTAL);
         gd.horizontalIndent = 25;
@@ -265,7 +266,7 @@
                 updateLaunchConfigurationDialog();
             }
         });
-        
+
         mContainerSearchButton = new Button(comp, SWT.PUSH);
         mContainerSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search); 
         mContainerSearchButton.addSelectionListener(new SelectionAdapter() {
@@ -821,7 +822,7 @@
 
     @SuppressWarnings("unchecked")
     private IJavaElement chooseContainer(IJavaElement initElement) {
-        Class[] acceptedClasses = new Class[] { IPackageFragmentRoot.class, IJavaProject.class,
+        Class[] acceptedClasses = new Class[] { IJavaProject.class,
                 IPackageFragment.class };
         TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
                 acceptedClasses, false) {
@@ -839,7 +840,7 @@
                 if (element instanceof IPackageFragmentRoot && 
                         ((IPackageFragmentRoot) element).isArchive()) {
                     return false;
-                }    
+                }
                 try {
                     if (element instanceof IPackageFragment &&
                             !((IPackageFragment) element).hasChildren()) {
@@ -852,7 +853,7 @@
             }
         };      
 
-        StandardJavaElementContentProvider provider = new StandardJavaElementContentProvider();
+        AndroidJavaElementContentProvider provider = new AndroidJavaElementContentProvider();
         ILabelProvider labelProvider = new JavaElementLabelProvider(
                 JavaElementLabelProvider.SHOW_DEFAULT); 
         ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), 
@@ -974,4 +975,23 @@
         mInstrumentations = null;
         mInstrumentationCombo.removeAll();
     }
+
+    /**
+     * Overrides the {@link StandardJavaElementContentProvider} to only display Android projects
+     */
+    private static class AndroidJavaElementContentProvider
+            extends StandardJavaElementContentProvider {
+
+        /**
+         * Override parent to return only Android projects if at the root. Otherwise, use parent 
+         * functionality.
+         */
+        @Override
+        public Object[] getChildren(Object element) {
+            if (element instanceof IJavaModel) {
+                return BaseProjectHelper.getAndroidProjects((IJavaModel) element);
+            }
+            return super.getChildren(element);
+        }
+    }
 }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitPropertyTester.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitPropertyTester.java
new file mode 100644
index 0000000..eadafee
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitPropertyTester.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.launch.junit;
+
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IMember;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.junit.util.TestSearchEngine;
+
+/**
+ * A {@link PropertyTester} that checks if selected elements can be run as Android
+ * JUnit tests.
+ * <p/>
+ * Based on org.eclipse.jdt.internal.junit.JUnitPropertyTester. The only substantial difference in
+ * this implementation is source folders cannot be run as Android JUnit.
+ */
+@SuppressWarnings("restriction")
+public class AndroidJUnitPropertyTester extends PropertyTester {
+    private static final String PROPERTY_IS_TEST = "isTest";  //$NON-NLS-1$
+    
+    private static final String PROPERTY_CAN_LAUNCH_AS_JUNIT_TEST = "canLaunchAsJUnit"; //$NON-NLS-1$
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jdt.internal.corext.refactoring.participants.properties.IPropertyEvaluator#test(java.lang.Object, java.lang.String, java.lang.String)
+     */
+    public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+        if (!(receiver instanceof IAdaptable)) {
+            final String elementName = (receiver == null ? "null" : //$NON-NLS-1$
+                receiver.getClass().getName());
+            throw new IllegalArgumentException(
+                    String.format("Element must be of type IAdaptable, is %s", //$NON-NLS-1$
+                            elementName));
+        }
+
+        IJavaElement element;
+        if (receiver instanceof IJavaElement) {
+            element = (IJavaElement) receiver;
+        } else if (receiver instanceof IResource) {
+            element = JavaCore.create((IResource) receiver);
+            if (element == null) {
+                return false;
+            }
+        } else { // is IAdaptable
+            element= (IJavaElement) ((IAdaptable) receiver).getAdapter(IJavaElement.class);
+            if (element == null) {
+                IResource resource = (IResource) ((IAdaptable) receiver).getAdapter(
+                        IResource.class);
+                element = JavaCore.create(resource);
+                if (element == null) {
+                    return false;
+                }
+            }
+        }
+        if (PROPERTY_IS_TEST.equals(property)) { 
+            return isJUnitTest(element);
+        } else if (PROPERTY_CAN_LAUNCH_AS_JUNIT_TEST.equals(property)) {
+            return canLaunchAsJUnitTest(element);
+        }
+        throw new IllegalArgumentException(
+                String.format("Unknown test property '%s'", property)); //$NON-NLS-1$
+    }
+    
+    private boolean canLaunchAsJUnitTest(IJavaElement element) {
+        try {
+            switch (element.getElementType()) {
+                case IJavaElement.JAVA_PROJECT:
+                    return true; // can run, let JDT detect if there are tests
+                case IJavaElement.PACKAGE_FRAGMENT_ROOT:
+                    return false; // not supported by Android test runner
+                case IJavaElement.PACKAGE_FRAGMENT:
+                    return ((IPackageFragment) element).hasChildren(); 
+                case IJavaElement.COMPILATION_UNIT:
+                case IJavaElement.CLASS_FILE:
+                case IJavaElement.TYPE:
+                case IJavaElement.METHOD:
+                    return isJUnitTest(element);
+                default:
+                    return false;
+            }
+        } catch (JavaModelException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Return whether the target resource is a JUnit test.
+     */
+    private boolean isJUnitTest(IJavaElement element) {
+        try {
+            IType testType = null;
+            if (element instanceof ICompilationUnit) {
+                testType = (((ICompilationUnit) element)).findPrimaryType();
+            } else if (element instanceof IClassFile) {
+                testType = (((IClassFile) element)).getType();
+            } else if (element instanceof IType) {
+                testType = (IType) element;
+            } else if (element instanceof IMember) {
+                testType = ((IMember) element).getDeclaringType();
+            }
+            if (testType != null && testType.exists()) {
+                return TestSearchEngine.isTestOrTestSuite(testType);
+            }
+        } catch (CoreException e) {
+            // ignore, return false
+        }
+        return false;
+    }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java
index 89cad97..8ac80ca 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/AndroidJUnitLaunchInfo.java
@@ -15,35 +15,38 @@
  */
 package com.android.ide.eclipse.adt.launch.junit.runtime;
 
-import org.eclipse.core.resources.IProject;
-
 import com.android.ddmlib.IDevice;
 
+import org.eclipse.core.resources.IProject;
+import org.eclipse.debug.core.ILaunch;
+
 /**
  * Contains info about Android JUnit launch
  */
 public class AndroidJUnitLaunchInfo {
     private final IProject mProject;
-    private final String mTestPackage;
+    private final String mAppPackage;
     private final String mRunner;
-    private final boolean mDebugMode;
-    private final IDevice mDevice;
-    
-    public AndroidJUnitLaunchInfo(IProject project, String testPackage, String runner,
-            boolean debugMode, IDevice device) {
+
+    private boolean mDebugMode = false;
+    private IDevice mDevice = null;
+    private String mTestPackage = null;
+    private String mTestClass = null;
+    private String mTestMethod = null;
+    private ILaunch mLaunch = null;
+
+    public AndroidJUnitLaunchInfo(IProject project, String appPackage, String runner) {
         mProject = project;
-        mTestPackage = testPackage;
+        mAppPackage = appPackage;
         mRunner = runner;
-        mDebugMode = debugMode;
-        mDevice = device;
     }
-    
+
     public IProject getProject() {
         return mProject;
     }
 
-    public String getTestPackage() {
-        return mTestPackage;
+    public String getAppPackage() {
+        return mAppPackage;
     }
 
     public String getRunner() {
@@ -53,8 +56,80 @@
     public boolean isDebugMode() {
         return mDebugMode;
     }
+    
+    public void setDebugMode(boolean debugMode) {
+        mDebugMode = debugMode;
+    }
 
     public IDevice getDevice() {
         return mDevice;
     }
+
+    public void setDevice(IDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * Specify to run all tests within given package.
+     *
+     * @param testPackage fully qualified java package
+     */
+    public void setTestPackage(String testPackage) {
+        mTestPackage = testPackage;
+    }
+
+    /**
+     * Return the package of tests to run.
+     *
+     * @return fully qualified java package. <code>null</code> if not specified.
+     */
+    public String getTestPackage() {
+        return mTestPackage;       
+    }
+
+    /**
+     * Sets the test class to run.
+     * 
+     * @param testClass fully qualfied test class to run
+     *    Expected format: x.y.x.testclass
+     */
+    public void setTestClass(String testClass) {
+        mTestClass = testClass;
+    }
+
+    /** 
+     * Returns the test class to run.
+     *
+     * @return fully qualfied test class to run.
+     *   <code>null</code> if not specified.
+     */
+    public String getTestClass() {
+        return mTestClass;
+    }
+    
+    /**
+     * Sets the test method to run. testClass must also be set. 
+     * 
+     * @param testMethod test method to run
+     */
+    public void setTestMethod(String testMethod) {
+        mTestMethod = testMethod;
+    }
+
+    /** 
+     * Returns the test method to run.
+     *
+     * @return test method to run. <code>null</code> if not specified.
+     */
+    public String getTestMethod() {
+        return mTestMethod;
+    }
+
+    public ILaunch getLaunch() {
+        return mLaunch;
+    }
+
+    public void setLaunch(ILaunch launch) {
+        mLaunch = launch;
+    }
 }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
index 0a6a3da..962d761 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
@@ -69,8 +69,9 @@
      * executing the tests,  and send it back to JDT JUnit. The second is the actual test execution,
      * whose results will be communicated back in real-time to JDT JUnit.
      * 
-     * @param testClassNames array of fully qualified test class names to execute. Cannot be empty.
-     * @param testName test to execute. If null, will be ignored.
+     * @param testClassNames ignored - the AndroidJUnitLaunchInfo will be used to determine which
+     *     tests to run.
+     * @param testName ignored
      * @param execution used to report test progress
      */
     @Override
@@ -78,16 +79,21 @@
         // hold onto this execution reference so it can be used to report test progress
         mExecution = execution;
         
-        RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getTestPackage(), 
+        RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getAppPackage(), 
                 mLaunchInfo.getRunner(), mLaunchInfo.getDevice()); 
 
-        if (testClassNames != null && testClassNames.length > 0) {
-            if (testName != null) {
-                runner.setMethodName(testClassNames[0], testName);
-            } else {
-                runner.setClassNames(testClassNames);
-            }
+        if (mLaunchInfo.getTestClass() != null) {
+            if (mLaunchInfo.getTestMethod() != null) {
+                runner.setMethodName(mLaunchInfo.getTestClass(), mLaunchInfo.getTestMethod());
+            } else {    
+                runner.setClassName(mLaunchInfo.getTestClass());
+            }    
         }
+
+        if (mLaunchInfo.getTestPackage() != null) {
+            runner.setTestPackageName(mLaunchInfo.getTestPackage());
+        }
+
         // set log only to first collect test case info, so Eclipse has correct test case count/
         // tree info
         runner.setLogOnly(true);