From 623cb646eba04513ce61ce0a0c8e601f7adb8fe2 Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet <> Date: Thu, 2 Apr 2009 23:52:36 -0700 Subject: [PATCH] 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 --- .../adt/launch/AndroidLaunchController.java | 8 +- .../eclipse/adt/launch/LaunchConfigDelegate.java | 24 ++-- .../eclipse/adt/launch/MainLaunchConfigTab.java | 37 +++--- .../wizards/newproject/NewProjectCreationPage.java | 17 ++- .../common/project/AndroidManifestParser.java | 127 +++++++++++++++------ 5 files changed, 144 insertions(+), 69 deletions(-) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java index 655c038e1..7ee3def57 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java @@ -1078,7 +1078,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener /** * Performs the installation of an application whose package has been uploaded on the device. - *

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 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener */ 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java index 9f12b16e4..4fa270e4e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java +++ b/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.adt.project.ProjectHelper; 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 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { 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 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { // 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 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { // 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 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { 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 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { } } } 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java index 91bd21cb7..a32c2ee0e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java +++ b/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.adt.AdtPlugin; 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.Composite; 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 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { private Button mProjButton; private Combo mActivityCombo; - private String[] mActivities; + private final ArrayList mActivities = new ArrayList(); private WidgetListener mListener = new WidgetListener(); @@ -214,8 +217,9 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { // 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 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { 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 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { 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 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { // 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java index e26b31cc3..6e8ff47fe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java +++ b/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; 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 @@ public class NewProjectCreationPage extends WizardPage { } String packageName = null; + Activity activity = null; String activityName = null; int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK; try { @@ -866,11 +868,11 @@ public class NewProjectCreationPage extends WizardPage { 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 @@ public class NewProjectCreationPage extends WizardPage { 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 @@ public class NewProjectCreationPage extends WizardPage { 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java index f853adacb..69982fc66 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java @@ -53,6 +53,7 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { } /** + * 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. *

* This serves both as an {@link XmlErrorHandler} to report errors and as a data repository @@ -117,9 +172,9 @@ public class AndroidManifestParser { /** Application package */ private String mPackage; /** List of all activities */ - private final ArrayList mActivities = new ArrayList(); + private final ArrayList mActivities = new ArrayList(); /** Launcher activity */ - private String mLauncherActivity = null; + private Activity mLauncherActivity = null; /** list of process names declared by the manifest */ private Set mProcesses = null; /** debuggable attribute value. If null, the attribute is not present. */ @@ -139,9 +194,7 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { * 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 @@ public class AndroidManifestParser { * 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 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { 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 @@ public class AndroidManifestParser { /** * 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 @@ public class AndroidManifestParser { * @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; -- 2.11.0