Home | History | Annotate | Download | only in launch
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.launch;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.annotations.Nullable;
     21 import com.android.ddmlib.AdbCommandRejectedException;
     22 import com.android.ddmlib.AndroidDebugBridge;
     23 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
     24 import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
     25 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
     26 import com.android.ddmlib.CanceledException;
     27 import com.android.ddmlib.Client;
     28 import com.android.ddmlib.ClientData;
     29 import com.android.ddmlib.ClientData.DebuggerStatus;
     30 import com.android.ddmlib.IDevice;
     31 import com.android.ddmlib.InstallException;
     32 import com.android.ddmlib.Log;
     33 import com.android.ddmlib.TimeoutException;
     34 import com.android.ide.common.xml.ManifestData;
     35 import com.android.ide.eclipse.adt.AdtPlugin;
     36 import com.android.ide.eclipse.adt.internal.actions.AvdManagerAction;
     37 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     38 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode;
     39 import com.android.ide.eclipse.adt.internal.launch.DelayedLaunchInfo.InstallRetryMode;
     40 import com.android.ide.eclipse.adt.internal.launch.DeviceChooserDialog.DeviceChooserResponse;
     41 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     42 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     43 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
     44 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     45 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
     46 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     47 import com.android.ide.eclipse.ddms.DdmsPlugin;
     48 import com.android.prefs.AndroidLocation.AndroidLocationException;
     49 import com.android.sdklib.AndroidVersion;
     50 import com.android.sdklib.IAndroidTarget;
     51 import com.android.sdklib.internal.avd.AvdInfo;
     52 import com.android.sdklib.internal.avd.AvdManager;
     53 import com.android.utils.NullLogger;
     54 
     55 import org.eclipse.core.resources.IFile;
     56 import org.eclipse.core.resources.IProject;
     57 import org.eclipse.core.resources.IResource;
     58 import org.eclipse.core.runtime.CoreException;
     59 import org.eclipse.core.runtime.IPath;
     60 import org.eclipse.core.runtime.IProgressMonitor;
     61 import org.eclipse.debug.core.DebugPlugin;
     62 import org.eclipse.debug.core.ILaunchConfiguration;
     63 import org.eclipse.debug.core.ILaunchConfigurationType;
     64 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
     65 import org.eclipse.debug.core.ILaunchManager;
     66 import org.eclipse.debug.core.model.IDebugTarget;
     67 import org.eclipse.debug.ui.DebugUITools;
     68 import org.eclipse.jdt.core.IJavaProject;
     69 import org.eclipse.jdt.core.JavaModelException;
     70 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
     71 import org.eclipse.jdt.launching.IVMConnector;
     72 import org.eclipse.jdt.launching.JavaRuntime;
     73 import org.eclipse.jface.dialogs.Dialog;
     74 import org.eclipse.jface.dialogs.MessageDialog;
     75 import org.eclipse.jface.preference.IPreferenceStore;
     76 import org.eclipse.swt.widgets.Display;
     77 import org.eclipse.swt.widgets.Shell;
     78 
     79 import java.io.BufferedReader;
     80 import java.io.IOException;
     81 import java.io.InputStreamReader;
     82 import java.util.ArrayList;
     83 import java.util.Collection;
     84 import java.util.Collections;
     85 import java.util.HashMap;
     86 import java.util.HashSet;
     87 import java.util.List;
     88 import java.util.Map.Entry;
     89 import java.util.Set;
     90 import java.util.concurrent.atomic.AtomicBoolean;
     91 
     92 /**
     93  * Controls the launch of Android application either on a device or on the
     94  * emulator. If an emulator is already running, this class will attempt to reuse
     95  * it.
     96  */
     97 public final class AndroidLaunchController implements IDebugBridgeChangeListener,
     98         IDeviceChangeListener, IClientChangeListener, ILaunchController {
     99 
    100     private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$
    101     private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$
    102     private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$
    103     private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$
    104     private static final String FLAG_NO_BOOT_ANIM = "-no-boot-anim"; //$NON-NLS-1$
    105 
    106     /**
    107      * Map to store {@link ILaunchConfiguration} objects that must be launched as simple connection
    108      * to running application. The integer is the port on which to connect.
    109      * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
    110      */
    111     private static final HashMap<ILaunchConfiguration, Integer> sRunningAppMap =
    112         new HashMap<ILaunchConfiguration, Integer>();
    113 
    114     private static final Object sListLock = sRunningAppMap;
    115 
    116     /**
    117      * List of {@link DelayedLaunchInfo} waiting for an emulator to connect.
    118      * <p>Once an emulator has connected, {@link DelayedLaunchInfo#getDevice()} is set and the
    119      * DelayedLaunchInfo object is moved to
    120      * {@link AndroidLaunchController#mWaitingForReadyEmulatorList}.
    121      * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
    122      */
    123     private final ArrayList<DelayedLaunchInfo> mWaitingForEmulatorLaunches =
    124         new ArrayList<DelayedLaunchInfo>();
    125 
    126     /**
    127      * List of application waiting to be launched on a device/emulator.<br>
    128      * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
    129      * */
    130     private final ArrayList<DelayedLaunchInfo> mWaitingForReadyEmulatorList =
    131         new ArrayList<DelayedLaunchInfo>();
    132 
    133     /**
    134      * Application waiting to show up as waiting for debugger.
    135      * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
    136      */
    137     private final ArrayList<DelayedLaunchInfo> mWaitingForDebuggerApplications =
    138         new ArrayList<DelayedLaunchInfo>();
    139 
    140     /**
    141      * List of clients that have appeared as waiting for debugger before their name was available.
    142      * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
    143      */
    144     private final ArrayList<Client> mUnknownClientsWaitingForDebugger = new ArrayList<Client>();
    145 
    146     /** static instance for singleton */
    147     private static AndroidLaunchController sThis = new AndroidLaunchController();
    148 
    149     /** private constructor to enforce singleton */
    150     private AndroidLaunchController() {
    151         AndroidDebugBridge.addDebugBridgeChangeListener(this);
    152         AndroidDebugBridge.addDeviceChangeListener(this);
    153         AndroidDebugBridge.addClientChangeListener(this);
    154     }
    155 
    156     /**
    157      * Returns the singleton reference.
    158      */
    159     public static AndroidLaunchController getInstance() {
    160         return sThis;
    161     }
    162 
    163 
    164     /**
    165      * Launches a remote java debugging session on an already running application
    166      * @param project The project of the application to debug.
    167      * @param debugPort The port to connect the debugger to.
    168      */
    169     public static void debugRunningApp(IProject project, int debugPort) {
    170         // get an existing or new launch configuration
    171         ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project,
    172                 LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID);
    173 
    174         if (config != null) {
    175             setPortLaunchConfigAssociation(config, debugPort);
    176 
    177             // and launch
    178             DebugUITools.launch(config, ILaunchManager.DEBUG_MODE);
    179         }
    180     }
    181 
    182     /**
    183      * Returns an {@link ILaunchConfiguration} for the specified {@link IProject}.
    184      * @param project the project
    185      * @param launchTypeId launch delegate type id
    186      * @return a new or already existing <code>ILaunchConfiguration</code> or null if there was
    187      * an error when creating a new one.
    188      */
    189     public static ILaunchConfiguration getLaunchConfig(IProject project, String launchTypeId) {
    190         // get the launch manager
    191         ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
    192 
    193         // now get the config type for our particular android type.
    194         ILaunchConfigurationType configType = manager.getLaunchConfigurationType(launchTypeId);
    195 
    196         String name = project.getName();
    197 
    198         // search for an existing launch configuration
    199         ILaunchConfiguration config = findConfig(manager, configType, name);
    200 
    201         // test if we found one or not
    202         if (config == null) {
    203             // Didn't find a matching config, so we make one.
    204             // It'll be made in the "working copy" object first.
    205             ILaunchConfigurationWorkingCopy wc = null;
    206 
    207             try {
    208                 // make the working copy object
    209                 wc = configType.newInstance(null,
    210                         manager.generateLaunchConfigurationName(name));
    211 
    212                 // set the project name
    213                 wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name);
    214 
    215                 // set the launch mode to default.
    216                 wc.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION,
    217                         LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION);
    218 
    219                 // set default target mode
    220                 wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
    221                         LaunchConfigDelegate.DEFAULT_TARGET_MODE.toString());
    222 
    223                 // default AVD: None
    224                 wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String) null);
    225 
    226                 // set the default network speed
    227                 wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
    228                         LaunchConfigDelegate.DEFAULT_SPEED);
    229 
    230                 // and delay
    231                 wc.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
    232                         LaunchConfigDelegate.DEFAULT_DELAY);
    233 
    234                 // default wipe data mode
    235                 wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
    236                         LaunchConfigDelegate.DEFAULT_WIPE_DATA);
    237 
    238                 // default disable boot animation option
    239                 wc.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM,
    240                         LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM);
    241 
    242                 // set default emulator options
    243                 IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
    244                 String emuOptions = store.getString(AdtPrefs.PREFS_EMU_OPTIONS);
    245                 wc.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions);
    246 
    247                 // map the config and the project
    248                 wc.setMappedResources(getResourcesToMap(project));
    249 
    250                 // save the working copy to get the launch config object which we return.
    251                 return wc.doSave();
    252 
    253             } catch (CoreException e) {
    254                 String msg = String.format(
    255                         "Failed to create a Launch config for project '%1$s': %2$s",
    256                         project.getName(), e.getMessage());
    257                 AdtPlugin.printErrorToConsole(project, msg);
    258 
    259                 // no launch!
    260                 return null;
    261             }
    262         }
    263 
    264         return config;
    265     }
    266 
    267     /**
    268      * Returns the list of resources to map to a Launch Configuration.
    269      * @param project the project associated to the launch configuration.
    270      */
    271     public static IResource[] getResourcesToMap(IProject project) {
    272         ArrayList<IResource> array = new ArrayList<IResource>(2);
    273         array.add(project);
    274 
    275         IFile manifest = ProjectHelper.getManifest(project);
    276         if (manifest != null) {
    277             array.add(manifest);
    278         }
    279 
    280         return array.toArray(new IResource[array.size()]);
    281     }
    282 
    283     /**
    284      * Launches an android app on the device or emulator
    285      *
    286      * @param project The project we're launching
    287      * @param mode the mode in which to launch, one of the mode constants
    288      *      defined by <code>ILaunchManager</code> - <code>RUN_MODE</code> or
    289      *      <code>DEBUG_MODE</code>.
    290      * @param apk the resource to the apk to launch.
    291      * @param packageName the Android package name of the app
    292      * @param debugPackageName the Android package name to debug
    293      * @param debuggable the debuggable value of the app's manifest, or null if not set.
    294      * @param requiredApiVersionNumber the api version required by the app, or null if none.
    295      * @param launchAction the action to perform after app sync
    296      * @param config the launch configuration
    297      * @param launch the launch object
    298      */
    299     public void launch(final IProject project, String mode, IFile apk,
    300             String packageName, String debugPackageName, Boolean debuggable,
    301             String requiredApiVersionNumber, final IAndroidLaunchAction launchAction,
    302             final AndroidLaunchConfiguration config, final AndroidLaunch launch,
    303             IProgressMonitor monitor) {
    304 
    305         String message = String.format("Performing %1$s", launchAction.getLaunchDescription());
    306         AdtPlugin.printToConsole(project, message);
    307 
    308         // create the launch info
    309         final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
    310                 debugPackageName, launchAction, apk, debuggable, requiredApiVersionNumber, launch,
    311                 monitor);
    312 
    313         // set the debug mode
    314         launchInfo.setDebugMode(mode.equals(ILaunchManager.DEBUG_MODE));
    315 
    316         // get the SDK
    317         Sdk currentSdk = Sdk.getCurrent();
    318         AvdManager avdManager = currentSdk.getAvdManager();
    319 
    320         // reload the AVDs to make sure we are up to date
    321         try {
    322             avdManager.reloadAvds(NullLogger.getLogger());
    323         } catch (AndroidLocationException e1) {
    324             // this happens if the AVD Manager failed to find the folder in which the AVDs are
    325             // stored. This is unlikely to happen, but if it does, we should force to go manual
    326             // to allow using physical devices.
    327             config.mTargetMode = TargetMode.MANUAL;
    328         }
    329 
    330         // get the sdk against which the project is built
    331         IAndroidTarget projectTarget = currentSdk.getTarget(project);
    332 
    333         // get the min required android version
    334         ManifestInfo mi = ManifestInfo.get(project);
    335         final int minApiLevel = mi.getMinSdkVersion();
    336         final String minApiCodeName = mi.getMinSdkCodeName();
    337         final AndroidVersion minApiVersion = new AndroidVersion(minApiLevel, minApiCodeName);
    338 
    339         // FIXME: check errors on missing sdk, AVD manager, or project target.
    340 
    341         // device chooser response object.
    342         final DeviceChooserResponse response = new DeviceChooserResponse();
    343 
    344         /*
    345          * Launch logic:
    346          * - Use Last Launched Device/AVD set.
    347          *       If user requested to use same device for future launches, and the last launched
    348          *       device/avd is still present, then simply launch on the same device/avd.
    349          * - Manual Mode
    350          *       Always display a UI that lets a user see the current running emulators/devices.
    351          *       The UI must show which devices are compatibles, and allow launching new emulators
    352          *       with compatible (and not yet running) AVD.
    353          * - Automatic Way
    354          *     * Preferred AVD set.
    355          *           If Preferred AVD is not running: launch it.
    356          *           Launch the application on the preferred AVD.
    357          *     * No preferred AVD.
    358          *           Count the number of compatible emulators/devices.
    359          *           If != 1, display a UI similar to manual mode.
    360          *           If == 1, launch the application on this AVD/device.
    361          * - Launch on multiple devices:
    362          *     From the currently active devices & emulators, filter out those that cannot run
    363          *     the app (by api level), and launch on all the others.
    364          */
    365         IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
    366         if (config.mReuseLastUsedDevice) {
    367             // check to see if the last used device is still online
    368             IDevice lastUsedDevice = getDeviceIfOnline(config.mLastUsedDevice,
    369                     devices);
    370             if (lastUsedDevice != null) {
    371                 response.setDeviceToUse(lastUsedDevice);
    372                 continueLaunch(response, project, launch, launchInfo, config);
    373                 return;
    374             }
    375         }
    376 
    377         if (config.mTargetMode == TargetMode.AUTO) {
    378             // first check if we have a preferred AVD name, and if it actually exists, and is valid
    379             // (ie able to run the project).
    380             // We need to check this in case the AVD was recreated with a different target that is
    381             // not compatible.
    382             AvdInfo preferredAvd = null;
    383             if (config.mAvdName != null) {
    384                 preferredAvd = avdManager.getAvd(config.mAvdName, true /*validAvdOnly*/);
    385             }
    386 
    387             if (preferredAvd != null) {
    388                 IAndroidTarget preferredAvdTarget = preferredAvd.getTarget();
    389                 if (preferredAvdTarget != null
    390                         && !preferredAvdTarget.getVersion().canRun(minApiVersion)) {
    391                     preferredAvd = null;
    392 
    393                     AdtPlugin.printErrorToConsole(project, String.format(
    394                             "Preferred AVD '%1$s' (API Level: %2$d) cannot run application with minApi %3$s. Looking for a compatible AVD...",
    395                             config.mAvdName,
    396                             preferredAvdTarget.getVersion().getApiLevel(),
    397                             minApiVersion));
    398                 }
    399             }
    400 
    401             if (preferredAvd != null) {
    402                 // We have a preferred avd that can actually run the application.
    403                 // Now see if the AVD is running, and if so use it, otherwise launch it.
    404 
    405                 for (IDevice d : devices) {
    406                     String deviceAvd = d.getAvdName();
    407                     if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) {
    408                         response.setDeviceToUse(d);
    409 
    410                         AdtPlugin.printToConsole(project, String.format(
    411                                 "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'",
    412                                 config.mAvdName, d));
    413 
    414                         continueLaunch(response, project, launch, launchInfo, config);
    415                         return;
    416                     }
    417                 }
    418 
    419                 // at this point we have a valid preferred AVD that is not running.
    420                 // We need to start it.
    421                 response.setAvdToLaunch(preferredAvd);
    422 
    423                 AdtPlugin.printToConsole(project, String.format(
    424                         "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.",
    425                         config.mAvdName));
    426 
    427                 continueLaunch(response, project, launch, launchInfo, config);
    428                 return;
    429             }
    430 
    431             // no (valid) preferred AVD? look for one.
    432 
    433             // If the API level requested in the manifest is lower than the current project
    434             // target, when we will iterate devices/avds later ideally we will want to find
    435             // a device/avd which target is as close to the manifest as possible (instead of
    436             // a device which target is the same as the project's target) and use it as the
    437             // new default.
    438 
    439             if (minApiCodeName != null && minApiLevel < projectTarget.getVersion().getApiLevel()) {
    440                 int maxDist = projectTarget.getVersion().getApiLevel() - minApiLevel;
    441                 IAndroidTarget candidate = null;
    442 
    443                 for (IAndroidTarget target : currentSdk.getTargets()) {
    444                     if (target.canRunOn(projectTarget)) {
    445                         int currDist = target.getVersion().getApiLevel() - minApiLevel;
    446                         if (currDist >= 0 && currDist < maxDist) {
    447                             maxDist = currDist;
    448                             candidate = target;
    449                             if (maxDist == 0) {
    450                                 // Found a perfect match
    451                                 break;
    452                             }
    453                         }
    454                     }
    455                 }
    456 
    457                 if (candidate != null) {
    458                     // We found a better SDK target candidate, that is closer to the
    459                     // API level from minSdkVersion than the one currently used by the
    460                     // project. Below (in the for...devices loop) we'll try to find
    461                     // a device/AVD for it.
    462                     projectTarget = candidate;
    463                 }
    464             }
    465 
    466             HashMap<IDevice, AvdInfo> compatibleRunningAvds = new HashMap<IDevice, AvdInfo>();
    467             boolean hasDevice = false; // if there's 1+ device running, we may force manual mode,
    468                                        // as we cannot always detect proper compatibility with
    469                                        // devices. This is the case if the project target is not
    470                                        // a standard platform
    471             for (IDevice d : devices) {
    472                 String deviceAvd = d.getAvdName();
    473                 if (deviceAvd != null) { // physical devices return null.
    474                     AvdInfo info = avdManager.getAvd(deviceAvd, true /*validAvdOnly*/);
    475                     if (AvdCompatibility.canRun(info, projectTarget, minApiVersion)
    476                             == AvdCompatibility.Compatibility.YES) {
    477                         compatibleRunningAvds.put(d, info);
    478                     }
    479                 } else {
    480                     if (projectTarget.isPlatform()) { // means this can run on any device as long
    481                                                       // as api level is high enough
    482                         AndroidVersion deviceVersion = Sdk.getDeviceVersion(d);
    483                         // the deviceVersion may be null if it wasn't yet queried (device just
    484                         // plugged in or emulator just booting up.
    485                         if (deviceVersion != null &&
    486                                 deviceVersion.canRun(projectTarget.getVersion())) {
    487                             // device is compatible with project
    488                             compatibleRunningAvds.put(d, null);
    489                             continue;
    490                         }
    491                     } else {
    492                         // for non project platform, we can't be sure if a device can
    493                         // run an application or not, since we don't query the device
    494                         // for the list of optional libraries that it supports.
    495                     }
    496                     hasDevice = true;
    497                 }
    498             }
    499 
    500             // depending on the number of devices, we'll simulate an automatic choice
    501             // from the device chooser or simply show up the device chooser.
    502             if (hasDevice == false && compatibleRunningAvds.size() == 0) {
    503                 // if zero emulators/devices, we launch an emulator.
    504                 // We need to figure out which AVD first.
    505 
    506                 // we are going to take the closest AVD. ie a compatible AVD that has the API level
    507                 // closest to the project target.
    508                 AvdInfo defaultAvd = findMatchingAvd(avdManager, projectTarget, minApiVersion);
    509 
    510                 if (defaultAvd != null) {
    511                     response.setAvdToLaunch(defaultAvd);
    512 
    513                     AdtPlugin.printToConsole(project, String.format(
    514                             "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'",
    515                             defaultAvd.getName()));
    516 
    517                     continueLaunch(response, project, launch, launchInfo, config);
    518                     return;
    519                 } else {
    520                     AdtPlugin.printToConsole(project, String.format(
    521                             "Failed to find an AVD compatible with target '%1$s'.",
    522                             projectTarget.getName()));
    523 
    524                     final Display display = AdtPlugin.getDisplay();
    525                     final boolean[] searchAgain = new boolean[] { false };
    526                     // ask the user to create a new one.
    527                     display.syncExec(new Runnable() {
    528                         @Override
    529                         public void run() {
    530                             Shell shell = display.getActiveShell();
    531                             if (MessageDialog.openQuestion(shell, "Android AVD Error",
    532                                     "No compatible targets were found. Do you wish to add a new Android Virtual Device?")) {
    533                                 AvdManagerAction action = new AvdManagerAction();
    534                                 action.run(null /*action*/);
    535                                 searchAgain[0] = true;
    536                             }
    537                         }
    538                     });
    539                     if (searchAgain[0]) {
    540                         // attempt to reload the AVDs and find one compatible.
    541                         defaultAvd = findMatchingAvd(avdManager, projectTarget, minApiVersion);
    542 
    543                         if (defaultAvd == null) {
    544                             AdtPlugin.printErrorToConsole(project, String.format(
    545                                     "Still no compatible AVDs with target '%1$s': Aborting launch.",
    546                                     projectTarget.getName()));
    547                             stopLaunch(launchInfo);
    548                         } else {
    549                             response.setAvdToLaunch(defaultAvd);
    550 
    551                             AdtPlugin.printToConsole(project, String.format(
    552                                     "Launching new emulator with compatible AVD '%1$s'",
    553                                     defaultAvd.getName()));
    554 
    555                             continueLaunch(response, project, launch, launchInfo, config);
    556                             return;
    557                         }
    558                     }
    559                 }
    560             } else if (hasDevice == false && compatibleRunningAvds.size() == 1) {
    561                 Entry<IDevice, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next();
    562                 response.setDeviceToUse(e.getKey());
    563 
    564                 // get the AvdInfo, if null, the device is a physical device.
    565                 AvdInfo avdInfo = e.getValue();
    566                 if (avdInfo != null) {
    567                     message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'",
    568                             response.getDeviceToUse(), e.getValue().getName());
    569                 } else {
    570                     message = String.format("Automatic Target Mode: using device '%1$s'",
    571                             response.getDeviceToUse());
    572                 }
    573                 AdtPlugin.printToConsole(project, message);
    574 
    575                 continueLaunch(response, project, launch, launchInfo, config);
    576                 return;
    577             }
    578 
    579             // if more than one device, we'll bring up the DeviceChooser dialog below.
    580             if (compatibleRunningAvds.size() >= 2) {
    581                 message = "Automatic Target Mode: Several compatible targets. Please select a target device.";
    582             } else if (hasDevice) {
    583                 message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device.";
    584             }
    585 
    586             AdtPlugin.printToConsole(project, message);
    587         } else if ((config.mTargetMode == TargetMode.ALL_DEVICES_AND_EMULATORS
    588                 || config.mTargetMode == TargetMode.ALL_DEVICES
    589                 || config.mTargetMode == TargetMode.ALL_EMULATORS)
    590                 && ILaunchManager.RUN_MODE.equals(mode)) {
    591             // if running on multiple devices, identify all compatible devices
    592             boolean includeDevices = config.mTargetMode != TargetMode.ALL_EMULATORS;
    593             boolean includeAvds = config.mTargetMode != TargetMode.ALL_DEVICES;
    594             Collection<IDevice> compatibleDevices = findCompatibleDevices(devices,
    595                     minApiVersion, includeDevices, includeAvds);
    596             if (compatibleDevices.size() == 0) {
    597                 AdtPlugin.printErrorToConsole(project,
    598                       "No active compatible AVD's or devices found. "
    599                     + "Relaunch this configuration after connecting a device or starting an AVD.");
    600                 stopLaunch(launchInfo);
    601             } else {
    602                 multiLaunch(launchInfo, compatibleDevices);
    603             }
    604             return;
    605         }
    606 
    607         // bring up the device chooser.
    608         final IAndroidTarget desiredProjectTarget = projectTarget;
    609         final AtomicBoolean continueLaunch = new AtomicBoolean(false);
    610         AdtPlugin.getDisplay().syncExec(new Runnable() {
    611             @Override
    612             public void run() {
    613                 try {
    614                     // open the chooser dialog. It'll fill 'response' with the device to use
    615                     // or the AVD to launch.
    616                     DeviceChooserDialog dialog = new DeviceChooserDialog(
    617                             AdtPlugin.getShell(),
    618                             response, launchInfo.getPackageName(),
    619                             desiredProjectTarget, minApiVersion,
    620                             config.mReuseLastUsedDevice);
    621                     if (dialog.open() == Dialog.OK) {
    622                         updateLaunchConfigWithLastUsedDevice(launch.getLaunchConfiguration(),
    623                                 response);
    624                         continueLaunch.set(true);
    625                     } else {
    626                         AdtPlugin.printErrorToConsole(project, "Launch canceled!");
    627                         stopLaunch(launchInfo);
    628                         return;
    629                     }
    630                 } catch (Exception e) {
    631                     // there seems to be some case where the shell will be null. (might be
    632                     // an OS X bug). Because of this the creation of the dialog will throw
    633                     // and IllegalArg exception interrupting the launch with no user feedback.
    634                     // So we trap all the exception and display something.
    635                     String msg = e.getMessage();
    636                     if (msg == null) {
    637                         msg = e.getClass().getCanonicalName();
    638                     }
    639                     AdtPlugin.printErrorToConsole(project,
    640                             String.format("Error during launch: %s", msg));
    641                     stopLaunch(launchInfo);
    642                 }
    643             }
    644         });
    645 
    646         if (continueLaunch.get()) {
    647             continueLaunch(response, project, launch, launchInfo, config);
    648         }
    649     }
    650 
    651     /**
    652      * Returns devices that can run a app of provided API level.
    653      * @param devices list of devices to filter from
    654      * @param requiredVersion minimum required API that should be supported
    655      * @param includeDevices include physical devices in the filtered list
    656      * @param includeAvds include emulators in the filtered list
    657      * @return set of compatible devices, may be an empty set
    658      */
    659     private Collection<IDevice> findCompatibleDevices(IDevice[] devices,
    660             AndroidVersion requiredVersion, boolean includeDevices, boolean includeAvds) {
    661         Set<IDevice> compatibleDevices = new HashSet<IDevice>(devices.length);
    662         AvdManager avdManager = Sdk.getCurrent().getAvdManager();
    663         for (IDevice d: devices) {
    664             boolean isEmulator = d.isEmulator();
    665             boolean canRun = false;
    666 
    667             if (isEmulator) {
    668                 if (!includeAvds) {
    669                     continue;
    670                 }
    671 
    672                 AvdInfo avdInfo = avdManager.getAvd(d.getAvdName(), true);
    673                 if (avdInfo != null && avdInfo.getTarget() != null) {
    674                     canRun = avdInfo.getTarget().getVersion().canRun(requiredVersion);
    675                 }
    676             } else {
    677                 if (!includeDevices) {
    678                     continue;
    679                 }
    680 
    681                 AndroidVersion deviceVersion = Sdk.getDeviceVersion(d);
    682                 if (deviceVersion != null) {
    683                     canRun = deviceVersion.canRun(requiredVersion);
    684                 }
    685             }
    686 
    687             if (canRun) {
    688                 compatibleDevices.add(d);
    689             }
    690         }
    691 
    692         return compatibleDevices;
    693     }
    694 
    695     /**
    696      * Find a matching AVD.
    697      * @param minApiVersion
    698      */
    699     private AvdInfo findMatchingAvd(AvdManager avdManager, final IAndroidTarget projectTarget,
    700             AndroidVersion minApiVersion) {
    701         AvdInfo[] avds = avdManager.getValidAvds();
    702         AvdInfo bestAvd = null;
    703         for (AvdInfo avd : avds) {
    704             if (AvdCompatibility.canRun(avd, projectTarget, minApiVersion)
    705                     == AvdCompatibility.Compatibility.YES) {
    706                 // at this point we can ignore the code name issue since
    707                 // AvdCompatibility.canRun() will already have filtered out the non compatible AVDs.
    708                 if (bestAvd == null ||
    709                         avd.getTarget().getVersion().getApiLevel() <
    710                             bestAvd.getTarget().getVersion().getApiLevel()) {
    711                     bestAvd = avd;
    712                 }
    713             }
    714         }
    715         return bestAvd;
    716     }
    717 
    718     /**
    719      * Continues the launch based on the DeviceChooser response.
    720      * @param response the device chooser response
    721      * @param project The project being launched
    722      * @param launch The eclipse launch info
    723      * @param launchInfo The {@link DelayedLaunchInfo}
    724      * @param config The config needed to start a new emulator.
    725      */
    726     private void continueLaunch(final DeviceChooserResponse response, final IProject project,
    727             final AndroidLaunch launch, final DelayedLaunchInfo launchInfo,
    728             final AndroidLaunchConfiguration config) {
    729         if (response.getAvdToLaunch() != null) {
    730             // there was no selected device, we start a new emulator.
    731             synchronized (sListLock) {
    732                 AvdInfo info = response.getAvdToLaunch();
    733                 mWaitingForEmulatorLaunches.add(launchInfo);
    734                 AdtPlugin.printToConsole(project, String.format(
    735                         "Launching a new emulator with Virtual Device '%1$s'",
    736                         info.getName()));
    737                 boolean status = launchEmulator(config, info);
    738 
    739                 if (status == false) {
    740                     // launching the emulator failed!
    741                     AdtPlugin.displayError("Emulator Launch",
    742                             "Couldn't launch the emulator! Make sure the SDK directory is properly setup and the emulator is not missing.");
    743 
    744                     // stop the launch and return
    745                     mWaitingForEmulatorLaunches.remove(launchInfo);
    746                     AdtPlugin.printErrorToConsole(project, "Launch canceled!");
    747                     stopLaunch(launchInfo);
    748                     return;
    749                 }
    750 
    751                 return;
    752             }
    753         } else if (response.getDeviceToUse() != null) {
    754             launchInfo.setDevice(response.getDeviceToUse());
    755             simpleLaunch(launchInfo, launchInfo.getDevice());
    756         }
    757     }
    758 
    759     /**
    760      * Queries for a debugger port for a specific {@link ILaunchConfiguration}.
    761      * <p/>
    762      * If the configuration and a debugger port where added through
    763      * {@link #setPortLaunchConfigAssociation(ILaunchConfiguration, int)}, then this method
    764      * will return the debugger port, and remove the configuration from the list.
    765      * @param launchConfig the {@link ILaunchConfiguration}
    766      * @return the debugger port or {@link LaunchConfigDelegate#INVALID_DEBUG_PORT} if the
    767      * configuration was not setup.
    768      */
    769     static int getPortForConfig(ILaunchConfiguration launchConfig) {
    770         synchronized (sListLock) {
    771             Integer port = sRunningAppMap.get(launchConfig);
    772             if (port != null) {
    773                 sRunningAppMap.remove(launchConfig);
    774                 return port;
    775             }
    776         }
    777 
    778         return LaunchConfigDelegate.INVALID_DEBUG_PORT;
    779     }
    780 
    781     /**
    782      * Set a {@link ILaunchConfiguration} and its associated debug port, in the list of
    783      * launch config to connect directly to a running app instead of doing full launch (sync,
    784      * launch, and connect to).
    785      * @param launchConfig the {@link ILaunchConfiguration} object.
    786      * @param port The debugger port to connect to.
    787      */
    788     private static void setPortLaunchConfigAssociation(ILaunchConfiguration launchConfig,
    789             int port) {
    790         synchronized (sListLock) {
    791             sRunningAppMap.put(launchConfig, port);
    792         }
    793     }
    794 
    795     /**
    796      * Checks the build information, and returns whether the launch should continue.
    797      * <p/>The value tested are:
    798      * <ul>
    799      * <li>Minimum API version requested by the application. If the target device does not match,
    800      * the launch is canceled.</li>
    801      * <li>Debuggable attribute of the application and whether or not the device requires it. If
    802      * the device requires it and it is not set in the manifest, the launch will be forced to
    803      * "release" mode instead of "debug"</li>
    804      * <ul>
    805      */
    806     private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, IDevice device) {
    807         if (device != null) {
    808             // check the app required API level versus the target device API level
    809 
    810             String deviceVersion = device.getProperty(IDevice.PROP_BUILD_VERSION);
    811             String deviceApiLevelString = device.getProperty(IDevice.PROP_BUILD_API_LEVEL);
    812             String deviceCodeName = device.getProperty(IDevice.PROP_BUILD_CODENAME);
    813 
    814             int deviceApiLevel = -1;
    815             try {
    816                 deviceApiLevel = Integer.parseInt(deviceApiLevelString);
    817             } catch (NumberFormatException e) {
    818                 // pass, we'll keep the apiLevel value at -1.
    819             }
    820 
    821             String requiredApiString = launchInfo.getRequiredApiVersionNumber();
    822             if (requiredApiString != null) {
    823                 int requiredApi = -1;
    824                 try {
    825                     requiredApi = Integer.parseInt(requiredApiString);
    826                 } catch (NumberFormatException e) {
    827                     // pass, we'll keep requiredApi value at -1.
    828                 }
    829 
    830                 if (requiredApi == -1) {
    831                     // this means the manifest uses a codename for minSdkVersion
    832                     // check that the device is using the same codename
    833                     if (requiredApiString.equals(deviceCodeName) == false) {
    834                         AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format(
    835                             "ERROR: Application requires a device running '%1$s'!",
    836                             requiredApiString));
    837                         return false;
    838                     }
    839                 } else {
    840                     // app requires a specific API level
    841                     if (deviceApiLevel == -1) {
    842                         AdtPlugin.printToConsole(launchInfo.getProject(),
    843                                 "WARNING: Unknown device API version!");
    844                     } else if (deviceApiLevel < requiredApi) {
    845                         String msg = String.format(
    846                                 "ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).",
    847                                 requiredApi, deviceApiLevel, deviceVersion);
    848                         AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
    849 
    850                         // abort the launch
    851                         return false;
    852                     }
    853                 }
    854             } else {
    855                 // warn the application API level requirement is not set.
    856                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
    857                         "WARNING: Application does not specify an API level requirement!");
    858 
    859                 // and display the target device API level (if known)
    860                 if (deviceApiLevel == -1) {
    861                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
    862                             "WARNING: Unknown device API version!");
    863                 } else {
    864                     AdtPlugin.printErrorToConsole(launchInfo.getProject(), String.format(
    865                             "Device API version is %1$d (Android %2$s)", deviceApiLevel,
    866                             deviceVersion));
    867                 }
    868             }
    869 
    870             // now checks that the device/app can be debugged (if needed)
    871             if (device.isEmulator() == false && launchInfo.isDebugMode()) {
    872                 String debuggableDevice = device.getProperty(IDevice.PROP_DEBUGGABLE);
    873                 if (debuggableDevice != null && debuggableDevice.equals("0")) { //$NON-NLS-1$
    874                     // the device is "secure" and requires apps to declare themselves as debuggable!
    875                     // launchInfo.getDebuggable() will return null if the manifest doesn't declare
    876                     // anything. In this case this is fine since the build system does insert
    877                     // debuggable=true. The only case to look for is if false is manually set
    878                     // in the manifest.
    879                     if (launchInfo.getDebuggable() == Boolean.FALSE) {
    880                         String message = String.format("Application '%1$s' has its 'debuggable' attribute set to FALSE and cannot be debugged.",
    881                                 launchInfo.getPackageName());
    882                         AdtPlugin.printErrorToConsole(launchInfo.getProject(), message);
    883 
    884                         // because am -D does not check for ro.debuggable and the
    885                         // 'debuggable' attribute, it is important we do not use the -D option
    886                         // in this case or the app will wait for a debugger forever and never
    887                         // really launch.
    888                         launchInfo.setDebugMode(false);
    889                     }
    890                 }
    891             }
    892         }
    893 
    894         return true;
    895     }
    896 
    897     /**
    898      * Do a simple launch on the specified device, attempting to sync the new
    899      * package, and then launching the application. Failed sync/launch will
    900      * stop the current AndroidLaunch and return false;
    901      * @param launchInfo
    902      * @param device
    903      * @return true if succeed
    904      */
    905     private boolean simpleLaunch(DelayedLaunchInfo launchInfo, IDevice device) {
    906         if (!doPreLaunchActions(launchInfo, device)) {
    907             AdtPlugin.printErrorToConsole(launchInfo.getProject(), "Launch canceled!");
    908             stopLaunch(launchInfo);
    909             return false;
    910         }
    911 
    912         // launch the app
    913         launchApp(launchInfo, device);
    914 
    915         return true;
    916     }
    917 
    918     private boolean doPreLaunchActions(DelayedLaunchInfo launchInfo, IDevice device) {
    919         // API level check
    920         if (!checkBuildInfo(launchInfo, device)) {
    921             return false;
    922         }
    923 
    924         // sync app
    925         if (!syncApp(launchInfo, device)) {
    926             return false;
    927         }
    928 
    929         return true;
    930     }
    931 
    932     private void multiLaunch(DelayedLaunchInfo launchInfo, Collection<IDevice> devices) {
    933         for (IDevice d: devices) {
    934             boolean success = doPreLaunchActions(launchInfo, d);
    935             if (!success) {
    936                 String deviceName = d.isEmulator() ? d.getAvdName() : d.getSerialNumber();
    937                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
    938                         "Launch failed on device: " + deviceName);
    939                 continue;
    940             }
    941         }
    942 
    943         doLaunchAction(launchInfo, devices);
    944 
    945         // multiple launches are only supported for run configuration, so we can terminate
    946         // the launch itself
    947         stopLaunch(launchInfo);
    948     }
    949 
    950     /**
    951      * If needed, syncs the application and all its dependencies on the device/emulator.
    952      *
    953      * @param launchInfo The Launch information object.
    954      * @param device the device on which to sync the application
    955      * @return true if the install succeeded.
    956      */
    957     private boolean syncApp(DelayedLaunchInfo launchInfo, IDevice device) {
    958         boolean alreadyInstalled = ApkInstallManager.getInstance().isApplicationInstalled(
    959                 launchInfo.getProject(), launchInfo.getPackageName(), device);
    960 
    961         if (alreadyInstalled) {
    962             AdtPlugin.printToConsole(launchInfo.getProject(),
    963             "Application already deployed. No need to reinstall.");
    964         } else {
    965             if (doSyncApp(launchInfo, device) == false) {
    966                 return false;
    967             }
    968         }
    969 
    970         // The app is now installed, now try the dependent projects
    971         for (DelayedLaunchInfo dependentLaunchInfo : getDependenciesLaunchInfo(launchInfo)) {
    972             String msg = String.format("Project dependency found, installing: %s",
    973                     dependentLaunchInfo.getProject().getName());
    974             AdtPlugin.printToConsole(launchInfo.getProject(), msg);
    975             if (syncApp(dependentLaunchInfo, device) == false) {
    976                 return false;
    977             }
    978         }
    979 
    980         return true;
    981     }
    982 
    983     /**
    984      * Syncs the application on the device/emulator.
    985      *
    986      * @param launchInfo The Launch information object.
    987      * @param device the device on which to sync the application
    988      * @return true if the install succeeded.
    989      */
    990     private boolean doSyncApp(DelayedLaunchInfo launchInfo, IDevice device) {
    991         IPath path = launchInfo.getPackageFile().getLocation();
    992         String fileName = path.lastSegment();
    993         try {
    994             String message = String.format("Uploading %1$s onto device '%2$s'",
    995                     fileName, device.getSerialNumber());
    996             AdtPlugin.printToConsole(launchInfo.getProject(), message);
    997 
    998             String remotePackagePath = device.syncPackageToDevice(path.toOSString());
    999             boolean installResult = installPackage(launchInfo, remotePackagePath, device);
   1000             device.removeRemotePackage(remotePackagePath);
   1001 
   1002             // if the installation succeeded, we register it.
   1003             if (installResult) {
   1004                ApkInstallManager.getInstance().registerInstallation(
   1005                        launchInfo.getProject(), launchInfo.getPackageName(), device);
   1006             }
   1007             return installResult;
   1008         }
   1009         catch (IOException e) {
   1010             String msg = String.format("Failed to install %1$s on device '%2$s': %3$s", fileName,
   1011                     device.getSerialNumber(), e.getMessage());
   1012             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e);
   1013         } catch (TimeoutException e) {
   1014             String msg = String.format("Failed to install %1$s on device '%2$s': timeout", fileName,
   1015                     device.getSerialNumber());
   1016             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
   1017         } catch (AdbCommandRejectedException e) {
   1018             String msg = String.format(
   1019                     "Failed to install %1$s on device '%2$s': adb rejected install command with: %3$s",
   1020                     fileName, device.getSerialNumber(), e.getMessage());
   1021             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e);
   1022         } catch (CanceledException e) {
   1023             if (e.wasCanceled()) {
   1024                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1025                         String.format("Install of %1$s canceled", fileName));
   1026             } else {
   1027                 String msg = String.format("Failed to install %1$s on device '%2$s': %3$s",
   1028                         fileName, device.getSerialNumber(), e.getMessage());
   1029                 AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e);
   1030             }
   1031         }
   1032 
   1033         return false;
   1034     }
   1035 
   1036     /**
   1037      * For the current launchInfo, create additional DelayedLaunchInfo that should be used to
   1038      * sync APKs that we are dependent on to the device.
   1039      *
   1040      * @param launchInfo the original launch info that we want to find the
   1041      * @return a list of DelayedLaunchInfo (may be empty if no dependencies were found or error)
   1042      */
   1043     public List<DelayedLaunchInfo> getDependenciesLaunchInfo(DelayedLaunchInfo launchInfo) {
   1044         List<DelayedLaunchInfo> dependencies = new ArrayList<DelayedLaunchInfo>();
   1045 
   1046         // Convert to equivalent JavaProject
   1047         IJavaProject javaProject;
   1048         try {
   1049             //assuming this is an Android (and Java) project since it is attached to the launchInfo.
   1050             javaProject = BaseProjectHelper.getJavaProject(launchInfo.getProject());
   1051         } catch (CoreException e) {
   1052             // return empty dependencies
   1053             AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
   1054             return dependencies;
   1055         }
   1056 
   1057         // Get all projects that this depends on
   1058         List<IJavaProject> androidProjectList;
   1059         try {
   1060             androidProjectList = ProjectHelper.getAndroidProjectDependencies(javaProject);
   1061         } catch (JavaModelException e) {
   1062             // return empty dependencies
   1063             AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
   1064             return dependencies;
   1065         }
   1066 
   1067         // for each project, parse manifest and create launch information
   1068         for (IJavaProject androidProject : androidProjectList) {
   1069             // Parse the Manifest to get various required information
   1070             // copied from LaunchConfigDelegate
   1071             ManifestData manifestData = AndroidManifestHelper.parseForData(
   1072                     androidProject.getProject());
   1073 
   1074             if (manifestData == null) {
   1075                 continue;
   1076             }
   1077 
   1078             // Get the APK location (can return null)
   1079             IFile apk = ProjectHelper.getApplicationPackage(androidProject.getProject());
   1080             if (apk == null) {
   1081                 // getApplicationPackage will have logged an error message
   1082                 continue;
   1083             }
   1084 
   1085             // Create new launchInfo as an hybrid between parent and dependency information
   1086             DelayedLaunchInfo delayedLaunchInfo = new DelayedLaunchInfo(
   1087                     androidProject.getProject(),
   1088                     manifestData.getPackage(),
   1089                     manifestData.getPackage(),
   1090                     launchInfo.getLaunchAction(),
   1091                     apk,
   1092                     manifestData.getDebuggable(),
   1093                     manifestData.getMinSdkVersionString(),
   1094                     launchInfo.getLaunch(),
   1095                     launchInfo.getMonitor());
   1096 
   1097             // Add to the list
   1098             dependencies.add(delayedLaunchInfo);
   1099         }
   1100 
   1101         return dependencies;
   1102     }
   1103 
   1104     /**
   1105      * Installs the application package on the device, and handles return result
   1106      * @param launchInfo The launch information
   1107      * @param remotePath The remote path of the package.
   1108      * @param device The device on which the launch is done.
   1109      */
   1110     private boolean installPackage(DelayedLaunchInfo launchInfo, final String remotePath,
   1111             final IDevice device) {
   1112         String message = String.format("Installing %1$s...", launchInfo.getPackageFile().getName());
   1113         AdtPlugin.printToConsole(launchInfo.getProject(), message);
   1114         try {
   1115             // try a reinstall first, because the most common case is the app is already installed
   1116             String result = doInstall(launchInfo, remotePath, device, true /* reinstall */);
   1117 
   1118             /* For now we force to retry the install (after uninstalling) because there's no
   1119              * other way around it: adb install does not want to update a package w/o uninstalling
   1120              * the old one first!
   1121              */
   1122             return checkInstallResult(result, device, launchInfo, remotePath,
   1123                     InstallRetryMode.ALWAYS);
   1124         } catch (Exception e) {
   1125             String msg = String.format(
   1126                     "Failed to install %1$s on device '%2$s!",
   1127                     launchInfo.getPackageFile().getName(), device.getSerialNumber());
   1128             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg, e.getMessage());
   1129         }
   1130 
   1131         return false;
   1132     }
   1133 
   1134     /**
   1135      * Checks the result of an installation, and takes optional actions based on it.
   1136      * @param result the result string from the installation
   1137      * @param device the device on which the installation occured.
   1138      * @param launchInfo the {@link DelayedLaunchInfo}
   1139      * @param remotePath the temporary path of the package on the device
   1140      * @param retryMode indicates what to do in case, a package already exists.
   1141      * @return <code>true<code> if success, <code>false</code> otherwise.
   1142      * @throws InstallException
   1143      */
   1144     private boolean checkInstallResult(String result, IDevice device, DelayedLaunchInfo launchInfo,
   1145             String remotePath, InstallRetryMode retryMode) throws InstallException {
   1146         if (result == null) {
   1147             AdtPlugin.printToConsole(launchInfo.getProject(), "Success!");
   1148             return true;
   1149         }
   1150         else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$
   1151             // this should never happen, since reinstall mode is used on the first attempt
   1152             if (retryMode == InstallRetryMode.PROMPT) {
   1153                 boolean prompt = AdtPlugin.displayPrompt("Application Install",
   1154                         "A previous installation needs to be uninstalled before the new package can be installed.\nDo you want to uninstall?");
   1155                 if (prompt) {
   1156                     retryMode = InstallRetryMode.ALWAYS;
   1157                 } else {
   1158                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1159                         "Installation error! The package already exists.");
   1160                     return false;
   1161                 }
   1162             }
   1163 
   1164             if (retryMode == InstallRetryMode.ALWAYS) {
   1165                 /*
   1166                  * TODO: create a UI that gives the dev the choice to:
   1167                  * - clean uninstall on launch
   1168                  * - full uninstall if application exists.
   1169                  * - soft uninstall if application exists (keeps the app data around).
   1170                  * - always ask (choice of soft-reinstall, full reinstall)
   1171                 AdtPlugin.printErrorToConsole(launchInfo.mProject,
   1172                         "Application already exists, uninstalling...");
   1173                 String res = doUninstall(device, launchInfo);
   1174                 if (res == null) {
   1175                     AdtPlugin.printToConsole(launchInfo.mProject, "Success!");
   1176                 } else {
   1177                     AdtPlugin.printErrorToConsole(launchInfo.mProject,
   1178                             String.format("Failed to uninstall: %1$s", res));
   1179                     return false;
   1180                 }
   1181                 */
   1182 
   1183                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1184                         "Application already exists. Attempting to re-install instead...");
   1185                 String res = doInstall(launchInfo, remotePath, device, true /* reinstall */ );
   1186                 return checkInstallResult(res, device, launchInfo, remotePath,
   1187                         InstallRetryMode.NEVER);
   1188             }
   1189             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1190                     "Installation error! The package already exists.");
   1191         } else if (result.equals("INSTALL_FAILED_INVALID_APK")) { //$NON-NLS-1$
   1192             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1193                 "Installation failed due to invalid APK file!",
   1194                 "Please check logcat output for more details.");
   1195         } else if (result.equals("INSTALL_FAILED_INVALID_URI")) { //$NON-NLS-1$
   1196             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1197                 "Installation failed due to invalid URI!",
   1198                 "Please check logcat output for more details.");
   1199         } else if (result.equals("INSTALL_FAILED_COULDNT_COPY")) { //$NON-NLS-1$
   1200             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1201                 String.format("Installation failed: Could not copy %1$s to its final location!",
   1202                         launchInfo.getPackageFile().getName()),
   1203                 "Please check logcat output for more details.");
   1204         } else if (result.equals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) { //$NON-NLS-1$
   1205             if (retryMode != InstallRetryMode.NEVER) {
   1206                 boolean prompt = AdtPlugin.displayPrompt("Application Install",
   1207                                 "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?");
   1208                 if (prompt) {
   1209                     doUninstall(device, launchInfo);
   1210                     String res = doInstall(launchInfo, remotePath, device, false);
   1211                     return checkInstallResult(res, device, launchInfo, remotePath,
   1212                             InstallRetryMode.NEVER);
   1213                 }
   1214             }
   1215             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1216                     "Re-installation failed due to different application signatures.",
   1217                     "You must perform a full uninstall of the application. WARNING: This will remove the application data!",
   1218                     String.format("Please execute 'adb uninstall %1$s' in a shell.", launchInfo.getPackageName()));
   1219         } else {
   1220             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1221                 String.format("Installation error: %1$s", result),
   1222                 "Please check logcat output for more details.");
   1223         }
   1224 
   1225         return false;
   1226     }
   1227 
   1228     /**
   1229      * Performs the uninstallation of an application.
   1230      * @param device the device on which to install the application.
   1231      * @param launchInfo the {@link DelayedLaunchInfo}.
   1232      * @return a {@link String} with an error code, or <code>null</code> if success.
   1233      * @throws InstallException if the installation failed.
   1234      */
   1235     private String doUninstall(IDevice device, DelayedLaunchInfo launchInfo)
   1236             throws InstallException {
   1237         try {
   1238             return device.uninstallPackage(launchInfo.getPackageName());
   1239         } catch (InstallException e) {
   1240             String msg = String.format(
   1241                     "Failed to uninstall %1$s: %2$s", launchInfo.getPackageName(), e.getMessage());
   1242             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
   1243             throw e;
   1244         }
   1245     }
   1246 
   1247     /**
   1248      * Performs the installation of an application whose package has been uploaded on the device.
   1249      *
   1250      * @param launchInfo the {@link DelayedLaunchInfo}.
   1251      * @param remotePath the path of the application package in the device tmp folder.
   1252      * @param device the device on which to install the application.
   1253      * @param reinstall
   1254      * @return a {@link String} with an error code, or <code>null</code> if success.
   1255      * @throws InstallException if the uninstallation failed.
   1256      */
   1257     private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath,
   1258             final IDevice device, boolean reinstall) throws InstallException {
   1259         return device.installRemotePackage(remotePath, reinstall);
   1260     }
   1261 
   1262     /**
   1263      * launches an application on a device or emulator
   1264      *
   1265      * @param info the {@link DelayedLaunchInfo} that indicates the launch action
   1266      * @param device the device or emulator to launch the application on
   1267      */
   1268     @Override
   1269     public void launchApp(final DelayedLaunchInfo info, IDevice device) {
   1270         if (info.isDebugMode()) {
   1271             synchronized (sListLock) {
   1272                 if (mWaitingForDebuggerApplications.contains(info) == false) {
   1273                     mWaitingForDebuggerApplications.add(info);
   1274                 }
   1275             }
   1276         }
   1277         if (doLaunchAction(info, device)) {
   1278             // if the app is not a debug app, we need to do some clean up, as
   1279             // the process is done!
   1280             if (info.isDebugMode() == false) {
   1281                 // stop the launch object, since there's no debug, and it can't
   1282                 // provide any control over the app
   1283                 stopLaunch(info);
   1284             }
   1285         } else {
   1286             // something went wrong or no further launch action needed
   1287             // lets stop the Launch
   1288             stopLaunch(info);
   1289         }
   1290     }
   1291 
   1292     private boolean doLaunchAction(final DelayedLaunchInfo info, Collection<IDevice> devices) {
   1293         boolean result = info.getLaunchAction().doLaunchAction(info, devices);
   1294 
   1295         // Monitor the logcat output on the launched device to notify
   1296         // the user if any significant error occurs that is visible from logcat
   1297         for (IDevice d : devices) {
   1298             DdmsPlugin.getDefault().startLogCatMonitor(d);
   1299         }
   1300 
   1301         return result;
   1302     }
   1303 
   1304     private boolean doLaunchAction(final DelayedLaunchInfo info, IDevice device) {
   1305         return doLaunchAction(info, Collections.singletonList(device));
   1306     }
   1307 
   1308     private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) {
   1309 
   1310         // split the custom command line in segments
   1311         ArrayList<String> customArgs = new ArrayList<String>();
   1312         boolean hasWipeData = false;
   1313         if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
   1314             String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
   1315 
   1316             // we need to remove the empty strings
   1317             for (String s : segments) {
   1318                 if (s.length() > 0) {
   1319                     customArgs.add(s);
   1320                     if (!hasWipeData && s.equals(FLAG_WIPE_DATA)) {
   1321                         hasWipeData = true;
   1322                     }
   1323                 }
   1324             }
   1325         }
   1326 
   1327         boolean needsWipeData = config.mWipeData && !hasWipeData;
   1328         if (needsWipeData) {
   1329             if (!AdtPlugin.displayPrompt("Android Launch", "Are you sure you want to wipe all user data when starting this emulator?")) {
   1330                 needsWipeData = false;
   1331             }
   1332         }
   1333 
   1334         // build the command line based on the available parameters.
   1335         ArrayList<String> list = new ArrayList<String>();
   1336 
   1337         String path = AdtPlugin.getOsAbsoluteEmulator();
   1338 
   1339         list.add(path);
   1340 
   1341         list.add(FLAG_AVD);
   1342         list.add(avdToLaunch.getName());
   1343 
   1344         if (config.mNetworkSpeed != null) {
   1345             list.add(FLAG_NETSPEED);
   1346             list.add(config.mNetworkSpeed);
   1347         }
   1348 
   1349         if (config.mNetworkDelay != null) {
   1350             list.add(FLAG_NETDELAY);
   1351             list.add(config.mNetworkDelay);
   1352         }
   1353 
   1354         if (needsWipeData) {
   1355             list.add(FLAG_WIPE_DATA);
   1356         }
   1357 
   1358         if (config.mNoBootAnim) {
   1359             list.add(FLAG_NO_BOOT_ANIM);
   1360         }
   1361 
   1362         list.addAll(customArgs);
   1363 
   1364         // convert the list into an array for the call to exec.
   1365         String[] command = list.toArray(new String[list.size()]);
   1366 
   1367         // launch the emulator
   1368         try {
   1369             Process process = Runtime.getRuntime().exec(command);
   1370             grabEmulatorOutput(process);
   1371         } catch (IOException e) {
   1372             return false;
   1373         }
   1374 
   1375         return true;
   1376     }
   1377 
   1378     /**
   1379      * Looks for and returns an existing {@link ILaunchConfiguration} object for a
   1380      * specified project.
   1381      * @param manager The {@link ILaunchManager}.
   1382      * @param type The {@link ILaunchConfigurationType}.
   1383      * @param projectName The name of the project
   1384      * @return an existing <code>ILaunchConfiguration</code> object matching the project, or
   1385      *      <code>null</code>.
   1386      */
   1387     private static ILaunchConfiguration findConfig(ILaunchManager manager,
   1388             ILaunchConfigurationType type, String projectName) {
   1389         try {
   1390             ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type);
   1391 
   1392             for (ILaunchConfiguration config : configs) {
   1393                 if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
   1394                         "").equals(projectName)) {  //$NON-NLS-1$
   1395                     return config;
   1396                 }
   1397             }
   1398         } catch (CoreException e) {
   1399             MessageDialog.openError(AdtPlugin.getShell(),
   1400                     "Launch Error", e.getStatus().getMessage());
   1401         }
   1402 
   1403         // didn't find anything that matches. Return null
   1404         return null;
   1405     }
   1406 
   1407 
   1408     /**
   1409      * Connects a remote debugger on the specified port.
   1410      * @param debugPort The port to connect the debugger to
   1411      * @param launch The associated AndroidLaunch object.
   1412      * @param monitor A Progress monitor
   1413      * @return false if cancelled by the monitor
   1414      * @throws CoreException
   1415      */
   1416     @SuppressWarnings("deprecation")
   1417     public static boolean connectRemoteDebugger(int debugPort,
   1418             AndroidLaunch launch, IProgressMonitor monitor)
   1419                 throws CoreException {
   1420         // get some default parameters.
   1421         int connectTimeout = JavaRuntime.getPreferences().getInt(JavaRuntime.PREF_CONNECT_TIMEOUT);
   1422 
   1423         HashMap<String, String> newMap = new HashMap<String, String>();
   1424 
   1425         newMap.put("hostname", "localhost");  //$NON-NLS-1$ //$NON-NLS-2$
   1426 
   1427         newMap.put("port", Integer.toString(debugPort)); //$NON-NLS-1$
   1428 
   1429         newMap.put("timeout", Integer.toString(connectTimeout));
   1430 
   1431         // get the default VM connector
   1432         IVMConnector connector = JavaRuntime.getDefaultVMConnector();
   1433 
   1434         // connect to remote VM
   1435         connector.connect(newMap, monitor, launch);
   1436 
   1437         // check for cancellation
   1438         if (monitor.isCanceled()) {
   1439             IDebugTarget[] debugTargets = launch.getDebugTargets();
   1440             for (IDebugTarget target : debugTargets) {
   1441                 if (target.canDisconnect()) {
   1442                     target.disconnect();
   1443                 }
   1444             }
   1445             return false;
   1446         }
   1447 
   1448         return true;
   1449     }
   1450 
   1451     /**
   1452      * Launch a new thread that connects a remote debugger on the specified port.
   1453      * @param debugPort The port to connect the debugger to
   1454      * @param androidLaunch The associated AndroidLaunch object.
   1455      * @param monitor A Progress monitor
   1456      * @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor)
   1457      */
   1458     public static void launchRemoteDebugger(final int debugPort, final AndroidLaunch androidLaunch,
   1459             final IProgressMonitor monitor) {
   1460         new Thread("Debugger connection") { //$NON-NLS-1$
   1461             @Override
   1462             public void run() {
   1463                 try {
   1464                     connectRemoteDebugger(debugPort, androidLaunch, monitor);
   1465                 } catch (CoreException e) {
   1466                     androidLaunch.stopLaunch();
   1467                 }
   1468                 monitor.done();
   1469             }
   1470         }.start();
   1471     }
   1472 
   1473     /**
   1474      * Sent when a new {@link AndroidDebugBridge} is started.
   1475      * <p/>
   1476      * This is sent from a non UI thread.
   1477      * @param bridge the new {@link AndroidDebugBridge} object.
   1478      *
   1479      * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
   1480      */
   1481     @Override
   1482     public void bridgeChanged(AndroidDebugBridge bridge) {
   1483         // The adb server has changed. We cancel any pending launches.
   1484         String message = "adb server change: cancelling '%1$s'!";
   1485         synchronized (sListLock) {
   1486             for (DelayedLaunchInfo launchInfo : mWaitingForReadyEmulatorList) {
   1487                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1488                     String.format(message, launchInfo.getLaunchAction().getLaunchDescription()));
   1489                 stopLaunch(launchInfo);
   1490             }
   1491             for (DelayedLaunchInfo launchInfo : mWaitingForDebuggerApplications) {
   1492                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1493                         String.format(message,
   1494                                 launchInfo.getLaunchAction().getLaunchDescription()));
   1495                 stopLaunch(launchInfo);
   1496             }
   1497 
   1498             mWaitingForReadyEmulatorList.clear();
   1499             mWaitingForDebuggerApplications.clear();
   1500         }
   1501     }
   1502 
   1503     /**
   1504      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
   1505      * <p/>
   1506      * This is sent from a non UI thread.
   1507      * @param device the new device.
   1508      *
   1509      * @see IDeviceChangeListener#deviceConnected(IDevice)
   1510      */
   1511     @Override
   1512     public void deviceConnected(IDevice device) {
   1513         synchronized (sListLock) {
   1514             // look if there's an app waiting for a device
   1515             if (mWaitingForEmulatorLaunches.size() > 0) {
   1516                 // get/remove first launch item from the list
   1517                 // FIXME: what if we have multiple launches waiting?
   1518                 DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0);
   1519                 mWaitingForEmulatorLaunches.remove(0);
   1520 
   1521                 // give the launch item its device for later use.
   1522                 launchInfo.setDevice(device);
   1523 
   1524                 // and move it to the other list
   1525                 mWaitingForReadyEmulatorList.add(launchInfo);
   1526 
   1527                 // and tell the user about it
   1528                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1529                         String.format("New emulator found: %1$s", device.getSerialNumber()));
   1530                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1531                         String.format("Waiting for HOME ('%1$s') to be launched...",
   1532                             AdtPlugin.getDefault().getPreferenceStore().getString(
   1533                                     AdtPrefs.PREFS_HOME_PACKAGE)));
   1534             }
   1535         }
   1536     }
   1537 
   1538     /**
   1539      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
   1540      * <p/>
   1541      * This is sent from a non UI thread.
   1542      * @param device the new device.
   1543      *
   1544      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
   1545      */
   1546     @Override
   1547     public void deviceDisconnected(IDevice device) {
   1548         // any pending launch on this device must be canceled.
   1549         String message = "%1$s disconnected! Cancelling '%2$s'!";
   1550         synchronized (sListLock) {
   1551             ArrayList<DelayedLaunchInfo> copyList =
   1552                 (ArrayList<DelayedLaunchInfo>) mWaitingForReadyEmulatorList.clone();
   1553             for (DelayedLaunchInfo launchInfo : copyList) {
   1554                 if (launchInfo.getDevice() == device) {
   1555                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1556                             String.format(message, device.getSerialNumber(),
   1557                                     launchInfo.getLaunchAction().getLaunchDescription()));
   1558                     stopLaunch(launchInfo);
   1559                 }
   1560             }
   1561             copyList = (ArrayList<DelayedLaunchInfo>) mWaitingForDebuggerApplications.clone();
   1562             for (DelayedLaunchInfo launchInfo : copyList) {
   1563                 if (launchInfo.getDevice() == device) {
   1564                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1565                             String.format(message, device.getSerialNumber(),
   1566                                     launchInfo.getLaunchAction().getLaunchDescription()));
   1567                     stopLaunch(launchInfo);
   1568                 }
   1569             }
   1570         }
   1571     }
   1572 
   1573     /**
   1574      * Sent when a device data changed, or when clients are started/terminated on the device.
   1575      * <p/>
   1576      * This is sent from a non UI thread.
   1577      * @param device the device that was updated.
   1578      * @param changeMask the mask indicating what changed.
   1579      *
   1580      * @see IDeviceChangeListener#deviceChanged(IDevice, int)
   1581      */
   1582     @Override
   1583     public void deviceChanged(IDevice device, int changeMask) {
   1584         // We could check if any starting device we care about is now ready, but we can wait for
   1585         // its home app to show up, so...
   1586     }
   1587 
   1588     /**
   1589      * Sent when an existing client information changed.
   1590      * <p/>
   1591      * This is sent from a non UI thread.
   1592      * @param client the updated client.
   1593      * @param changeMask the bit mask describing the changed properties. It can contain
   1594      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
   1595      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
   1596      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
   1597      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
   1598      *
   1599      * @see IClientChangeListener#clientChanged(Client, int)
   1600      */
   1601     @Override
   1602     public void clientChanged(final Client client, int changeMask) {
   1603         boolean connectDebugger = false;
   1604         if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
   1605             String applicationName = client.getClientData().getClientDescription();
   1606             if (applicationName != null) {
   1607                 IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
   1608                 String home = store.getString(AdtPrefs.PREFS_HOME_PACKAGE);
   1609 
   1610                 if (home.equals(applicationName)) {
   1611 
   1612                     // looks like home is up, get its device
   1613                     IDevice device = client.getDevice();
   1614 
   1615                     // look for application waiting for home
   1616                     synchronized (sListLock) {
   1617                         for (int i = 0; i < mWaitingForReadyEmulatorList.size(); ) {
   1618                             DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i);
   1619                             if (launchInfo.getDevice() == device) {
   1620                                 // it's match, remove from the list
   1621                                 mWaitingForReadyEmulatorList.remove(i);
   1622 
   1623                                 // We couldn't check earlier the API level of the device
   1624                                 // (it's asynchronous when the device boot, and usually
   1625                                 // deviceConnected is called before it's queried for its build info)
   1626                                 // so we check now
   1627                                 if (checkBuildInfo(launchInfo, device) == false) {
   1628                                     // device is not the proper API!
   1629                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1630                                             "Launch canceled!");
   1631                                     stopLaunch(launchInfo);
   1632                                     return;
   1633                                 }
   1634 
   1635                                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1636                                         String.format("HOME is up on device '%1$s'",
   1637                                                 device.getSerialNumber()));
   1638 
   1639                                 // attempt to sync the new package onto the device.
   1640                                 if (syncApp(launchInfo, device)) {
   1641                                     // application package is sync'ed, lets attempt to launch it.
   1642                                     launchApp(launchInfo, device);
   1643                                 } else {
   1644                                     // failure! Cancel and return
   1645                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1646                                     "Launch canceled!");
   1647                                     stopLaunch(launchInfo);
   1648                                 }
   1649 
   1650                                 break;
   1651                             } else {
   1652                                 i++;
   1653                             }
   1654                         }
   1655                     }
   1656                 }
   1657 
   1658                 // check if it's already waiting for a debugger, and if so we connect to it.
   1659                 if (client.getClientData().getDebuggerConnectionStatus() == DebuggerStatus.WAITING) {
   1660                     // search for this client in the list;
   1661                     synchronized (sListLock) {
   1662                         int index = mUnknownClientsWaitingForDebugger.indexOf(client);
   1663                         if (index != -1) {
   1664                             connectDebugger = true;
   1665                             mUnknownClientsWaitingForDebugger.remove(client);
   1666                         }
   1667                     }
   1668                 }
   1669             }
   1670         }
   1671 
   1672         // if it's not home, it could be an app that is now in debugger mode that we're waiting for
   1673         // lets check it
   1674 
   1675         if ((changeMask & Client.CHANGE_DEBUGGER_STATUS) == Client.CHANGE_DEBUGGER_STATUS) {
   1676             ClientData clientData = client.getClientData();
   1677             String applicationName = client.getClientData().getClientDescription();
   1678             if (clientData.getDebuggerConnectionStatus() == DebuggerStatus.WAITING) {
   1679                 // Get the application name, and make sure its valid.
   1680                 if (applicationName == null) {
   1681                     // looks like we don't have the client yet, so we keep it around for when its
   1682                     // name becomes available.
   1683                     synchronized (sListLock) {
   1684                         mUnknownClientsWaitingForDebugger.add(client);
   1685                     }
   1686                     return;
   1687                 } else {
   1688                     connectDebugger = true;
   1689                 }
   1690             }
   1691         }
   1692 
   1693         if (connectDebugger) {
   1694             Log.d("adt", "Debugging " + client);
   1695             // now check it against the apps waiting for a debugger
   1696             String applicationName = client.getClientData().getClientDescription();
   1697             Log.d("adt", "App Name: " + applicationName);
   1698             synchronized (sListLock) {
   1699                 for (int i = 0; i < mWaitingForDebuggerApplications.size(); ) {
   1700                     final DelayedLaunchInfo launchInfo = mWaitingForDebuggerApplications.get(i);
   1701                     if (client.getDevice() == launchInfo.getDevice() &&
   1702                             applicationName.equals(launchInfo.getDebugPackageName())) {
   1703                         // this is a match. We remove the launch info from the list
   1704                         mWaitingForDebuggerApplications.remove(i);
   1705 
   1706                         // and connect the debugger.
   1707                         String msg = String.format(
   1708                                 "Attempting to connect debugger to '%1$s' on port %2$d",
   1709                                 launchInfo.getDebugPackageName(), client.getDebuggerListenPort());
   1710                         AdtPlugin.printToConsole(launchInfo.getProject(), msg);
   1711 
   1712                         new Thread("Debugger Connection") { //$NON-NLS-1$
   1713                             @Override
   1714                             public void run() {
   1715                                 try {
   1716                                     if (connectRemoteDebugger(
   1717                                             client.getDebuggerListenPort(),
   1718                                             launchInfo.getLaunch(),
   1719                                             launchInfo.getMonitor()) == false) {
   1720                                         return;
   1721                                     }
   1722                                 } catch (CoreException e) {
   1723                                     // well something went wrong.
   1724                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1725                                             String.format("Launch error: %s", e.getMessage()));
   1726                                     // stop the launch
   1727                                     stopLaunch(launchInfo);
   1728                                 }
   1729 
   1730                                 launchInfo.getMonitor().done();
   1731                             }
   1732                         }.start();
   1733 
   1734                         // we're done processing this client.
   1735                         return;
   1736 
   1737                     } else {
   1738                         i++;
   1739                     }
   1740                 }
   1741             }
   1742 
   1743             // if we get here, we haven't found an app that we were launching, so we look
   1744             // for opened android projects that contains the app asking for a debugger.
   1745             // If we find one, we automatically connect to it.
   1746             IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName);
   1747 
   1748             if (project != null) {
   1749                 debugRunningApp(project, client.getDebuggerListenPort());
   1750             }
   1751         }
   1752     }
   1753 
   1754     /**
   1755      * Get the stderr/stdout outputs of a process and return when the process is done.
   1756      * Both <b>must</b> be read or the process will block on windows.
   1757      * @param process The process to get the output from
   1758      */
   1759     private void grabEmulatorOutput(final Process process) {
   1760         // read the lines as they come. if null is returned, it's
   1761         // because the process finished
   1762         new Thread("") { //$NON-NLS-1$
   1763             @Override
   1764             public void run() {
   1765                 // create a buffer to read the stderr output
   1766                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
   1767                 BufferedReader errReader = new BufferedReader(is);
   1768 
   1769                 try {
   1770                     while (true) {
   1771                         String line = errReader.readLine();
   1772                         if (line != null) {
   1773                             AdtPlugin.printErrorToConsole("Emulator", line);
   1774                         } else {
   1775                             break;
   1776                         }
   1777                     }
   1778                 } catch (IOException e) {
   1779                     // do nothing.
   1780                 }
   1781             }
   1782         }.start();
   1783 
   1784         new Thread("") { //$NON-NLS-1$
   1785             @Override
   1786             public void run() {
   1787                 InputStreamReader is = new InputStreamReader(process.getInputStream());
   1788                 BufferedReader outReader = new BufferedReader(is);
   1789 
   1790                 try {
   1791                     while (true) {
   1792                         String line = outReader.readLine();
   1793                         if (line != null) {
   1794                             AdtPlugin.printToConsole("Emulator", line);
   1795                         } else {
   1796                             break;
   1797                         }
   1798                     }
   1799                 } catch (IOException e) {
   1800                     // do nothing.
   1801                 }
   1802             }
   1803         }.start();
   1804     }
   1805 
   1806     /* (non-Javadoc)
   1807      * @see com.android.ide.eclipse.adt.launch.ILaunchController#stopLaunch(com.android.ide.eclipse.adt.launch.AndroidLaunchController.DelayedLaunchInfo)
   1808      */
   1809     @Override
   1810     public void stopLaunch(DelayedLaunchInfo launchInfo) {
   1811         launchInfo.getLaunch().stopLaunch();
   1812         synchronized (sListLock) {
   1813             mWaitingForReadyEmulatorList.remove(launchInfo);
   1814             mWaitingForDebuggerApplications.remove(launchInfo);
   1815         }
   1816     }
   1817 
   1818     public static void updateLaunchConfigWithLastUsedDevice(
   1819             ILaunchConfiguration launchConfiguration, DeviceChooserResponse response) {
   1820         try {
   1821             boolean configModified = false;
   1822             boolean reuse = launchConfiguration.getAttribute(
   1823                     LaunchConfigDelegate.ATTR_REUSE_LAST_USED_DEVICE, false);
   1824             String serial = launchConfiguration.getAttribute(
   1825                     LaunchConfigDelegate.ATTR_LAST_USED_DEVICE, (String)null);
   1826 
   1827             ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
   1828             if (reuse != response.useDeviceForFutureLaunches()) {
   1829                 reuse = response.useDeviceForFutureLaunches();
   1830                 wc.setAttribute(LaunchConfigDelegate.ATTR_REUSE_LAST_USED_DEVICE, reuse);
   1831                 configModified = true;
   1832             }
   1833 
   1834             if (reuse) {
   1835                 String selected = getSerial(response);
   1836                 if (selected != null && !selected.equalsIgnoreCase(serial)) {
   1837                     wc.setAttribute(LaunchConfigDelegate.ATTR_LAST_USED_DEVICE, selected);
   1838                     configModified = true;
   1839                 }
   1840             }
   1841 
   1842             if (configModified) {
   1843                 wc.doSave();
   1844             }
   1845         } catch (CoreException e) {
   1846             // in such a case, users just won't see this setting take effect
   1847             return;
   1848         }
   1849     }
   1850 
   1851     private static String getSerial(DeviceChooserResponse response) {
   1852         AvdInfo avd = response.getAvdToLaunch();
   1853         return (avd != null) ? avd.getName() : response.getDeviceToUse().getSerialNumber();
   1854     }
   1855 
   1856     @Nullable
   1857     public static IDevice getDeviceIfOnline(@Nullable String serial,
   1858             @NonNull IDevice[] onlineDevices) {
   1859         if (serial == null) {
   1860             return null;
   1861         }
   1862 
   1863         for (IDevice device : onlineDevices) {
   1864             if (serial.equals(device.getAvdName()) ||
   1865                     serial.equals(device.getSerialNumber())) {
   1866                 return device;
   1867             }
   1868         }
   1869 
   1870         return null;
   1871     }
   1872 }
   1873