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