AI 144419: am: CL 144382 am: CL 144366 Activity Launcher filters out unlauncheable activity (bug #1736754)
  Activities that do not have an action, or that are set to not be exported cannot be launched from 'am start...' so they should not be considered when finding an activity to launch.
  Original author: xav
  Merged from: //branches/cupcake/...
  Original author: android-build

Automated import of CL 144419
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
index 655c038..7ee3def 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
@@ -1078,7 +1078,7 @@
 
     /**
      * Performs the installation of an application whose package has been uploaded on the device.
-     * <p/>Before doing it, if the application is already running on the device, it is killed. 
+     *
      * @param launchInfo the {@link DelayedLaunchInfo}.
      * @param remotePath the path of the application package in the device tmp folder.
      * @param device the device on which to install the application.
@@ -1088,12 +1088,6 @@
      */
     private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath,
             final IDevice device, boolean reinstall) throws IOException {
-        // kill running application
-        Client application = device.getClient(launchInfo.getPackageName());
-        if (application != null) {
-            application.kill();
-        }
-        
         InstallReceiver receiver = new InstallReceiver();
         try {
             String cmd = String.format(
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
index 9f12b16..4fa270e 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
@@ -23,6 +23,7 @@
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.AndroidManifestParser;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
+import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
@@ -247,7 +248,7 @@
             activityName = getActivityName(configuration);
     
             // Get the full activity list and make sure the one we got matches.
-            String[] activities = manifestParser.getActivities();
+            Activity[] activities = manifestParser.getActivities();
     
             // first we check that there are, in fact, activities.
             if (activities.length == 0) {
@@ -261,8 +262,11 @@
                 // if the activity we got is null, we look for the default one.
                 AdtPlugin.printErrorToConsole(project,
                         "No activity specified! Getting the launcher activity.");
-                activityName = manifestParser.getLauncherActivity();
-                
+                Activity launcherActivity = manifestParser.getLauncherActivity();
+                if (launcherActivity != null) {
+                    activityName = launcherActivity.getName();
+                }
+
                 // if there's no default activity. We revert to a sync-only launch.
                 if (activityName == null) {
                     revertToNoActionLaunch(project, config);
@@ -271,8 +275,8 @@
     
                 // check the one we got from the config matches any from the list
                 boolean match = false;
-                for (String a : activities) {
-                    if (a != null && a.equals(activityName)) {
+                for (Activity a : activities) {
+                    if (a != null && a.getName().equals(activityName)) {
                         match = true;
                         break;
                     }
@@ -282,7 +286,10 @@
                 if (match == false) {
                     AdtPlugin.printErrorToConsole(project,
                             "The specified activity does not exist! Getting the launcher activity.");
-                    activityName = manifestParser.getLauncherActivity();
+                    Activity launcherActivity = manifestParser.getLauncherActivity();
+                    if (launcherActivity != null) {
+                        activityName = launcherActivity.getName();
+                    }
             
                     // if there's no default activity. We revert to a sync-only launch.
                     if (activityName == null) {
@@ -291,7 +298,10 @@
                 }
             }
         } else if (config.mLaunchAction == ACTION_DEFAULT) {
-            activityName = manifestParser.getLauncherActivity();
+            Activity launcherActivity = manifestParser.getLauncherActivity();
+            if (launcherActivity != null) {
+                activityName = launcherActivity.getName();
+            }
             
             // if there's no default activity. We revert to a sync-only launch.
             if (activityName == null) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
index 91bd21c..a32c2ee 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
@@ -20,6 +20,7 @@
 import com.android.ide.eclipse.common.project.AndroidManifestParser;
 import com.android.ide.eclipse.common.project.BaseProjectHelper;
 import com.android.ide.eclipse.common.project.ProjectChooserHelper;
+import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -50,6 +51,8 @@
 import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Text;
 
+import java.util.ArrayList;
+
 /**
  * Class for the main launch configuration tab.
  */
@@ -66,7 +69,7 @@
     private Button mProjButton;
 
     private Combo mActivityCombo;
-    private String[] mActivities;
+    private final ArrayList<Activity> mActivities = new ArrayList<Activity>();
 
     private WidgetListener mListener = new WidgetListener();
 
@@ -214,8 +217,9 @@
         
         // add the activity
         int selection = mActivityCombo.getSelectionIndex();
-        if (mActivities != null && selection >=0 && selection < mActivities.length) {
-            configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, mActivities[selection]);
+        if (mActivities != null && selection >=0 && selection < mActivities.size()) {
+            configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY,
+                    mActivities.get(selection).getName());
         }
         
         // link the project and the launch config.
@@ -349,11 +353,11 @@
             mActivityCombo.setEnabled(true);
             if (activityName == null || activityName.equals(EMPTY_STRING)) {
                 mActivityCombo.clearSelection();
-            } else if (mActivities != null && mActivities.length > 0) {
+            } else if (mActivities != null && mActivities.size() > 0) {
                 // look for the name of the activity in the combo.
                 boolean found = false;
-                for (int i = 0 ; i < mActivities.length ; i++) {
-                    if (activityName.equals(mActivities[i])) {
+                for (int i = 0 ; i < mActivities.size() ; i++) {
+                    if (activityName.equals(mActivities.get(i).getName())) {
                         found = true;
                         mActivityCombo.select(i);
                         break;
@@ -404,17 +408,22 @@
                         BaseProjectHelper.getJavaProject(project), null /* errorListener */,
                         true /* gatherData */, false /* markErrors */);
                 if (manifestParser != null) {
-                    mActivities = manifestParser.getActivities();
-    
+                    Activity[] activities = manifestParser.getActivities();
+
+                    mActivities.clear();
                     mActivityCombo.removeAll();
-    
-                    if (mActivities.length > 0) {
+                    
+                    for (Activity activity : activities) {
+                        if (activity.getExported() && activity.hasAction()) {
+                            mActivities.add(activity);
+                            mActivityCombo.add(activity.getName());
+                        }
+                    }
+                    
+                    if (mActivities.size() > 0) {
                         if (mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY) {
                             mActivityCombo.setEnabled(true);
                         }
-                        for (String s : mActivities) {
-                            mActivityCombo.add(s);
-                        }
                     } else {
                         mActivityCombo.setEnabled(false);
                     }
@@ -435,7 +444,7 @@
         // if we reach this point, either project is null, or we got an exception during
         // the parsing. In either case, we empty the activity list.
         mActivityCombo.removeAll();
-        mActivities = null;
+        mActivities.clear();
     }
     
     /**
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
index e26b31c..6e8ff47 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
@@ -27,6 +27,7 @@
 import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
 import com.android.ide.eclipse.common.AndroidConstants;
 import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.project.ProjectProperties;
@@ -859,6 +860,7 @@
         }
         
         String packageName = null;
+        Activity activity = null;
         String activityName = null;
         int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
         try {
@@ -866,11 +868,11 @@
             minSdkVersion = manifestData.getApiLevelRequirement();
 
             // try to get the first launcher activity. If none, just take the first activity.
-            activityName = manifestData.getLauncherActivity();
-            if (activityName == null) {
-                String[] activities = manifestData.getActivities();
+            activity = manifestData.getLauncherActivity();
+            if (activity == null) {
+                Activity[] activities = manifestData.getActivities();
                 if (activities != null && activities.length > 0) {
-                    activityName = activities[0];
+                    activity = activities[0];
                 }
             }
         } catch (Exception e) {
@@ -881,7 +883,10 @@
             mPackageNameField.setText(packageName);
         }
         
-        activityName = AndroidManifestParser.extractActivityName(activityName, packageName);
+        if (activity != null) {
+            activityName = AndroidManifestParser.extractActivityName(activity.getName(),
+                    packageName);
+        }
 
         if (activityName != null && activityName.length() > 0) {
             mInternalActivityNameUpdate = true;
@@ -1136,7 +1141,7 @@
                         MSG_ERROR);
             }
 
-            String[] activities = manifestData.getActivities();
+            Activity[] activities = manifestData.getActivities();
             if (activities == null || activities.length == 0) {
                 // This is acceptable now as long as no activity needs to be created
                 if (isCreateActivity()) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
index f853ada..69982fc 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
@@ -53,6 +53,7 @@
     private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$
     private final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
     private final static String ATTRIBUTE_TARGET_PACKAGE = "targetPackage"; //$NON-NLS-1$
+    private final static String ATTRIBUTE_EXPORTED = "exported"; //$NON-NLS-1$
     private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
     private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
     private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$
@@ -84,7 +85,7 @@
         private final String mName;
         private final String mTargetPackage;
         
-        public Instrumentation(String name, String targetPackage) {
+        Instrumentation(String name, String targetPackage) {
             mName = name;
             mTargetPackage = targetPackage;
         }
@@ -105,6 +106,60 @@
     }
     
     /**
+     * Activity info obtained from the manifest.
+     */
+    public static class Activity {
+        private final String mName;
+        private final boolean mExported;
+        private boolean mHasAction = false;
+        private boolean mHasMainAction = false;
+        private boolean mHasLauncherCategory = false;
+        
+        public Activity(String name, boolean exported) {
+            mName = name;
+            mExported = exported;
+        }
+        
+        public String getName() {
+            return mName;
+        }
+        
+        public boolean getExported() {
+            return mExported;
+        }
+        
+        public boolean hasAction() {
+            return mHasAction;
+        }
+        
+        public boolean isHomeActivity() {
+            return mHasMainAction && mHasLauncherCategory;
+        }
+        
+        void setHasAction(boolean hasAction) {
+            mHasAction = hasAction;
+        }
+        
+        /** If the activity doesn't yet have a filter set for the launcher, this resets both
+         * flags. This is to handle multiple intent-filters where one could have the valid
+         * action, and another one of the valid category.
+         */
+        void resetIntentFilter() {
+            if (isHomeActivity() == false) {
+                mHasMainAction = mHasLauncherCategory = false;
+            }
+        }
+        
+        void setHasMainAction(boolean hasMainAction) {
+            mHasMainAction = hasMainAction;
+        }
+        
+        void setHasLauncherCategory(boolean hasLauncherCategory) {
+            mHasLauncherCategory = hasLauncherCategory;
+        }
+    }
+    
+    /**
      * XML error & data handler used when parsing the AndroidManifest.xml file.
      * <p/>
      * This serves both as an {@link XmlErrorHandler} to report errors and as a data repository
@@ -117,9 +172,9 @@
         /** Application package */
         private String mPackage;
         /** List of all activities */
-        private final ArrayList<String> mActivities = new ArrayList<String>();
+        private final ArrayList<Activity> mActivities = new ArrayList<Activity>();
         /** Launcher activity */
-        private String mLauncherActivity = null;
+        private Activity mLauncherActivity = null;
         /** list of process names declared by the manifest */
         private Set<String> mProcesses = null;
         /** debuggable attribute value. If null, the attribute is not present. */
@@ -139,9 +194,7 @@
         private boolean mMarkErrors = false;
         private int mCurrentLevel = 0;
         private int mValidLevel = 0;
-        private boolean mFoundMainAction = false;
-        private boolean mFoundLauncherCategory = false;
-        private String mCurrentActivity = null;
+        private Activity mCurrentActivity = null;
         private Locator mLocator;
         
         /**
@@ -173,8 +226,8 @@
          * Returns the list of activities found in the manifest.
          * @return An array of fully qualified class names, or empty if no activity were found.
          */
-        String[] getActivities() {
-            return mActivities.toArray(new String[mActivities.size()]);
+        Activity[] getActivities() {
+            return mActivities.toArray(new Activity[mActivities.size()]);
         }
         
         /**
@@ -182,7 +235,7 @@
          * up in the HOME screen.  
          * @return the fully qualified name of a HOME activity or null if none were found. 
          */
-        String getLauncherActivity() {
+        Activity getLauncherActivity() {
             return mLauncherActivity;
         }
         
@@ -314,27 +367,26 @@
                         case LEVEL_INTENT_FILTER:
                             // only process this level if we are in an activity
                             if (mCurrentActivity != null && NODE_INTENT.equals(localName)) {
-                                // if we're at the intent level, lets reset some flag to
-                                // be used when parsing the children
-                                mFoundMainAction = false;
-                                mFoundLauncherCategory = false;
+                                mCurrentActivity.resetIntentFilter();
                                 mValidLevel++;
                             }
                             break;
                         case LEVEL_CATEGORY:
-                            if (mCurrentActivity != null && mLauncherActivity == null) {
+                            if (mCurrentActivity != null) {
                                 if (NODE_ACTION.equals(localName)) {
                                     // get the name attribute
-                                    if (ACTION_MAIN.equals(
-                                            getAttributeValue(attributes, ATTRIBUTE_NAME,
-                                                    true /* hasNamespace */))) {
-                                        mFoundMainAction = true;
+                                    String action = getAttributeValue(attributes, ATTRIBUTE_NAME,
+                                            true /* hasNamespace */);
+                                    if (action != null) {
+                                        mCurrentActivity.setHasAction(true);
+                                        mCurrentActivity.setHasMainAction(
+                                                ACTION_MAIN.equals(action));
                                     }
                                 } else if (NODE_CATEGORY.equals(localName)) {
-                                    if (CATEGORY_LAUNCHER.equals(
-                                            getAttributeValue(attributes, ATTRIBUTE_NAME,
-                                                    true /* hasNamespace */))) {
-                                        mFoundLauncherCategory = true;
+                                    String category = getAttributeValue(attributes, ATTRIBUTE_NAME,
+                                            true /* hasNamespace */);
+                                    if (CATEGORY_LAUNCHER.equals(category)) {
+                                        mCurrentActivity.setHasLauncherCategory(true);
                                     }
                                 }
                                 
@@ -378,8 +430,7 @@
                         case LEVEL_INTENT_FILTER:
                             // if we found both a main action and a launcher category, this is our
                             // launcher activity!
-                            if (mCurrentActivity != null &&
-                                    mFoundMainAction && mFoundLauncherCategory) {
+                            if (mCurrentActivity != null && mCurrentActivity.isHomeActivity()) {
                                 mLauncherActivity = mCurrentActivity;
                             }
                             break;
@@ -432,17 +483,23 @@
             String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME,
                     true /* hasNamespace */);
             if (activityName != null) {
-                mCurrentActivity = combinePackageAndClassName(mPackage, activityName);
+                activityName = combinePackageAndClassName(mPackage, activityName);
+                
+                // get the exported flag.
+                String exportedStr = getAttributeValue(attributes, ATTRIBUTE_EXPORTED, true);
+                boolean exported = exportedStr == null ||
+                        exportedStr.toLowerCase().equals("true"); // $NON-NLS-1$
+                mCurrentActivity = new Activity(activityName, exported);
                 mActivities.add(mCurrentActivity);
                 
                 if (mMarkErrors) {
-                    checkClass(mCurrentActivity, AndroidConstants.CLASS_ACTIVITY,
+                    checkClass(activityName, AndroidConstants.CLASS_ACTIVITY,
                             true /* testVisibility */);
                 }
             } else {
                 // no activity found! Aapt will output an error,
                 // so we don't have to do anything
-                mCurrentActivity = activityName;
+                mCurrentActivity = null;
             }
             
             String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
@@ -570,8 +627,8 @@
     private static SAXParserFactory sParserFactory;
     
     private final String mJavaPackage;
-    private final String[] mActivities;
-    private final String mLauncherActivity;
+    private final Activity[] mActivities;
+    private final Activity mLauncherActivity;
     private final String[] mProcesses;
     private final Boolean mDebuggable;
     private final int mApiLevelRequirement;
@@ -811,18 +868,18 @@
 
     /** 
      * Returns the list of activities found in the manifest.
-     * @return An array of fully qualified class names, or empty if no activity were found.
+     * @return An array of {@link Activity}, or empty if no activity were found.
      */
-    public String[] getActivities() {
+    public Activity[] getActivities() {
         return mActivities;
     }
 
     /**
      * Returns the name of one activity found in the manifest, that is configured to show
      * up in the HOME screen.  
-     * @return the fully qualified name of a HOME activity or null if none were found. 
+     * @return The {@link Activity} representing a HOME activity or null if none were found. 
      */
-    public String getLauncherActivity() {
+    public Activity getLauncherActivity() {
         return mLauncherActivity;
     }
     
@@ -880,8 +937,8 @@
      * @param instrumentations the list of instrumentations parsed from the manifest.
      * @param libraries the list of libraries in use parsed from the manifest.
      */
-    private AndroidManifestParser(String javaPackage, String[] activities,
-            String launcherActivity, String[] processes, Boolean debuggable,
+    private AndroidManifestParser(String javaPackage, Activity[] activities,
+            Activity launcherActivity, String[] processes, Boolean debuggable,
             int apiLevelRequirement, Instrumentation[] instrumentations, String[] libraries) {
         mJavaPackage = javaPackage;
         mActivities = activities;