| /* |
| * Copyright (C) 2007 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.internal.launch; |
| |
| import com.android.ddmlib.AndroidDebugBridge; |
| import com.android.ide.common.xml.ManifestData; |
| import com.android.ide.common.xml.ManifestData.Activity; |
| import com.android.ide.eclipse.adt.AdtConstants; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode; |
| import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; |
| import com.android.ide.eclipse.adt.internal.project.ProjectHelper; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.model.LaunchConfigurationDelegate; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; |
| |
| /** |
| * Implementation of an eclipse LauncConfigurationDelegate to launch android |
| * application in debug. |
| */ |
| public class LaunchConfigDelegate extends LaunchConfigurationDelegate { |
| final static int INVALID_DEBUG_PORT = -1; |
| |
| public final static String ANDROID_LAUNCH_TYPE_ID = |
| "com.android.ide.eclipse.adt.debug.LaunchConfigType"; //$NON-NLS-1$ |
| |
| /** Target mode parameters: true is automatic, false is manual */ |
| public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$ |
| public static final TargetMode DEFAULT_TARGET_MODE = TargetMode.AUTO; |
| |
| /** Flag indicating whether the last used device should be used for future launches. */ |
| public static final String ATTR_REUSE_LAST_USED_DEVICE = |
| AdtPlugin.PLUGIN_ID + ".reuse.last.used.device"; //$NON-NLS-1$ |
| |
| /** Device on which the last launch happened. */ |
| public static final String ATTR_LAST_USED_DEVICE = |
| AdtPlugin.PLUGIN_ID + ".last.used.device"; //$NON-NLS-1$ |
| |
| /** |
| * Launch action: |
| * <ul> |
| * <li>0: launch default activity</li> |
| * <li>1: launch specified activity. See {@link #ATTR_ACTIVITY}</li> |
| * <li>2: Do Nothing</li> |
| * </ul> |
| */ |
| public final static String ATTR_LAUNCH_ACTION = AdtPlugin.PLUGIN_ID + ".action"; //$NON-NLS-1$ |
| |
| /** Default launch action. This launches the activity that is setup to be found in the HOME |
| * screen. |
| */ |
| public final static int ACTION_DEFAULT = 0; |
| /** Launch action starting a specific activity. */ |
| public final static int ACTION_ACTIVITY = 1; |
| /** Launch action that does nothing. */ |
| public final static int ACTION_DO_NOTHING = 2; |
| /** Default launch action value. */ |
| public final static int DEFAULT_LAUNCH_ACTION = ACTION_DEFAULT; |
| |
| /** |
| * Activity to be launched if {@link #ATTR_LAUNCH_ACTION} is 1 |
| */ |
| public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ |
| |
| public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$ |
| |
| public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ |
| |
| /** |
| * Index of the default network speed setting for the emulator.<br> |
| * Get the emulator option with <code>EmulatorConfigTab.getSpeed(index)</code> |
| */ |
| public static final int DEFAULT_SPEED = 0; |
| |
| public static final String ATTR_DELAY = AdtPlugin.PLUGIN_ID + ".delay"; //$NON-NLS-1$ |
| |
| /** |
| * Index of the default network latency setting for the emulator.<br> |
| * Get the emulator option with <code>EmulatorConfigTab.getDelay(index)</code> |
| */ |
| public static final int DEFAULT_DELAY = 0; |
| |
| public static final String ATTR_COMMANDLINE = AdtPlugin.PLUGIN_ID + ".commandline"; //$NON-NLS-1$ |
| |
| public static final String ATTR_WIPE_DATA = AdtPlugin.PLUGIN_ID + ".wipedata"; //$NON-NLS-1$ |
| public static final boolean DEFAULT_WIPE_DATA = false; |
| |
| public static final String ATTR_NO_BOOT_ANIM = AdtPlugin.PLUGIN_ID + ".nobootanim"; //$NON-NLS-1$ |
| public static final boolean DEFAULT_NO_BOOT_ANIM = false; |
| |
| public static final String ATTR_DEBUG_PORT = |
| AdtPlugin.PLUGIN_ID + ".debugPort"; //$NON-NLS-1$ |
| |
| @Override |
| public void launch(ILaunchConfiguration configuration, String mode, |
| ILaunch launch, IProgressMonitor monitor) throws CoreException { |
| // We need to check if it's a standard launch or if it's a launch |
| // to debug an application already running. |
| int debugPort = AndroidLaunchController.getPortForConfig(configuration); |
| |
| // get the project |
| IProject project = getProject(configuration); |
| |
| // first we make sure the launch is of the proper type |
| AndroidLaunch androidLaunch = null; |
| if (launch instanceof AndroidLaunch) { |
| androidLaunch = (AndroidLaunch)launch; |
| } else { |
| // wrong type, not sure how we got there, but we don't do |
| // anything else |
| AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!"); |
| return; |
| } |
| |
| // if we have a valid debug port, this means we're debugging an app |
| // that's already launched. |
| if (debugPort != INVALID_DEBUG_PORT) { |
| AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor); |
| return; |
| } |
| |
| if (project == null) { |
| AdtPlugin.printErrorToConsole("Couldn't get project object!"); |
| androidLaunch.stopLaunch(); |
| return; |
| } |
| |
| // make sure the project and its dependencies are built |
| // and PostCompilerBuilder runs. |
| // This is a synchronous call which returns when the |
| // build is done. |
| ProjectHelper.doFullIncrementalDebugBuild(project, monitor); |
| |
| // check if the project has errors, and abort in this case. |
| if (ProjectHelper.hasError(project, true)) { |
| AdtPlugin.displayError("Android Launch", |
| "Your project contains error(s), please fix them before running your application."); |
| return; |
| } |
| |
| AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$ |
| AdtPlugin.printToConsole(project, "Android Launch!"); |
| |
| // check if the project is using the proper sdk. |
| // if that throws an exception, we simply let it propagate to the caller. |
| if (checkAndroidProject(project) == false) { |
| AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!"); |
| androidLaunch.stopLaunch(); |
| return; |
| } |
| |
| // Check adb status and abort if needed. |
| AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); |
| if (bridge == null || bridge.isConnected() == false) { |
| try { |
| int connections = -1; |
| int restarts = -1; |
| if (bridge != null) { |
| connections = bridge.getConnectionAttemptCount(); |
| restarts = bridge.getRestartAttemptCount(); |
| } |
| |
| // if we get -1, the device monitor is not even setup (anymore?). |
| // We need to ask the user to restart eclipse. |
| // This shouldn't happen, but it's better to let the user know in case it does. |
| if (connections == -1 || restarts == -1) { |
| AdtPlugin.printErrorToConsole(project, |
| "The connection to adb is down, and a severe error has occured.", |
| "You must restart adb and Eclipse.", |
| String.format( |
| "Please ensure that adb is correctly located at '%1$s' and can be executed.", |
| AdtPlugin.getOsAbsoluteAdb())); |
| return; |
| } |
| |
| if (restarts == 0) { |
| AdtPlugin.printErrorToConsole(project, |
| "Connection with adb was interrupted.", |
| String.format("%1$s attempts have been made to reconnect.", connections), |
| "You may want to manually restart adb from the Devices view."); |
| } else { |
| AdtPlugin.printErrorToConsole(project, |
| "Connection with adb was interrupted, and attempts to reconnect have failed.", |
| String.format("%1$s attempts have been made to restart adb.", restarts), |
| "You may want to manually restart adb from the Devices view."); |
| |
| } |
| return; |
| } finally { |
| androidLaunch.stopLaunch(); |
| } |
| } |
| |
| // since adb is working, we let the user know |
| // TODO have a verbose mode for launch with more info (or some of the less useful info we now have). |
| AdtPlugin.printToConsole(project, "adb is running normally."); |
| |
| // make a config class |
| AndroidLaunchConfiguration config = new AndroidLaunchConfiguration(); |
| |
| // fill it with the config coming from the ILaunchConfiguration object |
| config.set(configuration); |
| |
| // get the launch controller singleton |
| AndroidLaunchController controller = AndroidLaunchController.getInstance(); |
| |
| // get the application package |
| IFile applicationPackage = ProjectHelper.getApplicationPackage(project); |
| if (applicationPackage == null) { |
| androidLaunch.stopLaunch(); |
| return; |
| } |
| |
| // we need some information from the manifest |
| ManifestData manifestData = AndroidManifestHelper.parseForData(project); |
| |
| if (manifestData == null) { |
| AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!"); |
| androidLaunch.stopLaunch(); |
| return; |
| } |
| |
| doLaunch(configuration, mode, monitor, project, androidLaunch, config, controller, |
| applicationPackage, manifestData); |
| } |
| |
| protected void doLaunch(ILaunchConfiguration configuration, String mode, |
| IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, |
| AndroidLaunchConfiguration config, AndroidLaunchController controller, |
| IFile applicationPackage, ManifestData manifestData) { |
| |
| String activityName = null; |
| |
| if (config.mLaunchAction == ACTION_ACTIVITY) { |
| // Get the activity name defined in the config |
| activityName = getActivityName(configuration); |
| |
| // Get the full activity list and make sure the one we got matches. |
| Activity[] activities = manifestData.getActivities(); |
| |
| // first we check that there are, in fact, activities. |
| if (activities.length == 0) { |
| // if the activities list is null, then the manifest is empty |
| // and we can't launch the app. We'll revert to a sync-only launch |
| AdtPlugin.printErrorToConsole(project, |
| "The Manifest defines no activity!", |
| "The launch will only sync the application package on the device!"); |
| config.mLaunchAction = ACTION_DO_NOTHING; |
| } else if (activityName == null) { |
| // if the activity we got is null, we look for the default one. |
| AdtPlugin.printErrorToConsole(project, |
| "No activity specified! Getting the launcher activity."); |
| Activity launcherActivity = manifestData.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); |
| } |
| } else { |
| |
| // check the one we got from the config matches any from the list |
| boolean match = false; |
| for (Activity a : activities) { |
| if (a != null && a.getName().equals(activityName)) { |
| match = true; |
| break; |
| } |
| } |
| |
| // if we didn't find a match, we revert to the default activity if any. |
| if (match == false) { |
| AdtPlugin.printErrorToConsole(project, |
| "The specified activity does not exist! Getting the launcher activity."); |
| Activity launcherActivity = manifestData.getLauncherActivity(); |
| if (launcherActivity != null) { |
| activityName = launcherActivity.getName(); |
| } else { |
| // if there's no default activity. We revert to a sync-only launch. |
| revertToNoActionLaunch(project, config); |
| } |
| } |
| } |
| } else if (config.mLaunchAction == ACTION_DEFAULT) { |
| Activity launcherActivity = manifestData.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); |
| } |
| } |
| |
| IAndroidLaunchAction launchAction = null; |
| if (config.mLaunchAction == ACTION_DO_NOTHING || activityName == null) { |
| launchAction = new EmptyLaunchAction(); |
| } else { |
| launchAction = new ActivityLaunchAction(activityName, controller); |
| } |
| |
| // everything seems fine, we ask the launch controller to handle |
| // the rest |
| controller.launch(project, mode, applicationPackage,manifestData.getPackage(), |
| manifestData.getPackage(), manifestData.getDebuggable(), |
| manifestData.getMinSdkVersionString(), launchAction, config, androidLaunch, |
| monitor); |
| } |
| |
| @Override |
| public boolean buildForLaunch(ILaunchConfiguration configuration, |
| String mode, IProgressMonitor monitor) throws CoreException { |
| // if this returns true, this forces a full workspace rebuild which is not |
| // what we want. |
| // Instead in the #launch method, we'll rebuild only the launching project. |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @throws CoreException |
| */ |
| @Override |
| public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) |
| throws CoreException { |
| return new AndroidLaunch(configuration, mode, null); |
| } |
| |
| /** |
| * Returns the IProject object matching the name found in the configuration |
| * object under the name |
| * <code>IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME</code> |
| * @param configuration |
| * @return The IProject object or null |
| */ |
| private IProject getProject(ILaunchConfiguration configuration){ |
| // get the project name from the config |
| String projectName; |
| try { |
| projectName = configuration.getAttribute( |
| IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); |
| } catch (CoreException e) { |
| return null; |
| } |
| |
| // get the current workspace |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| |
| // and return the project with the name from the config |
| return workspace.getRoot().getProject(projectName); |
| } |
| |
| /** |
| * Checks the project is an android project. |
| * @param project The project to check |
| * @return true if the project is an android SDK. |
| * @throws CoreException |
| */ |
| private boolean checkAndroidProject(IProject project) throws CoreException { |
| // check if the project is a java and an android project. |
| if (project.hasNature(JavaCore.NATURE_ID) == false) { |
| String msg = String.format("%1$s is not a Java project!", project.getName()); |
| AdtPlugin.displayError("Android Launch", msg); |
| return false; |
| } |
| |
| if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { |
| String msg = String.format("%1$s is not an Android project!", project.getName()); |
| AdtPlugin.displayError("Android Launch", msg); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Returns the name of the activity. |
| */ |
| private String getActivityName(ILaunchConfiguration configuration) { |
| String empty = ""; |
| String activityName; |
| try { |
| activityName = configuration.getAttribute(ATTR_ACTIVITY, empty); |
| } catch (CoreException e) { |
| return null; |
| } |
| |
| return (activityName != empty) ? activityName : null; |
| } |
| |
| private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) { |
| AdtPlugin.printErrorToConsole(project, |
| "No Launcher activity found!", |
| "The launch will only sync the application package on the device!"); |
| config.mLaunchAction = ACTION_DO_NOTHING; |
| } |
| } |