AndroidLaunchControllerpublic final class AndroidLaunchController extends Object implements com.android.ddmlib.AndroidDebugBridge.IClientChangeListener, com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener, com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener, ILaunchControllerControls 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. |
Fields Summary |
---|
private static final String | FLAG_AVD | private static final String | FLAG_NETDELAY | private static final String | FLAG_NETSPEED | private static final String | FLAG_WIPE_DATA | private static final String | FLAG_NO_BOOT_ANIM | private static final HashMap | sRunningAppMapMap to store {@link ILaunchConfiguration} objects that must be launched as simple connection
to running application. The integer is the port on which to connect.
ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! | private static final Object | sListLock | private final ArrayList | mWaitingForEmulatorLaunchesList of {@link DelayedLaunchInfo} waiting for an emulator to connect.
Once an emulator has connected, {@link DelayedLaunchInfo#getDevice()} is set and the
DelayedLaunchInfo object is moved to
{@link AndroidLaunchController#mWaitingForReadyEmulatorList}.
ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! | private final ArrayList | mWaitingForReadyEmulatorListList of application waiting to be launched on a device/emulator.
ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! | private final ArrayList | mWaitingForDebuggerApplicationsApplication waiting to show up as waiting for debugger.
ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! | private final ArrayList | mUnknownClientsWaitingForDebuggerList of clients that have appeared as waiting for debugger before their name was available.
ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! | private static AndroidLaunchController | sThisstatic instance for singleton |
Constructors Summary |
---|
private AndroidLaunchController()private constructor to enforce singleton
AndroidDebugBridge.addDebugBridgeChangeListener(this);
AndroidDebugBridge.addDeviceChangeListener(this);
AndroidDebugBridge.addClientChangeListener(this);
|
Methods Summary |
---|
public void | bridgeChanged(com.android.ddmlib.AndroidDebugBridge bridge)Sent when a new {@link AndroidDebugBridge} is started.
This is sent from a non UI thread.
// 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();
}
| private boolean | checkBuildInfo(DelayedLaunchInfo launchInfo, com.android.ddmlib.IDevice device)Checks the build information, and returns whether the launch should continue.
The value tested are:
- Minimum API version requested by the application. If the target device does not match,
the launch is canceled.
- 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"
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;
| private boolean | checkInstallResult(java.lang.String result, com.android.ddmlib.IDevice device, DelayedLaunchInfo launchInfo, java.lang.String remotePath, com.android.ide.eclipse.adt.launch.DelayedLaunchInfo.InstallRetryMode retryMode)Checks the result of an installation, and takes optional actions based on it.
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;
| public void | clientChanged(com.android.ddmlib.Client client, int changeMask)Sent when an existing client information changed.
This is sent from a non UI thread.
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.getDebugPackageName())) {
// 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.getDebugPackageName(), 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());
}
}
| public static boolean | connectRemoteDebugger(int debugPort, AndroidLaunch launch, org.eclipse.core.runtime.IProgressMonitor monitor)Connects a remote debugger on the specified port.
// 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;
| void | continueLaunch(com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse response, org.eclipse.core.resources.IProject project, AndroidLaunch launch, DelayedLaunchInfo launchInfo, AndroidLaunchConfiguration config)Continues the launch based on the DeviceChooser response.
// 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();
| public static void | debugRunningApp(org.eclipse.core.resources.IProject project, int debugPort)Launches a remote java debugging session on an already running application
// 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);
}
| public void | deviceChanged(com.android.ddmlib.Device device, int changeMask)Sent when a device data changed, or when clients are started/terminated on the device.
This is sent from a non UI thread.
// 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...
| public void | deviceConnected(com.android.ddmlib.Device device)Sent when the a device is connected to the {@link AndroidDebugBridge}.
This is sent from a non UI thread.
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)));
}
}
| public void | deviceDisconnected(com.android.ddmlib.Device device)Sent when the a device is connected to the {@link AndroidDebugBridge}.
This is sent from a non UI thread.
// 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);
}
}
}
| private java.lang.String | doInstall(DelayedLaunchInfo launchInfo, java.lang.String remotePath, com.android.ddmlib.IDevice device, boolean reinstall)Performs the installation of an application whose package has been uploaded on the device.
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();
| private boolean | doSyncApp(DelayedLaunchInfo launchInfo, com.android.ddmlib.IDevice device)Syncs the application on the device/emulator.
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;
}
// if the installation succeeded, we register it.
if (installResult) {
ApkInstallManager.getInstance().registerInstallation(
launchInfo.getProject(), device);
}
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;
| private java.lang.String | doUninstall(com.android.ddmlib.IDevice device, DelayedLaunchInfo launchInfo)Performs the uninstallation of an application.
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();
| private static org.eclipse.debug.core.ILaunchConfiguration | findConfig(org.eclipse.debug.core.ILaunchManager manager, org.eclipse.debug.core.ILaunchConfigurationType type, java.lang.String projectName)Looks for and returns an existing {@link ILaunchConfiguration} object for a
specified project.
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;
| public java.util.List | getDependenciesLaunchInfo(DelayedLaunchInfo launchInfo)For the current launchInfo, create additional DelayedLaunchInfo that should be used to
sync APKs that we are dependent on to the device.
List<DelayedLaunchInfo> dependencies = new ArrayList<DelayedLaunchInfo>();
// Convert to equivalent JavaProject
IJavaProject javaProject;
try {
//assuming this is an Android (and Java) project since it is attached to the launchInfo.
javaProject = BaseProjectHelper.getJavaProject(launchInfo.getProject());
} catch (CoreException e) {
// return empty dependencies
AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
return dependencies;
}
// Get all projects that this depends on
List<IJavaProject> androidProjectList;
try {
androidProjectList = ProjectHelper.getAndroidProjectDependencies(javaProject);
} catch (JavaModelException e) {
// return empty dependencies
AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
return dependencies;
}
// for each project, parse manifest and create launch information
for (IJavaProject androidProject : androidProjectList) {
// Parse the Manifest to get various required information
// copied from LaunchConfigDelegate
AndroidManifestParser manifestParser;
try {
manifestParser = AndroidManifestParser.parse(
androidProject, null /* errorListener */,
true /* gatherData */, false /* markErrors */);
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(
launchInfo.getProject(),
String.format("Error parsing manifest of %s",
androidProject.getElementName()));
continue;
}
// Get the APK location (can return null)
IFile apk = ProjectHelper.getApplicationPackage(androidProject.getProject());
if (apk == null) {
// getApplicationPackage will have logged an error message
continue;
}
// Create new launchInfo as an hybrid between parent and dependency information
DelayedLaunchInfo delayedLaunchInfo = new DelayedLaunchInfo(
androidProject.getProject(),
manifestParser.getPackage(),
manifestParser.getPackage(),
launchInfo.getLaunchAction(),
apk,
manifestParser.getDebuggable(),
manifestParser.getApiLevelRequirement(),
launchInfo.getLaunch(),
launchInfo.getMonitor());
// Add to the list
dependencies.add(delayedLaunchInfo);
}
return dependencies;
| public static com.android.ide.eclipse.adt.launch.AndroidLaunchController | getInstance()Returns the singleton reference.
return sThis;
| public static org.eclipse.debug.core.ILaunchConfiguration | getLaunchConfig(org.eclipse.core.resources.IProject project)Returns an {@link ILaunchConfiguration} for the specified {@link IProject}.
// 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.getValue());
// 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;
| static int | getPortForConfig(org.eclipse.debug.core.ILaunchConfiguration launchConfig)Queries for a debugger port for a specific {@link ILaunchConfiguration}.
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.
synchronized (sListLock) {
Integer port = sRunningAppMap.get(launchConfig);
if (port != null) {
sRunningAppMap.remove(launchConfig);
return port;
}
}
return LaunchConfigDelegate.INVALID_DEBUG_PORT;
| public static org.eclipse.core.resources.IResource[] | getResourcesToMap(org.eclipse.core.resources.IProject project)Returns the list of resources to map to a Launch Configuration.
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()]);
| private void | grabEmulatorOutput(java.lang.Process process)Get the stderr/stdout outputs of a process and return when the process is done.
Both must be read or the process will block on windows.
// 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();
| private boolean | installPackage(DelayedLaunchInfo launchInfo, java.lang.String remotePath, com.android.ddmlib.IDevice device)Installs the application package that was pushed to a temporary location on the 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;
| public void | launch(org.eclipse.core.resources.IProject project, java.lang.String mode, org.eclipse.core.resources.IFile apk, java.lang.String packageName, java.lang.String debugPackageName, java.lang.Boolean debuggable, int requiredApiVersionNumber, IAndroidLaunchAction launchAction, AndroidLaunchConfiguration config, AndroidLaunch launch, org.eclipse.core.runtime.IProgressMonitor monitor)Launches an android app on the device or emulator
String message = String.format("Performing %1$s", launchAction.getLaunchDescription());
AdtPlugin.printToConsole(project, message);
// create the launch info
final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
debugPackageName, 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();
// reload the AVDs to make sure we are up to date
try {
avdManager.reloadAvds();
} catch (AndroidLocationException e1) {
// this happens if the AVD Manager failed to find the folder in which the AVDs are
// stored. This is unlikely to happen, but if it does, we should force to go manual
// to allow using physical devices.
config.mTargetMode = TargetMode.MANUAL;
}
// 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 == TargetMode.AUTO) {
// 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, true /*validAvdOnly*/);
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, true /*validAvdOnly*/);
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.getValidAvds();
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 an 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);
}
}
});
| public void | launchApp(DelayedLaunchInfo info, com.android.ddmlib.IDevice device)launches an application on a device or emulator
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, com.android.sdklib.avd.AvdManager.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;
| public static void | launchRemoteDebugger(int debugPort, AndroidLaunch androidLaunch, org.eclipse.core.runtime.IProgressMonitor monitor)Launch a new thread that connects a remote debugger on the specified port.
new Thread("Debugger connection") { //$NON-NLS-1$
@Override
public void run() {
try {
connectRemoteDebugger(debugPort, androidLaunch, monitor);
} catch (CoreException e) {
androidLaunch.stopLaunch();
}
monitor.done();
}
}.start();
| private static void | setPortLaunchConfigAssociation(org.eclipse.debug.core.ILaunchConfiguration launchConfig, int 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).
synchronized (sListLock) {
sRunningAppMap.put(launchConfig, port);
}
| private boolean | simpleLaunch(DelayedLaunchInfo launchInfo, com.android.ddmlib.IDevice device)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;
// 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;
| public void | stopLaunch(DelayedLaunchInfo launchInfo)
launchInfo.getLaunch().stopLaunch();
synchronized (sListLock) {
mWaitingForReadyEmulatorList.remove(launchInfo);
mWaitingForDebuggerApplications.remove(launchInfo);
}
| private boolean | syncApp(DelayedLaunchInfo launchInfo, com.android.ddmlib.IDevice device)If needed, syncs the application and all its dependencies on the device/emulator.
boolean alreadyInstalled = ApkInstallManager.getInstance().isApplicationInstalled(
launchInfo.getProject(), device);
if (alreadyInstalled) {
AdtPlugin.printToConsole(launchInfo.getProject(),
"Application already deployed. No need to reinstall.");
} else {
if (doSyncApp(launchInfo, device) == false) {
return false;
}
}
// The app is now installed, now try the dependent projects
for (DelayedLaunchInfo dependentLaunchInfo : getDependenciesLaunchInfo(launchInfo)) {
String msg = String.format("Project dependency found, installing: %s",
dependentLaunchInfo.getProject().getName());
AdtPlugin.printToConsole(launchInfo.getProject(), msg);
if (syncApp(dependentLaunchInfo, device) == false) {
return false;
}
}
return true;
|
|