blob: 86fc2ffaec41abb8b49b3ac3e4b49adfde4316e9 [file] [log] [blame]
/*
* 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;
}
}