diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java | 1872 |
1 files changed, 0 insertions, 1872 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java deleted file mode 100644 index a95ed6882..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java +++ /dev/null @@ -1,1872 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.launch; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ddmlib.AdbCommandRejectedException; -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.CanceledException; -import com.android.ddmlib.Client; -import com.android.ddmlib.ClientData; -import com.android.ddmlib.ClientData.DebuggerStatus; -import com.android.ddmlib.IDevice; -import com.android.ddmlib.InstallException; -import com.android.ddmlib.Log; -import com.android.ddmlib.TimeoutException; -import com.android.ide.common.xml.ManifestData; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.actions.AvdManagerAction; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode; -import com.android.ide.eclipse.adt.internal.launch.DelayedLaunchInfo.InstallRetryMode; -import com.android.ide.eclipse.adt.internal.launch.DeviceChooserDialog.DeviceChooserResponse; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; -import com.android.ide.eclipse.adt.internal.project.ApkInstallManager; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.project.ProjectHelper; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.ide.eclipse.ddms.DdmsPlugin; -import com.android.prefs.AndroidLocation.AndroidLocationException; -import com.android.sdklib.AndroidVersion; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.internal.avd.AvdInfo; -import com.android.sdklib.internal.avd.AvdManager; -import com.android.utils.NullLogger; - -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.core.IJavaProject; -import org.eclipse.jdt.core.JavaModelException; -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 org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * 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#getDevice()} 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(); - - /** 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, - LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID); - - 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 - * @param launchTypeId launch delegate type id - * @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, String launchTypeId) { - // get the launch manager - ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); - - // now get the config type for our particular android type. - ILaunchConfigurationType configType = manager.getLaunchConfigurationType(launchTypeId); - - 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.generateLaunchConfigurationName(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.toString()); - - // 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(AdtPrefs.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 = ProjectHelper.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 packageName the Android package name of the app - * @param debugPackageName the Android package name to debug - * @param debuggable the debuggable value of the app's manifest, or null if not set. - * @param requiredApiVersionNumber the api version required by the app, or null 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, String debugPackageName, Boolean debuggable, - String 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, - 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(NullLogger.getLogger()); - } 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 sdk against which the project is built - IAndroidTarget projectTarget = currentSdk.getTarget(project); - - // get the min required android version - ManifestInfo mi = ManifestInfo.get(project); - final int minApiLevel = mi.getMinSdkVersion(); - final String minApiCodeName = mi.getMinSdkCodeName(); - final AndroidVersion minApiVersion = new AndroidVersion(minApiLevel, minApiCodeName); - - // FIXME: check errors on missing sdk, AVD manager, or project target. - - // device chooser response object. - final DeviceChooserResponse response = new DeviceChooserResponse(); - - /* - * Launch logic: - * - Use Last Launched Device/AVD set. - * If user requested to use same device for future launches, and the last launched - * device/avd is still present, then simply launch on the same device/avd. - * - Manual 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. - * - Launch on multiple devices: - * From the currently active devices & emulators, filter out those that cannot run - * the app (by api level), and launch on all the others. - */ - IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); - if (config.mReuseLastUsedDevice) { - // check to see if the last used device is still online - IDevice lastUsedDevice = getDeviceIfOnline(config.mLastUsedDevice, - devices); - if (lastUsedDevice != null) { - response.setDeviceToUse(lastUsedDevice); - continueLaunch(response, project, launch, launchInfo, config); - return; - } - } - - if (config.mTargetMode == TargetMode.AUTO) { - // 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 (preferredAvd != null) { - IAndroidTarget preferredAvdTarget = preferredAvd.getTarget(); - if (preferredAvdTarget != null - && !preferredAvdTarget.getVersion().canRun(minApiVersion)) { - preferredAvd = null; - - AdtPlugin.printErrorToConsole(project, String.format( - "Preferred AVD '%1$s' (API Level: %2$d) cannot run application with minApi %3$s. Looking for a compatible AVD...", - config.mAvdName, - preferredAvdTarget.getVersion().getApiLevel(), - minApiVersion)); - } - } - - if (preferredAvd != null) { - // We have a preferred avd that can actually run the application. - // Now see if the AVD is running, and if so use it, otherwise launch it. - - 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. - - // If the API level requested in the manifest is lower than the current project - // target, when we will iterate devices/avds later ideally we will want to find - // a device/avd which target is as close to the manifest as possible (instead of - // a device which target is the same as the project's target) and use it as the - // new default. - - if (minApiCodeName != null && minApiLevel < projectTarget.getVersion().getApiLevel()) { - int maxDist = projectTarget.getVersion().getApiLevel() - minApiLevel; - IAndroidTarget candidate = null; - - for (IAndroidTarget target : currentSdk.getTargets()) { - if (target.canRunOn(projectTarget)) { - int currDist = target.getVersion().getApiLevel() - minApiLevel; - if (currDist >= 0 && currDist < maxDist) { - maxDist = currDist; - candidate = target; - if (maxDist == 0) { - // Found a perfect match - break; - } - } - } - } - - if (candidate != null) { - // We found a better SDK target candidate, that is closer to the - // API level from minSdkVersion than the one currently used by the - // project. Below (in the for...devices loop) we'll try to find - // a device/AVD for it. - projectTarget = candidate; - } - } - - 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 (AvdCompatibility.canRun(info, projectTarget, minApiVersion) - == AvdCompatibility.Compatibility.YES) { - compatibleRunningAvds.put(d, info); - } - } else { - if (projectTarget.isPlatform()) { // means this can run on any device as long - // as api level is high enough - AndroidVersion deviceVersion = Sdk.getDeviceVersion(d); - // the deviceVersion may be null if it wasn't yet queried (device just - // plugged in or emulator just booting up. - if (deviceVersion != null && - deviceVersion.canRun(projectTarget.getVersion())) { - // device is compatible with project - compatibleRunningAvds.put(d, null); - continue; - } - } else { - // for non project platform, we can't be sure if a device can - // run an application or not, since we don't query the device - // for the list of optional libraries that it supports. - } - 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 defaultAvd = findMatchingAvd(avdManager, projectTarget, minApiVersion); - - 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 { - AdtPlugin.printToConsole(project, String.format( - "Failed to find an AVD compatible with target '%1$s'.", - projectTarget.getName())); - - final Display display = AdtPlugin.getDisplay(); - final boolean[] searchAgain = new boolean[] { false }; - // ask the user to create a new one. - display.syncExec(new Runnable() { - @Override - public void run() { - Shell shell = display.getActiveShell(); - if (MessageDialog.openQuestion(shell, "Android AVD Error", - "No compatible targets were found. Do you wish to add a new Android Virtual Device?")) { - AvdManagerAction action = new AvdManagerAction(); - action.run(null /*action*/); - searchAgain[0] = true; - } - } - }); - if (searchAgain[0]) { - // attempt to reload the AVDs and find one compatible. - defaultAvd = findMatchingAvd(avdManager, projectTarget, minApiVersion); - - if (defaultAvd == null) { - AdtPlugin.printErrorToConsole(project, String.format( - "Still no compatible AVDs with target '%1$s': Aborting launch.", - projectTarget.getName())); - stopLaunch(launchInfo); - } else { - response.setAvdToLaunch(defaultAvd); - - AdtPlugin.printToConsole(project, String.format( - "Launching new emulator with compatible AVD '%1$s'", - defaultAvd.getName())); - - continueLaunch(response, project, launch, launchInfo, config); - 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); - } else if ((config.mTargetMode == TargetMode.ALL_DEVICES_AND_EMULATORS - || config.mTargetMode == TargetMode.ALL_DEVICES - || config.mTargetMode == TargetMode.ALL_EMULATORS) - && ILaunchManager.RUN_MODE.equals(mode)) { - // if running on multiple devices, identify all compatible devices - boolean includeDevices = config.mTargetMode != TargetMode.ALL_EMULATORS; - boolean includeAvds = config.mTargetMode != TargetMode.ALL_DEVICES; - Collection<IDevice> compatibleDevices = findCompatibleDevices(devices, - minApiVersion, includeDevices, includeAvds); - if (compatibleDevices.size() == 0) { - AdtPlugin.printErrorToConsole(project, - "No active compatible AVD's or devices found. " - + "Relaunch this configuration after connecting a device or starting an AVD."); - stopLaunch(launchInfo); - } else { - multiLaunch(launchInfo, compatibleDevices); - } - return; - } - - // bring up the device chooser. - final IAndroidTarget desiredProjectTarget = projectTarget; - final AtomicBoolean continueLaunch = new AtomicBoolean(false); - AdtPlugin.getDisplay().syncExec(new Runnable() { - @Override - 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.getShell(), - response, launchInfo.getPackageName(), - desiredProjectTarget, minApiVersion, - config.mReuseLastUsedDevice); - if (dialog.open() == Dialog.OK) { - updateLaunchConfigWithLastUsedDevice(launch.getLaunchConfiguration(), - response); - continueLaunch.set(true); - } 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); - } - } - }); - - if (continueLaunch.get()) { - continueLaunch(response, project, launch, launchInfo, config); - } - } - - /** - * Returns devices that can run a app of provided API level. - * @param devices list of devices to filter from - * @param requiredVersion minimum required API that should be supported - * @param includeDevices include physical devices in the filtered list - * @param includeAvds include emulators in the filtered list - * @return set of compatible devices, may be an empty set - */ - private Collection<IDevice> findCompatibleDevices(IDevice[] devices, - AndroidVersion requiredVersion, boolean includeDevices, boolean includeAvds) { - Set<IDevice> compatibleDevices = new HashSet<IDevice>(devices.length); - AvdManager avdManager = Sdk.getCurrent().getAvdManager(); - for (IDevice d: devices) { - boolean isEmulator = d.isEmulator(); - boolean canRun = false; - - if (isEmulator) { - if (!includeAvds) { - continue; - } - - AvdInfo avdInfo = avdManager.getAvd(d.getAvdName(), true); - if (avdInfo != null && avdInfo.getTarget() != null) { - canRun = avdInfo.getTarget().getVersion().canRun(requiredVersion); - } - } else { - if (!includeDevices) { - continue; - } - - AndroidVersion deviceVersion = Sdk.getDeviceVersion(d); - if (deviceVersion != null) { - canRun = deviceVersion.canRun(requiredVersion); - } - } - - if (canRun) { - compatibleDevices.add(d); - } - } - - return compatibleDevices; - } - - /** - * Find a matching AVD. - * @param minApiVersion - */ - private AvdInfo findMatchingAvd(AvdManager avdManager, final IAndroidTarget projectTarget, - AndroidVersion minApiVersion) { - AvdInfo[] avds = avdManager.getValidAvds(); - AvdInfo bestAvd = null; - for (AvdInfo avd : avds) { - if (AvdCompatibility.canRun(avd, projectTarget, minApiVersion) - == AvdCompatibility.Compatibility.YES) { - // at this point we can ignore the code name issue since - // AvdCompatibility.canRun() will already have filtered out the non compatible AVDs. - if (bestAvd == null || - avd.getTarget().getVersion().getApiLevel() < - bestAvd.getTarget().getVersion().getApiLevel()) { - bestAvd = avd; - } - } - } - return bestAvd; - } - - /** - * 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. - */ - private void continueLaunch(final DeviceChooserResponse response, final IProject project, - final AndroidLaunch launch, final DelayedLaunchInfo launchInfo, - final AndroidLaunchConfiguration config) { - 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()); - } - } - - /** - * 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 deviceVersion = device.getProperty(IDevice.PROP_BUILD_VERSION); - String deviceApiLevelString = device.getProperty(IDevice.PROP_BUILD_API_LEVEL); - String deviceCodeName = device.getProperty(IDevice.PROP_BUILD_CODENAME); - - int deviceApiLevel = -1; - try { - deviceApiLevel = Integer.parseInt(deviceApiLevelString); - } catch (NumberFormatException e) { - // pass, we'll keep the apiLevel value at -1. - } - - String requiredApiString = launchInfo.getRequiredApiVersionNumber(); - if (requiredApiString != null) { - int requiredApi = -1; - try { - requiredApi = Integer.parseInt(requiredApiString); - } catch (NumberFormatException e) { - // pass, we'll keep requiredApi value at -1. - } - - if (requiredApi == -1) { - // this means the manifest uses a codename for minSdkVersion - // check that the device is using the same codename - if (requiredApiString.equals(deviceCodeName) == false) { - AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format( - "ERROR: Application requires a device running '%1$s'!", - requiredApiString)); - return false; - } - } else { - // app requires a specific API level - if (deviceApiLevel == -1) { - AdtPlugin.printToConsole(launchInfo.getProject(), - "WARNING: Unknown device API version!"); - } else if (deviceApiLevel < requiredApi) { - String msg = String.format( - "ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).", - requiredApi, deviceApiLevel, deviceVersion); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg); - - // abort the launch - return false; - } - } - } else { - // warn the application 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 (deviceApiLevel == -1) { - 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)", deviceApiLevel, - deviceVersion)); - } - } - - // 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! - // launchInfo.getDebuggable() will return null if the manifest doesn't declare - // anything. In this case this is fine since the build system does insert - // debuggable=true. The only case to look for is if false is manually set - // in the manifest. - 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) { - if (!doPreLaunchActions(launchInfo, device)) { - AdtPlugin.printErrorToConsole(launchInfo.getProject(), "Launch canceled!"); - stopLaunch(launchInfo); - return false; - } - - // launch the app - launchApp(launchInfo, device); - - return true; - } - - private boolean doPreLaunchActions(DelayedLaunchInfo launchInfo, IDevice device) { - // API level check - if (!checkBuildInfo(launchInfo, device)) { - return false; - } - - // sync app - if (!syncApp(launchInfo, device)) { - return false; - } - - return true; - } - - private void multiLaunch(DelayedLaunchInfo launchInfo, Collection<IDevice> devices) { - for (IDevice d: devices) { - boolean success = doPreLaunchActions(launchInfo, d); - if (!success) { - String deviceName = d.isEmulator() ? d.getAvdName() : d.getSerialNumber(); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), - "Launch failed on device: " + deviceName); - continue; - } - } - - doLaunchAction(launchInfo, devices); - - // multiple launches are only supported for run configuration, so we can terminate - // the launch itself - stopLaunch(launchInfo); - } - - /** - * If needed, syncs the application and all its dependencies 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) { - boolean alreadyInstalled = ApkInstallManager.getInstance().isApplicationInstalled( - launchInfo.getProject(), launchInfo.getPackageName(), 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; - } - - /** - * 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 doSyncApp(DelayedLaunchInfo launchInfo, IDevice device) { - IPath path = launchInfo.getPackageFile().getLocation(); - String fileName = path.lastSegment(); - try { - String message = String.format("Uploading %1$s onto device '%2$s'", - fileName, device.getSerialNumber()); - AdtPlugin.printToConsole(launchInfo.getProject(), message); - - String remotePackagePath = device.syncPackageToDevice(path.toOSString()); - boolean installResult = installPackage(launchInfo, remotePackagePath, device); - device.removeRemotePackage(remotePackagePath); - - // if the installation succeeded, we register it. - if (installResult) { - ApkInstallManager.getInstance().registerInstallation( - launchInfo.getProject(), launchInfo.getPackageName(), device); - } - return installResult; - } - catch (IOException e) { - String msg = String.format("Failed to install %1$s on device '%2$s': %3$s", fileName, - device.getSerialNumber(), e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e); - } catch (TimeoutException e) { - String msg = String.format("Failed to install %1$s on device '%2$s': timeout", fileName, - device.getSerialNumber()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg); - } catch (AdbCommandRejectedException e) { - String msg = String.format( - "Failed to install %1$s on device '%2$s': adb rejected install command with: %3$s", - fileName, device.getSerialNumber(), e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e); - } catch (CanceledException e) { - if (e.wasCanceled()) { - AdtPlugin.printToConsole(launchInfo.getProject(), - String.format("Install of %1$s canceled", fileName)); - } else { - String msg = String.format("Failed to install %1$s on device '%2$s': %3$s", - fileName, device.getSerialNumber(), e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e); - } - } - - return false; - } - - /** - * For the current launchInfo, create additional DelayedLaunchInfo that should be used to - * sync APKs that we are dependent on to the device. - * - * @param launchInfo the original launch info that we want to find the - * @return a list of DelayedLaunchInfo (may be empty if no dependencies were found or error) - */ - public List<DelayedLaunchInfo> getDependenciesLaunchInfo(DelayedLaunchInfo launchInfo) { - 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 - ManifestData manifestData = AndroidManifestHelper.parseForData( - androidProject.getProject()); - - if (manifestData == null) { - 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(), - manifestData.getPackage(), - manifestData.getPackage(), - launchInfo.getLaunchAction(), - apk, - manifestData.getDebuggable(), - manifestData.getMinSdkVersionString(), - launchInfo.getLaunch(), - launchInfo.getMonitor()); - - // Add to the list - dependencies.add(delayedLaunchInfo); - } - - return dependencies; - } - - /** - * Installs the application package on the device, and handles return result - * @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 { - // try a reinstall first, because the most common case is the app is already installed - String result = doInstall(launchInfo, remotePath, device, true /* 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 (Exception e) { - String msg = String.format( - "Failed to install %1$s on device '%2$s!", - launchInfo.getPackageFile().getName(), device.getSerialNumber()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e.getMessage()); - } - - 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 InstallException - */ - private boolean checkInstallResult(String result, IDevice device, DelayedLaunchInfo launchInfo, - String remotePath, InstallRetryMode retryMode) throws InstallException { - if (result == null) { - AdtPlugin.printToConsole(launchInfo.getProject(), "Success!"); - return true; - } - else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$ - // this should never happen, since reinstall mode is used on the first attempt - 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")) { //$NON-NLS-1$ - if (retryMode != InstallRetryMode.NEVER) { - boolean prompt = AdtPlugin.displayPrompt("Application Install", - "Re-installation failed due to different application signatures. You must perform a full uninstall of the application. WARNING: This will remove the application data!\nDo you want to uninstall?"); - if (prompt) { - doUninstall(device, launchInfo); - String res = doInstall(launchInfo, remotePath, device, false); - return checkInstallResult(res, device, launchInfo, remotePath, - InstallRetryMode.NEVER); - } - } - 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 InstallException if the installation failed. - */ - private String doUninstall(IDevice device, DelayedLaunchInfo launchInfo) - throws InstallException { - try { - return device.uninstallPackage(launchInfo.getPackageName()); - } catch (InstallException e) { - String msg = String.format( - "Failed to uninstall %1$s: %2$s", launchInfo.getPackageName(), e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg); - throw e; - } - } - - /** - * Performs the installation of an application whose package has been uploaded on the device. - * - * @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 InstallException if the uninstallation failed. - */ - private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath, - final IDevice device, boolean reinstall) throws InstallException { - return device.installRemotePackage(remotePath, reinstall); - } - - /** - * 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 - */ - @Override - public void launchApp(final DelayedLaunchInfo info, IDevice device) { - if (info.isDebugMode()) { - synchronized (sListLock) { - if (mWaitingForDebuggerApplications.contains(info) == false) { - mWaitingForDebuggerApplications.add(info); - } - } - } - if (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 doLaunchAction(final DelayedLaunchInfo info, Collection<IDevice> devices) { - boolean result = info.getLaunchAction().doLaunchAction(info, devices); - - // Monitor the logcat output on the launched device to notify - // the user if any significant error occurs that is visible from logcat - for (IDevice d : devices) { - DdmsPlugin.getDefault().startLogCatMonitor(d); - } - - return result; - } - - private boolean doLaunchAction(final DelayedLaunchInfo info, IDevice device) { - return doLaunchAction(info, Collections.singletonList(device)); - } - - 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>(); - - String path = AdtPlugin.getOsAbsoluteEmulator(); - - list.add(path); - - 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.getShell(), - "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 - */ - @SuppressWarnings("deprecation") - 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) - */ - @Override - 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(IDevice) - */ - @Override - public void deviceConnected(IDevice 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( - AdtPrefs.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(IDevice) - */ - @Override - public void deviceDisconnected(IDevice 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(IDevice, int) - */ - @Override - public void deviceChanged(IDevice 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_STATUS}, {@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) - */ - @Override - 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(AdtPrefs.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() == DebuggerStatus.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_STATUS) == Client.CHANGE_DEBUGGER_STATUS) { - ClientData clientData = client.getClientData(); - String applicationName = client.getClientData().getClientDescription(); - if (clientData.getDebuggerConnectionStatus() == DebuggerStatus.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()); - } - } - } - - /** - * 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 output 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) - */ - @Override - public void stopLaunch(DelayedLaunchInfo launchInfo) { - launchInfo.getLaunch().stopLaunch(); - synchronized (sListLock) { - mWaitingForReadyEmulatorList.remove(launchInfo); - mWaitingForDebuggerApplications.remove(launchInfo); - } - } - - public static void updateLaunchConfigWithLastUsedDevice( - ILaunchConfiguration launchConfiguration, DeviceChooserResponse response) { - try { - boolean configModified = false; - boolean reuse = launchConfiguration.getAttribute( - LaunchConfigDelegate.ATTR_REUSE_LAST_USED_DEVICE, false); - String serial = launchConfiguration.getAttribute( - LaunchConfigDelegate.ATTR_LAST_USED_DEVICE, (String)null); - - ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy(); - if (reuse != response.useDeviceForFutureLaunches()) { - reuse = response.useDeviceForFutureLaunches(); - wc.setAttribute(LaunchConfigDelegate.ATTR_REUSE_LAST_USED_DEVICE, reuse); - configModified = true; - } - - if (reuse) { - String selected = getSerial(response); - if (selected != null && !selected.equalsIgnoreCase(serial)) { - wc.setAttribute(LaunchConfigDelegate.ATTR_LAST_USED_DEVICE, selected); - configModified = true; - } - } - - if (configModified) { - wc.doSave(); - } - } catch (CoreException e) { - // in such a case, users just won't see this setting take effect - return; - } - } - - private static String getSerial(DeviceChooserResponse response) { - AvdInfo avd = response.getAvdToLaunch(); - return (avd != null) ? avd.getName() : response.getDeviceToUse().getSerialNumber(); - } - - @Nullable - public static IDevice getDeviceIfOnline(@Nullable String serial, - @NonNull IDevice[] onlineDevices) { - if (serial == null) { - return null; - } - - for (IDevice device : onlineDevices) { - if (serial.equals(device.getAvdName()) || - serial.equals(device.getSerialNumber())) { - return device; - } - } - - return null; - } -} |