blob: 499cca7040930933d2132da3e61f2629273b2b98 [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.launch;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.Device;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.SyncService.SyncResult;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo.InstallRetryMode;
import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IVMConnector;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Controls the launch of Android application either on a device or on the
* emulator. If an emulator is already running, this class will attempt to reuse
* it.
*/
public final class AndroidLaunchController implements IDebugBridgeChangeListener,
IDeviceChangeListener, IClientChangeListener, ILaunchController {
private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$
private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$
private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$
private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$
private static final String FLAG_NO_BOOT_ANIM = "-no-boot-anim"; //$NON-NLS-1$
/**
* Map to store {@link ILaunchConfiguration} objects that must be launched as simple connection
* to running application. The integer is the port on which to connect.
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
*/
private static final HashMap<ILaunchConfiguration, Integer> sRunningAppMap =
new HashMap<ILaunchConfiguration, Integer>();
private static final Object sListLock = sRunningAppMap;
/**
* List of {@link DelayedLaunchInfo} waiting for an emulator to connect.
* <p>Once an emulator has connected, {@link DelayedLaunchInfo#mDevice} is set and the
* DelayedLaunchInfo object is moved to {@link AndroidLaunchController#mWaitingForReadyEmulatorList}.
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
*/
private final ArrayList<DelayedLaunchInfo> mWaitingForEmulatorLaunches =
new ArrayList<DelayedLaunchInfo>();
/**
* List of application waiting to be launched on a device/emulator.<br>
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
* */
private final ArrayList<DelayedLaunchInfo> mWaitingForReadyEmulatorList =
new ArrayList<DelayedLaunchInfo>();
/**
* Application waiting to show up as waiting for debugger.
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
*/
private final ArrayList<DelayedLaunchInfo> mWaitingForDebuggerApplications =
new ArrayList<DelayedLaunchInfo>();
/**
* List of clients that have appeared as waiting for debugger before their name was available.
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
*/
private final ArrayList<Client> mUnknownClientsWaitingForDebugger = new ArrayList<Client>();
/** static instance for singleton */
private static AndroidLaunchController sThis = new AndroidLaunchController();
/**
* Output receiver for "pm install package.apk" command line.
*/
private static final class InstallReceiver extends MultiLineReceiver {
private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
private String mSuccess = null;
public InstallReceiver() {
}
@Override
public void processNewLines(String[] lines) {
for (String line : lines) {
if (line.length() > 0) {
if (line.startsWith(SUCCESS_OUTPUT)) {
mSuccess = null;
} else {
Matcher m = FAILURE_PATTERN.matcher(line);
if (m.matches()) {
mSuccess = m.group(1);
}
}
}
}
}
public boolean isCancelled() {
return false;
}
public String getSuccess() {
return mSuccess;
}
}
/** private constructor to enforce singleton */
private AndroidLaunchController() {
AndroidDebugBridge.addDebugBridgeChangeListener(this);
AndroidDebugBridge.addDeviceChangeListener(this);
AndroidDebugBridge.addClientChangeListener(this);
}
/**
* Returns the singleton reference.
*/
public static AndroidLaunchController getInstance() {
return sThis;
}
/**
* Launches a remote java debugging session on an already running application
* @param project The project of the application to debug.
* @param debugPort The port to connect the debugger to.
*/
public static void debugRunningApp(IProject project, int debugPort) {
// get an existing or new launch configuration
ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project);
if (config != null) {
setPortLaunchConfigAssociation(config, debugPort);
// and launch
DebugUITools.launch(config, ILaunchManager.DEBUG_MODE);
}
}
/**
* Returns an {@link ILaunchConfiguration} for the specified {@link IProject}.
* @param project the project
* @return a new or already existing <code>ILaunchConfiguration</code> or null if there was
* an error when creating a new one.
*/
public static ILaunchConfiguration getLaunchConfig(IProject project) {
// get the launch manager
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
// now get the config type for our particular android type.
ILaunchConfigurationType configType = manager.getLaunchConfigurationType(
LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID);
String name = project.getName();
// search for an existing launch configuration
ILaunchConfiguration config = findConfig(manager, configType, name);
// test if we found one or not
if (config == null) {
// Didn't find a matching config, so we make one.
// It'll be made in the "working copy" object first.
ILaunchConfigurationWorkingCopy wc = null;
try {
// make the working copy object
wc = configType.newInstance(null,
manager.generateUniqueLaunchConfigurationNameFrom(name));
// set the project name
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name);
// set the launch mode to default.
wc.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION,
LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION);
// set default target mode
wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE);
// default AVD: None
wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String) null);
// set the default network speed
wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED);
// and delay
wc.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
LaunchConfigDelegate.DEFAULT_DELAY);
// default wipe data mode
wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
LaunchConfigDelegate.DEFAULT_WIPE_DATA);
// default disable boot animation option
wc.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM,
LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM);
// set default emulator options
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS);
wc.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions);
// map the config and the project
wc.setMappedResources(getResourcesToMap(project));
// save the working copy to get the launch config object which we return.
return wc.doSave();
} catch (CoreException e) {
String msg = String.format(
"Failed to create a Launch config for project '%1$s': %2$s",
project.getName(), e.getMessage());
AdtPlugin.printErrorToConsole(project, msg);
// no launch!
return null;
}
}
return config;
}
/**
* Returns the list of resources to map to a Launch Configuration.
* @param project the project associated to the launch configuration.
*/
public static IResource[] getResourcesToMap(IProject project) {
ArrayList<IResource> array = new ArrayList<IResource>(2);
array.add(project);
IFile manifest = AndroidManifestParser.getManifest(project);
if (manifest != null) {
array.add(manifest);
}
return array.toArray(new IResource[array.size()]);
}
/**
* Launches an android app on the device or emulator
*
* @param project The project we're launching
* @param mode the mode in which to launch, one of the mode constants
* defined by <code>ILaunchManager</code> - <code>RUN_MODE</code> or
* <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch.
* @param debuggable the debuggable value of the app, or null if not set.
* @param requiredApiVersionNumber the api version required by the app, or
* {@link AndroidManifestParser#INVALID_MIN_SDK} if none.
* @param launchAction the action to perform after app sync
* @param config the launch configuration
* @param launch the launch object
*/
public void launch(final IProject project, String mode, IFile apk,
String packageName, Boolean debuggable, int requiredApiVersionNumber,
final IAndroidLaunchAction launchAction, final AndroidLaunchConfiguration config,
final AndroidLaunch launch, IProgressMonitor monitor) {
String message = String.format("Performing %1$s", launchAction.getLaunchDescription());
AdtPlugin.printToConsole(project, message);
// create the launch info
final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
launchAction, apk, debuggable, requiredApiVersionNumber, launch, monitor);
// set the debug mode
launchInfo.setDebugMode(mode.equals(ILaunchManager.DEBUG_MODE));
// get the SDK
Sdk currentSdk = Sdk.getCurrent();
AvdManager avdManager = currentSdk.getAvdManager();
// get the project target
final IAndroidTarget projectTarget = currentSdk.getTarget(project);
// FIXME: check errors on missing sdk, AVD manager, or project target.
// device chooser response object.
final DeviceChooserResponse response = new DeviceChooserResponse();
/*
* Launch logic:
* - Manually Mode
* Always display a UI that lets a user see the current running emulators/devices.
* The UI must show which devices are compatibles, and allow launching new emulators
* with compatible (and not yet running) AVD.
* - Automatic Way
* * Preferred AVD set.
* If Preferred AVD is not running: launch it.
* Launch the application on the preferred AVD.
* * No preferred AVD.
* Count the number of compatible emulators/devices.
* If != 1, display a UI similar to manual mode.
* If == 1, launch the application on this AVD/device.
*/
if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) {
// if we are in automatic target mode, we need to find the current devices
IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
// first check if we have a preferred AVD name, and if it actually exists, and is valid
// (ie able to run the project).
// We need to check this in case the AVD was recreated with a different target that is
// not compatible.
AvdInfo preferredAvd = null;
if (config.mAvdName != null) {
preferredAvd = avdManager.getAvd(config.mAvdName);
if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) {
preferredAvd = null;
AdtPlugin.printErrorToConsole(project, String.format(
"Preferred AVD '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible AVD...",
config.mAvdName, projectTarget.getName()));
}
}
if (preferredAvd != null) {
// look for a matching device
for (IDevice d : devices) {
String deviceAvd = d.getAvdName();
if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) {
response.setDeviceToUse(d);
AdtPlugin.printToConsole(project, String.format(
"Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'",
config.mAvdName, d));
continueLaunch(response, project, launch, launchInfo, config);
return;
}
}
// at this point we have a valid preferred AVD that is not running.
// We need to start it.
response.setAvdToLaunch(preferredAvd);
AdtPlugin.printToConsole(project, String.format(
"Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.",
config.mAvdName));
continueLaunch(response, project, launch, launchInfo, config);
return;
}
// no (valid) preferred AVD? look for one.
HashMap<IDevice, AvdInfo> compatibleRunningAvds = new HashMap<IDevice, AvdInfo>();
boolean hasDevice = false; // if there's 1+ device running, we may force manual mode,
// as we cannot always detect proper compatibility with
// devices. This is the case if the project target is not
// a standard platform
for (IDevice d : devices) {
String deviceAvd = d.getAvdName();
if (deviceAvd != null) { // physical devices return null.
AvdInfo info = avdManager.getAvd(deviceAvd);
if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) {
compatibleRunningAvds.put(d, info);
}
} else {
if (projectTarget.isPlatform()) { // means this can run on any device as long
// as api level is high enough
String apiString = d.getProperty(SdkManager.PROP_VERSION_SDK);
try {
int apiNumber = Integer.parseInt(apiString);
if (apiNumber >= projectTarget.getApiVersionNumber()) {
// device is compatible with project
compatibleRunningAvds.put(d, null);
continue;
}
} catch (NumberFormatException e) {
// do nothing, we'll consider it a non compatible device below.
}
}
hasDevice = true;
}
}
// depending on the number of devices, we'll simulate an automatic choice
// from the device chooser or simply show up the device chooser.
if (hasDevice == false && compatibleRunningAvds.size() == 0) {
// if zero emulators/devices, we launch an emulator.
// We need to figure out which AVD first.
// we are going to take the closest AVD. ie a compatible AVD that has the API level
// closest to the project target.
AvdInfo[] avds = avdManager.getAvds();
AvdInfo defaultAvd = null;
for (AvdInfo avd : avds) {
if (projectTarget.isCompatibleBaseFor(avd.getTarget())) {
if (defaultAvd == null ||
avd.getTarget().getApiVersionNumber() <
defaultAvd.getTarget().getApiVersionNumber()) {
defaultAvd = avd;
}
}
}
if (defaultAvd != null) {
response.setAvdToLaunch(defaultAvd);
AdtPlugin.printToConsole(project, String.format(
"Automatic Target Mode: launching new emulator with compatible AVD '%1$s'",
defaultAvd.getName()));
continueLaunch(response, project, launch, launchInfo, config);
return;
} else {
// FIXME: ask the user if he wants to create a AVD.
// we found no compatible AVD.
AdtPlugin.printErrorToConsole(project, String.format(
"Failed to find a AVD compatible with target '%1$s'. Launch aborted.",
projectTarget.getName()));
stopLaunch(launchInfo);
return;
}
} else if (hasDevice == false && compatibleRunningAvds.size() == 1) {
Entry<IDevice, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next();
response.setDeviceToUse(e.getKey());
// get the AvdInfo, if null, the device is a physical device.
AvdInfo avdInfo = e.getValue();
if (avdInfo != null) {
message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'",
response.getDeviceToUse(), e.getValue().getName());
} else {
message = String.format("Automatic Target Mode: using device '%1$s'",
response.getDeviceToUse());
}
AdtPlugin.printToConsole(project, message);
continueLaunch(response, project, launch, launchInfo, config);
return;
}
// if more than one device, we'll bring up the DeviceChooser dialog below.
if (compatibleRunningAvds.size() >= 2) {
message = "Automatic Target Mode: Several compatible targets. Please select a target device.";
} else if (hasDevice) {
message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device.";
}
AdtPlugin.printToConsole(project, message);
}
// bring up the device chooser.
AdtPlugin.getDisplay().asyncExec(new Runnable() {
public void run() {
try {
// open the chooser dialog. It'll fill 'response' with the device to use
// or the AVD to launch.
DeviceChooserDialog dialog = new DeviceChooserDialog(
AdtPlugin.getDisplay().getActiveShell(),
response, launchInfo.getPackageName(), projectTarget);
if (dialog.open() == Dialog.OK) {
AndroidLaunchController.this.continueLaunch(response, project, launch,
launchInfo, config);
} else {
AdtPlugin.printErrorToConsole(project, "Launch canceled!");
stopLaunch(launchInfo);
return;
}
} catch (Exception e) {
// there seems to be some case where the shell will be null. (might be
// an OS X bug). Because of this the creation of the dialog will throw
// and IllegalArg exception interrupting the launch with no user feedback.
// So we trap all the exception and display something.
String msg = e.getMessage();
if (msg == null) {
msg = e.getClass().getCanonicalName();
}
AdtPlugin.printErrorToConsole(project,
String.format("Error during launch: %s", msg));
stopLaunch(launchInfo);
}
}
});
}
/**
* Continues the launch based on the DeviceChooser response.
* @param response the device chooser response
* @param project The project being launched
* @param launch The eclipse launch info
* @param launchInfo The {@link DelayedLaunchInfo}
* @param config The config needed to start a new emulator.
*/
void continueLaunch(final DeviceChooserResponse response, final IProject project,
final AndroidLaunch launch, final DelayedLaunchInfo launchInfo,
final AndroidLaunchConfiguration config) {
// Since this is called from the UI thread we spawn a new thread
// to finish the launch.
new Thread() {
@Override
public void run() {
if (response.getAvdToLaunch() != null) {
// there was no selected device, we start a new emulator.
synchronized (sListLock) {
AvdInfo info = response.getAvdToLaunch();
mWaitingForEmulatorLaunches.add(launchInfo);
AdtPlugin.printToConsole(project, String.format(
"Launching a new emulator with Virtual Device '%1$s'",
info.getName()));
boolean status = launchEmulator(config, info);
if (status == false) {
// launching the emulator failed!
AdtPlugin.displayError("Emulator Launch",
"Couldn't launch the emulator! Make sure the SDK directory is properly setup and the emulator is not missing.");
// stop the launch and return
mWaitingForEmulatorLaunches.remove(launchInfo);
AdtPlugin.printErrorToConsole(project, "Launch canceled!");
stopLaunch(launchInfo);
return;
}
return;
}
} else if (response.getDeviceToUse() != null) {
launchInfo.setDevice(response.getDeviceToUse());
simpleLaunch(launchInfo, launchInfo.getDevice());
}
}
}.start();
}
/**
* Queries for a debugger port for a specific {@link ILaunchConfiguration}.
* <p/>
* If the configuration and a debugger port where added through
* {@link #setPortLaunchConfigAssociation(ILaunchConfiguration, int)}, then this method
* will return the debugger port, and remove the configuration from the list.
* @param launchConfig the {@link ILaunchConfiguration}
* @return the debugger port or {@link LaunchConfigDelegate#INVALID_DEBUG_PORT} if the
* configuration was not setup.
*/
static int getPortForConfig(ILaunchConfiguration launchConfig) {
synchronized (sListLock) {
Integer port = sRunningAppMap.get(launchConfig);
if (port != null) {
sRunningAppMap.remove(launchConfig);
return port;
}
}
return LaunchConfigDelegate.INVALID_DEBUG_PORT;
}
/**
* Set a {@link ILaunchConfiguration} and its associated debug port, in the list of
* launch config to connect directly to a running app instead of doing full launch (sync,
* launch, and connect to).
* @param launchConfig the {@link ILaunchConfiguration} object.
* @param port The debugger port to connect to.
*/
private static void setPortLaunchConfigAssociation(ILaunchConfiguration launchConfig,
int port) {
synchronized (sListLock) {
sRunningAppMap.put(launchConfig, port);
}
}
/**
* Checks the build information, and returns whether the launch should continue.
* <p/>The value tested are:
* <ul>
* <li>Minimum API version requested by the application. If the target device does not match,
* the launch is canceled.</li>
* <li>Debuggable attribute of the application and whether or not the device requires it. If
* the device requires it and it is not set in the manifest, the launch will be forced to
* "release" mode instead of "debug"</li>
* <ul>
*/
private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, IDevice device) {
if (device != null) {
// check the app required API level versus the target device API level
String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION);
String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER);
int deviceApiVersionNumber = AndroidManifestParser.INVALID_MIN_SDK;
try {
deviceApiVersionNumber = Integer.parseInt(value);
} catch (NumberFormatException e) {
// pass, we'll keep the deviceVersionNumber value at 0.
}
if (launchInfo.getRequiredApiVersionNumber() == AndroidManifestParser.INVALID_MIN_SDK) {
// warn the API level requirement is not set.
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Application does not specify an API level requirement!");
// and display the target device API level (if known)
if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!");
} else {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format(
"Device API version is %1$d (Android %2$s)", deviceApiVersionNumber,
deviceApiVersionName));
}
} else { // app requires a specific API level
if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!");
} else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) {
String msg = String.format(
"ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).",
launchInfo.getRequiredApiVersionNumber(), deviceApiVersionNumber,
deviceApiVersionName);
AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
// abort the launch
return false;
}
}
// now checks that the device/app can be debugged (if needed)
if (device.isEmulator() == false && launchInfo.isDebugMode()) {
String debuggableDevice = device.getProperty(IDevice.PROP_DEBUGGABLE);
if (debuggableDevice != null && debuggableDevice.equals("0")) { //$NON-NLS-1$
// the device is "secure" and requires apps to declare themselves as debuggable!
if (launchInfo.getDebuggable() == null) {
String message1 = String.format(
"Device '%1$s' requires that applications explicitely declare themselves as debuggable in their manifest.",
device.getSerialNumber());
String message2 = String.format("Application '%1$s' does not have the attribute 'debuggable' set to TRUE in its manifest and cannot be debugged.",
launchInfo.getPackageName());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), message1, message2);
// because am -D does not check for ro.debuggable and the
// 'debuggable' attribute, it is important we do not use the -D option
// in this case or the app will wait for a debugger forever and never
// really launch.
launchInfo.setDebugMode(false);
} else if (launchInfo.getDebuggable() == Boolean.FALSE) {
String message = String.format("Application '%1$s' has its 'debuggable' attribute set to FALSE and cannot be debugged.",
launchInfo.getPackageName());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), message);
// because am -D does not check for ro.debuggable and the
// 'debuggable' attribute, it is important we do not use the -D option
// in this case or the app will wait for a debugger forever and never
// really launch.
launchInfo.setDebugMode(false);
}
}
}
}
return true;
}
/**
* Do a simple launch on the specified device, attempting to sync the new
* package, and then launching the application. Failed sync/launch will
* stop the current AndroidLaunch and return false;
* @param launchInfo
* @param device
* @return true if succeed
*/
private boolean simpleLaunch(DelayedLaunchInfo launchInfo, IDevice device) {
// API level check
if (checkBuildInfo(launchInfo, device) == false) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), "Launch canceled!");
stopLaunch(launchInfo);
return false;
}
// sync the app
if (syncApp(launchInfo, device) == false) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), "Launch canceled!");
stopLaunch(launchInfo);
return false;
}
// launch the app
launchApp(launchInfo, device);
return true;
}
/**
* Syncs the application on the device/emulator.
*
* @param launchInfo The Launch information object.
* @param device the device on which to sync the application
* @return true if the install succeeded.
*/
private boolean syncApp(DelayedLaunchInfo launchInfo, IDevice device) {
SyncService sync = device.getSyncService();
if (sync != null) {
IPath path = launchInfo.getPackageFile().getLocation();
String message = String.format("Uploading %1$s onto device '%2$s'",
path.lastSegment(), device.getSerialNumber());
AdtPlugin.printToConsole(launchInfo.getProject(), message);
String osLocalPath = path.toOSString();
String apkName = launchInfo.getPackageFile().getName();
String remotePath = "/data/local/tmp/" + apkName; //$NON-NLS-1$
SyncResult result = sync.pushFile(osLocalPath, remotePath,
SyncService.getNullProgressMonitor());
if (result.getCode() != SyncService.RESULT_OK) {
String msg = String.format("Failed to upload %1$s on '%2$s': %3$s",
apkName, device.getSerialNumber(), result.getMessage());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
return false;
}
// Now that the package is uploaded, we can install it properly.
// This will check that there isn't another apk declaring the same package, or
// that another install used a different key.
boolean installResult = installPackage(launchInfo, remotePath, device);
// now we delete the app we sync'ed
try {
device.executeShellCommand("rm " + remotePath, new MultiLineReceiver() { //$NON-NLS-1$
@Override
public void processNewLines(String[] lines) {
// pass
}
public boolean isCancelled() {
return false;
}
});
} catch (IOException e) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format(
"Failed to delete temporary package: %1$s", e.getMessage()));
return false;
}
return installResult;
}
String msg = String.format(
"Failed to upload %1$s on device '%2$s': Unable to open sync connection!",
launchInfo.getPackageFile().getName(), device.getSerialNumber());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
return false;
}
/**
* Installs the application package that was pushed to a temporary location on the device.
* @param launchInfo The launch information
* @param remotePath The remote path of the package.
* @param device The device on which the launch is done.
*/
private boolean installPackage(DelayedLaunchInfo launchInfo, final String remotePath,
final IDevice device) {
String message = String.format("Installing %1$s...", launchInfo.getPackageFile().getName());
AdtPlugin.printToConsole(launchInfo.getProject(), message);
try {
String result = doInstall(launchInfo, remotePath, device, false /* reinstall */);
/* For now we force to retry the install (after uninstalling) because there's no
* other way around it: adb install does not want to update a package w/o uninstalling
* the old one first!
*/
return checkInstallResult(result, device, launchInfo, remotePath,
InstallRetryMode.ALWAYS);
} catch (IOException e) {
// do nothing, we'll return false
}
return false;
}
/**
* Checks the result of an installation, and takes optional actions based on it.
* @param result the result string from the installation
* @param device the device on which the installation occured.
* @param launchInfo the {@link DelayedLaunchInfo}
* @param remotePath the temporary path of the package on the device
* @param retryMode indicates what to do in case, a package already exists.
* @return <code>true<code> if success, <code>false</code> otherwise.
* @throws IOException
*/
private boolean checkInstallResult(String result, IDevice device, DelayedLaunchInfo launchInfo,
String remotePath, InstallRetryMode retryMode) throws IOException {
if (result == null) {
AdtPlugin.printToConsole(launchInfo.getProject(), "Success!");
return true;
} else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$
if (retryMode == InstallRetryMode.PROMPT) {
boolean prompt = AdtPlugin.displayPrompt("Application Install",
"A previous installation needs to be uninstalled before the new package can be installed.\nDo you want to uninstall?");
if (prompt) {
retryMode = InstallRetryMode.ALWAYS;
} else {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Installation error! The package already exists.");
return false;
}
}
if (retryMode == InstallRetryMode.ALWAYS) {
/*
* TODO: create a UI that gives the dev the choice to:
* - clean uninstall on launch
* - full uninstall if application exists.
* - soft uninstall if application exists (keeps the app data around).
* - always ask (choice of soft-reinstall, full reinstall)
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"Application already exists, uninstalling...");
String res = doUninstall(device, launchInfo);
if (res == null) {
AdtPlugin.printToConsole(launchInfo.mProject, "Success!");
} else {
AdtPlugin.printErrorToConsole(launchInfo.mProject,
String.format("Failed to uninstall: %1$s", res));
return false;
}
*/
AdtPlugin.printToConsole(launchInfo.getProject(),
"Application already exists. Attempting to re-install instead...");
String res = doInstall(launchInfo, remotePath, device, true /* reinstall */);
return checkInstallResult(res, device, launchInfo, remotePath,
InstallRetryMode.NEVER);
}
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Installation error! The package already exists.");
} else if (result.equals("INSTALL_FAILED_INVALID_APK")) { //$NON-NLS-1$
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Installation failed due to invalid APK file!",
"Please check logcat output for more details.");
} else if (result.equals("INSTALL_FAILED_INVALID_URI")) { //$NON-NLS-1$
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Installation failed due to invalid URI!",
"Please check logcat output for more details.");
} else if (result.equals("INSTALL_FAILED_COULDNT_COPY")) { //$NON-NLS-1$
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format("Installation failed: Could not copy %1$s to its final location!",
launchInfo.getPackageFile().getName()),
"Please check logcat output for more details.");
} else if (result.equals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Re-installation failed due to different application signatures.",
"You must perform a full uninstall of the application. WARNING: This will remove the application data!",
String.format("Please execute 'adb uninstall %1$s' in a shell.", launchInfo.getPackageName()));
} else {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format("Installation error: %1$s", result),
"Please check logcat output for more details.");
}
return false;
}
/**
* Performs the uninstallation of an application.
* @param device the device on which to install the application.
* @param launchInfo the {@link DelayedLaunchInfo}.
* @return a {@link String} with an error code, or <code>null</code> if success.
* @throws IOException
*/
@SuppressWarnings("unused")
private String doUninstall(IDevice device, DelayedLaunchInfo launchInfo) throws IOException {
InstallReceiver receiver = new InstallReceiver();
try {
device.executeShellCommand("pm uninstall " + launchInfo.getPackageName(), //$NON-NLS-1$
receiver);
} catch (IOException e) {
String msg = String.format(
"Failed to uninstall %1$s: %2$s", launchInfo.getPackageName(), e.getMessage());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
throw e;
}
return receiver.getSuccess();
}
/**
* 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.
* @param reinstall
* @return a {@link String} with an error code, or <code>null</code> if success.
* @throws IOException
*/
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(
reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"", //$NON-NLS-1$ //$NON-NLS-2$
remotePath); //$NON-NLS-1$ //$NON-NLS-2$
device.executeShellCommand(cmd, receiver);
} catch (IOException e) {
String msg = String.format(
"Failed to install %1$s on device '%2$s': %3$s",
launchInfo.getPackageFile().getName(), device.getSerialNumber(),
e.getMessage());
AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
throw e;
}
return receiver.getSuccess();
}
/**
* launches an application on a device or emulator
*
* @param info the {@link DelayedLaunchInfo} that indicates the launch action
* @param device the device or emulator to launch the application on
*/
public void launchApp(final DelayedLaunchInfo info, IDevice device) {
if (info.isDebugMode()) {
synchronized (sListLock) {
if (mWaitingForDebuggerApplications.contains(info) == false) {
mWaitingForDebuggerApplications.add(info);
}
}
}
if (info.getLaunchAction().doLaunchAction(info, device)) {
// if the app is not a debug app, we need to do some clean up, as
// the process is done!
if (info.isDebugMode() == false) {
// stop the launch object, since there's no debug, and it can't
// provide any control over the app
stopLaunch(info);
}
} else {
// something went wrong or no further launch action needed
// lets stop the Launch
stopLaunch(info);
}
}
private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) {
// split the custom command line in segments
ArrayList<String> customArgs = new ArrayList<String>();
boolean hasWipeData = false;
if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
// we need to remove the empty strings
for (String s : segments) {
if (s.length() > 0) {
customArgs.add(s);
if (!hasWipeData && s.equals(FLAG_WIPE_DATA)) {
hasWipeData = true;
}
}
}
}
boolean needsWipeData = config.mWipeData && !hasWipeData;
if (needsWipeData) {
if (!AdtPlugin.displayPrompt("Android Launch", "Are you sure you want to wipe all user data when starting this emulator?")) {
needsWipeData = false;
}
}
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(AdtPlugin.getOsAbsoluteEmulator());
list.add(FLAG_AVD);
list.add(avdToLaunch.getName());
if (config.mNetworkSpeed != null) {
list.add(FLAG_NETSPEED);
list.add(config.mNetworkSpeed);
}
if (config.mNetworkDelay != null) {
list.add(FLAG_NETDELAY);
list.add(config.mNetworkDelay);
}
if (needsWipeData) {
list.add(FLAG_WIPE_DATA);
}
if (config.mNoBootAnim) {
list.add(FLAG_NO_BOOT_ANIM);
}
list.addAll(customArgs);
// convert the list into an array for the call to exec.
String[] command = list.toArray(new String[list.size()]);
// launch the emulator
try {
Process process = Runtime.getRuntime().exec(command);
grabEmulatorOutput(process);
} catch (IOException e) {
return false;
}
return true;
}
/**
* Looks for and returns an existing {@link ILaunchConfiguration} object for a
* specified project.
* @param manager The {@link ILaunchManager}.
* @param type The {@link ILaunchConfigurationType}.
* @param projectName The name of the project
* @return an existing <code>ILaunchConfiguration</code> object matching the project, or
* <code>null</code>.
*/
private static ILaunchConfiguration findConfig(ILaunchManager manager,
ILaunchConfigurationType type, String projectName) {
try {
ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type);
for (ILaunchConfiguration config : configs) {
if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
"").equals(projectName)) { //$NON-NLS-1$
return config;
}
}
} catch (CoreException e) {
MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
"Launch Error", e.getStatus().getMessage());
}
// didn't find anything that matches. Return null
return null;
}
/**
* Connects a remote debugger on the specified port.
* @param debugPort The port to connect the debugger to
* @param launch The associated AndroidLaunch object.
* @param monitor A Progress monitor
* @return false if cancelled by the monitor
* @throws CoreException
*/
public static boolean connectRemoteDebugger(int debugPort,
AndroidLaunch launch, IProgressMonitor monitor)
throws CoreException {
// get some default parameters.
int connectTimeout = JavaRuntime.getPreferences().getInt(JavaRuntime.PREF_CONNECT_TIMEOUT);
HashMap<String, String> newMap = new HashMap<String, String>();
newMap.put("hostname", "localhost"); //$NON-NLS-1$ //$NON-NLS-2$
newMap.put("port", Integer.toString(debugPort)); //$NON-NLS-1$
newMap.put("timeout", Integer.toString(connectTimeout));
// get the default VM connector
IVMConnector connector = JavaRuntime.getDefaultVMConnector();
// connect to remote VM
connector.connect(newMap, monitor, launch);
// check for cancellation
if (monitor.isCanceled()) {
IDebugTarget[] debugTargets = launch.getDebugTargets();
for (IDebugTarget target : debugTargets) {
if (target.canDisconnect()) {
target.disconnect();
}
}
return false;
}
return true;
}
/**
* Launch a new thread that connects a remote debugger on the specified port.
* @param debugPort The port to connect the debugger to
* @param androidLaunch The associated AndroidLaunch object.
* @param monitor A Progress monitor
* @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor)
*/
public static void launchRemoteDebugger(final int debugPort, final AndroidLaunch androidLaunch,
final IProgressMonitor monitor) {
new Thread("Debugger connection") { //$NON-NLS-1$
@Override
public void run() {
try {
connectRemoteDebugger(debugPort, androidLaunch, monitor);
} catch (CoreException e) {
androidLaunch.stopLaunch();
}
monitor.done();
}
}.start();
}
/**
* Sent when a new {@link AndroidDebugBridge} is started.
* <p/>
* This is sent from a non UI thread.
* @param bridge the new {@link AndroidDebugBridge} object.
*
* @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
*/
public void bridgeChanged(AndroidDebugBridge bridge) {
// The adb server has changed. We cancel any pending launches.
String message = "adb server change: cancelling '%1$s'!";
synchronized (sListLock) {
for (DelayedLaunchInfo launchInfo : mWaitingForReadyEmulatorList) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format(message, launchInfo.getLaunchAction().getLaunchDescription()));
stopLaunch(launchInfo);
}
for (DelayedLaunchInfo launchInfo : mWaitingForDebuggerApplications) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format(message,
launchInfo.getLaunchAction().getLaunchDescription()));
stopLaunch(launchInfo);
}
mWaitingForReadyEmulatorList.clear();
mWaitingForDebuggerApplications.clear();
}
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceConnected(Device)
*/
public void deviceConnected(Device device) {
synchronized (sListLock) {
// look if there's an app waiting for a device
if (mWaitingForEmulatorLaunches.size() > 0) {
// get/remove first launch item from the list
// FIXME: what if we have multiple launches waiting?
DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0);
mWaitingForEmulatorLaunches.remove(0);
// give the launch item its device for later use.
launchInfo.setDevice(device);
// and move it to the other list
mWaitingForReadyEmulatorList.add(launchInfo);
// and tell the user about it
AdtPlugin.printToConsole(launchInfo.getProject(),
String.format("New emulator found: %1$s", device.getSerialNumber()));
AdtPlugin.printToConsole(launchInfo.getProject(),
String.format("Waiting for HOME ('%1$s') to be launched...",
AdtPlugin.getDefault().getPreferenceStore().getString(
AdtPlugin.PREFS_HOME_PACKAGE)));
}
}
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceDisconnected(Device)
*/
@SuppressWarnings("unchecked")
public void deviceDisconnected(Device device) {
// any pending launch on this device must be canceled.
String message = "%1$s disconnected! Cancelling '%2$s'!";
synchronized (sListLock) {
ArrayList<DelayedLaunchInfo> copyList =
(ArrayList<DelayedLaunchInfo>) mWaitingForReadyEmulatorList.clone();
for (DelayedLaunchInfo launchInfo : copyList) {
if (launchInfo.getDevice() == device) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format(message, device.getSerialNumber(),
launchInfo.getLaunchAction().getLaunchDescription()));
stopLaunch(launchInfo);
}
}
copyList = (ArrayList<DelayedLaunchInfo>) mWaitingForDebuggerApplications.clone();
for (DelayedLaunchInfo launchInfo : copyList) {
if (launchInfo.getDevice() == device) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format(message, device.getSerialNumber(),
launchInfo.getLaunchAction().getLaunchDescription()));
stopLaunch(launchInfo);
}
}
}
}
/**
* Sent when a device data changed, or when clients are started/terminated on the device.
* <p/>
* This is sent from a non UI thread.
* @param device the device that was updated.
* @param changeMask the mask indicating what changed.
*
* @see IDeviceChangeListener#deviceChanged(Device, int)
*/
public void deviceChanged(Device device, int changeMask) {
// We could check if any starting device we care about is now ready, but we can wait for
// its home app to show up, so...
}
/**
* Sent when an existing client information changed.
* <p/>
* This is sent from a non UI thread.
* @param client the updated client.
* @param changeMask the bit mask describing the changed properties. It can contain
* any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
* {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE},
* {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
* {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
*
* @see IClientChangeListener#clientChanged(Client, int)
*/
public void clientChanged(final Client client, int changeMask) {
boolean connectDebugger = false;
if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
String applicationName = client.getClientData().getClientDescription();
if (applicationName != null) {
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
String home = store.getString(AdtPlugin.PREFS_HOME_PACKAGE);
if (home.equals(applicationName)) {
// looks like home is up, get its device
IDevice device = client.getDevice();
// look for application waiting for home
synchronized (sListLock) {
for (int i = 0; i < mWaitingForReadyEmulatorList.size(); ) {
DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i);
if (launchInfo.getDevice() == device) {
// it's match, remove from the list
mWaitingForReadyEmulatorList.remove(i);
// We couldn't check earlier the API level of the device
// (it's asynchronous when the device boot, and usually
// deviceConnected is called before it's queried for its build info)
// so we check now
if (checkBuildInfo(launchInfo, device) == false) {
// device is not the proper API!
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Launch canceled!");
stopLaunch(launchInfo);
return;
}
AdtPlugin.printToConsole(launchInfo.getProject(),
String.format("HOME is up on device '%1$s'",
device.getSerialNumber()));
// attempt to sync the new package onto the device.
if (syncApp(launchInfo, device)) {
// application package is sync'ed, lets attempt to launch it.
launchApp(launchInfo, device);
} else {
// failure! Cancel and return
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"Launch canceled!");
stopLaunch(launchInfo);
}
break;
} else {
i++;
}
}
}
}
// check if it's already waiting for a debugger, and if so we connect to it.
if (client.getClientData().getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) {
// search for this client in the list;
synchronized (sListLock) {
int index = mUnknownClientsWaitingForDebugger.indexOf(client);
if (index != -1) {
connectDebugger = true;
mUnknownClientsWaitingForDebugger.remove(client);
}
}
}
}
}
// if it's not home, it could be an app that is now in debugger mode that we're waiting for
// lets check it
if ((changeMask & Client.CHANGE_DEBUGGER_INTEREST) == Client.CHANGE_DEBUGGER_INTEREST) {
ClientData clientData = client.getClientData();
String applicationName = client.getClientData().getClientDescription();
if (clientData.getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) {
// Get the application name, and make sure its valid.
if (applicationName == null) {
// looks like we don't have the client yet, so we keep it around for when its
// name becomes available.
synchronized (sListLock) {
mUnknownClientsWaitingForDebugger.add(client);
}
return;
} else {
connectDebugger = true;
}
}
}
if (connectDebugger) {
Log.d("adt", "Debugging " + client);
// now check it against the apps waiting for a debugger
String applicationName = client.getClientData().getClientDescription();
Log.d("adt", "App Name: " + applicationName);
synchronized (sListLock) {
for (int i = 0; i < mWaitingForDebuggerApplications.size(); ) {
final DelayedLaunchInfo launchInfo = mWaitingForDebuggerApplications.get(i);
if (client.getDevice() == launchInfo.getDevice() &&
applicationName.equals(launchInfo.getPackageName())) {
// this is a match. We remove the launch info from the list
mWaitingForDebuggerApplications.remove(i);
// and connect the debugger.
String msg = String.format(
"Attempting to connect debugger to '%1$s' on port %2$d",
launchInfo.getPackageName(), client.getDebuggerListenPort());
AdtPlugin.printToConsole(launchInfo.getProject(), msg);
new Thread("Debugger Connection") { //$NON-NLS-1$
@Override
public void run() {
try {
if (connectRemoteDebugger(
client.getDebuggerListenPort(),
launchInfo.getLaunch(),
launchInfo.getMonitor()) == false) {
return;
}
} catch (CoreException e) {
// well something went wrong.
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
String.format("Launch error: %s", e.getMessage()));
// stop the launch
stopLaunch(launchInfo);
}
launchInfo.getMonitor().done();
}
}.start();
// we're done processing this client.
return;
} else {
i++;
}
}
}
// if we get here, we haven't found an app that we were launching, so we look
// for opened android projects that contains the app asking for a debugger.
// If we find one, we automatically connect to it.
IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName);
if (project != null) {
debugRunningApp(project, client.getDebuggerListenPort());
}
}
}
/**
* Get the stderr/stdout outputs of a process and return when the process is done.
* Both <b>must</b> be read or the process will block on windows.
* @param process The process to get the ouput from
*/
private void grabEmulatorOutput(final Process process) {
// read the lines as they come. if null is returned, it's
// because the process finished
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
// create a buffer to read the stderr output
InputStreamReader is = new InputStreamReader(process.getErrorStream());
BufferedReader errReader = new BufferedReader(is);
try {
while (true) {
String line = errReader.readLine();
if (line != null) {
AdtPlugin.printErrorToConsole("Emulator", line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
InputStreamReader is = new InputStreamReader(process.getInputStream());
BufferedReader outReader = new BufferedReader(is);
try {
while (true) {
String line = outReader.readLine();
if (line != null) {
AdtPlugin.printToConsole("Emulator", line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
}
/* (non-Javadoc)
* @see com.android.ide.eclipse.adt.launch.ILaunchController#stopLaunch(com.android.ide.eclipse.adt.launch.AndroidLaunchController.DelayedLaunchInfo)
*/
public void stopLaunch(DelayedLaunchInfo launchInfo) {
launchInfo.getLaunch().stopLaunch();
synchronized (sListLock) {
mWaitingForReadyEmulatorList.remove(launchInfo);
mWaitingForDebuggerApplications.remove(launchInfo);
}
}
}