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