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")) { //$NON-NLS-1$
   1198             if (retryMode != InstallRetryMode.NEVER) {
   1199                 boolean prompt = AdtPlugin.displayPrompt("Application Install",
   1200                                 "Re-installation failed due to different application signatures. You must perform a full uninstall of the application. WARNING: This will remove the application data!\nDo you want to uninstall?");
   1201                 if (prompt) {
   1202                     doUninstall(device, launchInfo);
   1203                     String res = doInstall(launchInfo, remotePath, device, false);
   1204                     return checkInstallResult(res, device, launchInfo, remotePath,
   1205                             InstallRetryMode.NEVER);
   1206                 }
   1207             }
   1208             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1209                     "Re-installation failed due to different application signatures.",
   1210                     "You must perform a full uninstall of the application. WARNING: This will remove the application data!",
   1211                     String.format("Please execute 'adb uninstall %1$s' in a shell.", launchInfo.getPackageName()));
   1212         } else {
   1213             AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1214                 String.format("Installation error: %1$s", result),
   1215                 "Please check logcat output for more details.");
   1216         }
   1217 
   1218         return false;
   1219     }
   1220 
   1221     /**
   1222      * Performs the uninstallation of an application.
   1223      * @param device the device on which to install the application.
   1224      * @param launchInfo the {@link DelayedLaunchInfo}.
   1225      * @return a {@link String} with an error code, or <code>null</code> if success.
   1226      * @throws InstallException if the installation failed.
   1227      */
   1228     private String doUninstall(IDevice device, DelayedLaunchInfo launchInfo)
   1229             throws InstallException {
   1230         try {
   1231             return device.uninstallPackage(launchInfo.getPackageName());
   1232         } catch (InstallException e) {
   1233             String msg = String.format(
   1234                     "Failed to uninstall %1$s: %2$s", launchInfo.getPackageName(), e.getMessage());
   1235             AdtPlugin.printErrorToConsole(launchInfo.getProject(), msg);
   1236             throw e;
   1237         }
   1238     }
   1239 
   1240     /**
   1241      * Performs the installation of an application whose package has been uploaded on the device.
   1242      *
   1243      * @param launchInfo the {@link DelayedLaunchInfo}.
   1244      * @param remotePath the path of the application package in the device tmp folder.
   1245      * @param device the device on which to install the application.
   1246      * @param reinstall
   1247      * @return a {@link String} with an error code, or <code>null</code> if success.
   1248      * @throws InstallException if the uninstallation failed.
   1249      */
   1250     private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath,
   1251             final IDevice device, boolean reinstall) throws InstallException {
   1252         return device.installRemotePackage(remotePath, reinstall);
   1253     }
   1254 
   1255     /**
   1256      * launches an application on a device or emulator
   1257      *
   1258      * @param info the {@link DelayedLaunchInfo} that indicates the launch action
   1259      * @param device the device or emulator to launch the application on
   1260      */
   1261     @Override
   1262     public void launchApp(final DelayedLaunchInfo info, IDevice device) {
   1263         if (info.isDebugMode()) {
   1264             synchronized (sListLock) {
   1265                 if (mWaitingForDebuggerApplications.contains(info) == false) {
   1266                     mWaitingForDebuggerApplications.add(info);
   1267                 }
   1268             }
   1269         }
   1270         if (doLaunchAction(info, device)) {
   1271             // if the app is not a debug app, we need to do some clean up, as
   1272             // the process is done!
   1273             if (info.isDebugMode() == false) {
   1274                 // stop the launch object, since there's no debug, and it can't
   1275                 // provide any control over the app
   1276                 stopLaunch(info);
   1277             }
   1278         } else {
   1279             // something went wrong or no further launch action needed
   1280             // lets stop the Launch
   1281             stopLaunch(info);
   1282         }
   1283     }
   1284 
   1285     private boolean doLaunchAction(final DelayedLaunchInfo info, Collection<IDevice> devices) {
   1286         boolean result = info.getLaunchAction().doLaunchAction(info, devices);
   1287 
   1288         // Monitor the logcat output on the launched device to notify
   1289         // the user if any significant error occurs that is visible from logcat
   1290         for (IDevice d : devices) {
   1291             DdmsPlugin.getDefault().startLogCatMonitor(d);
   1292         }
   1293 
   1294         return result;
   1295     }
   1296 
   1297     private boolean doLaunchAction(final DelayedLaunchInfo info, IDevice device) {
   1298         return doLaunchAction(info, Collections.singletonList(device));
   1299     }
   1300 
   1301     private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) {
   1302 
   1303         // split the custom command line in segments
   1304         ArrayList<String> customArgs = new ArrayList<String>();
   1305         boolean hasWipeData = false;
   1306         if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
   1307             String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
   1308 
   1309             // we need to remove the empty strings
   1310             for (String s : segments) {
   1311                 if (s.length() > 0) {
   1312                     customArgs.add(s);
   1313                     if (!hasWipeData && s.equals(FLAG_WIPE_DATA)) {
   1314                         hasWipeData = true;
   1315                     }
   1316                 }
   1317             }
   1318         }
   1319 
   1320         boolean needsWipeData = config.mWipeData && !hasWipeData;
   1321         if (needsWipeData) {
   1322             if (!AdtPlugin.displayPrompt("Android Launch", "Are you sure you want to wipe all user data when starting this emulator?")) {
   1323                 needsWipeData = false;
   1324             }
   1325         }
   1326 
   1327         // build the command line based on the available parameters.
   1328         ArrayList<String> list = new ArrayList<String>();
   1329 
   1330         String path = AdtPlugin.getOsAbsoluteEmulator();
   1331 
   1332         list.add(path);
   1333 
   1334         list.add(FLAG_AVD);
   1335         list.add(avdToLaunch.getName());
   1336 
   1337         if (config.mNetworkSpeed != null) {
   1338             list.add(FLAG_NETSPEED);
   1339             list.add(config.mNetworkSpeed);
   1340         }
   1341 
   1342         if (config.mNetworkDelay != null) {
   1343             list.add(FLAG_NETDELAY);
   1344             list.add(config.mNetworkDelay);
   1345         }
   1346 
   1347         if (needsWipeData) {
   1348             list.add(FLAG_WIPE_DATA);
   1349         }
   1350 
   1351         if (config.mNoBootAnim) {
   1352             list.add(FLAG_NO_BOOT_ANIM);
   1353         }
   1354 
   1355         list.addAll(customArgs);
   1356 
   1357         // convert the list into an array for the call to exec.
   1358         String[] command = list.toArray(new String[list.size()]);
   1359 
   1360         // launch the emulator
   1361         try {
   1362             Process process = Runtime.getRuntime().exec(command);
   1363             grabEmulatorOutput(process);
   1364         } catch (IOException e) {
   1365             return false;
   1366         }
   1367 
   1368         return true;
   1369     }
   1370 
   1371     /**
   1372      * Looks for and returns an existing {@link ILaunchConfiguration} object for a
   1373      * specified project.
   1374      * @param manager The {@link ILaunchManager}.
   1375      * @param type The {@link ILaunchConfigurationType}.
   1376      * @param projectName The name of the project
   1377      * @return an existing <code>ILaunchConfiguration</code> object matching the project, or
   1378      *      <code>null</code>.
   1379      */
   1380     private static ILaunchConfiguration findConfig(ILaunchManager manager,
   1381             ILaunchConfigurationType type, String projectName) {
   1382         try {
   1383             ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type);
   1384 
   1385             for (ILaunchConfiguration config : configs) {
   1386                 if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
   1387                         "").equals(projectName)) {  //$NON-NLS-1$
   1388                     return config;
   1389                 }
   1390             }
   1391         } catch (CoreException e) {
   1392             MessageDialog.openError(AdtPlugin.getShell(),
   1393                     "Launch Error", e.getStatus().getMessage());
   1394         }
   1395 
   1396         // didn't find anything that matches. Return null
   1397         return null;
   1398     }
   1399 
   1400 
   1401     /**
   1402      * Connects a remote debugger on the specified port.
   1403      * @param debugPort The port to connect the debugger to
   1404      * @param launch The associated AndroidLaunch object.
   1405      * @param monitor A Progress monitor
   1406      * @return false if cancelled by the monitor
   1407      * @throws CoreException
   1408      */
   1409     @SuppressWarnings("deprecation")
   1410     public static boolean connectRemoteDebugger(int debugPort,
   1411             AndroidLaunch launch, IProgressMonitor monitor)
   1412                 throws CoreException {
   1413         // get some default parameters.
   1414         int connectTimeout = JavaRuntime.getPreferences().getInt(JavaRuntime.PREF_CONNECT_TIMEOUT);
   1415 
   1416         HashMap<String, String> newMap = new HashMap<String, String>();
   1417 
   1418         newMap.put("hostname", "localhost");  //$NON-NLS-1$ //$NON-NLS-2$
   1419 
   1420         newMap.put("port", Integer.toString(debugPort)); //$NON-NLS-1$
   1421 
   1422         newMap.put("timeout", Integer.toString(connectTimeout));
   1423 
   1424         // get the default VM connector
   1425         IVMConnector connector = JavaRuntime.getDefaultVMConnector();
   1426 
   1427         // connect to remote VM
   1428         connector.connect(newMap, monitor, launch);
   1429 
   1430         // check for cancellation
   1431         if (monitor.isCanceled()) {
   1432             IDebugTarget[] debugTargets = launch.getDebugTargets();
   1433             for (IDebugTarget target : debugTargets) {
   1434                 if (target.canDisconnect()) {
   1435                     target.disconnect();
   1436                 }
   1437             }
   1438             return false;
   1439         }
   1440 
   1441         return true;
   1442     }
   1443 
   1444     /**
   1445      * Launch a new thread that connects a remote debugger on the specified port.
   1446      * @param debugPort The port to connect the debugger to
   1447      * @param androidLaunch The associated AndroidLaunch object.
   1448      * @param monitor A Progress monitor
   1449      * @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor)
   1450      */
   1451     public static void launchRemoteDebugger(final int debugPort, final AndroidLaunch androidLaunch,
   1452             final IProgressMonitor monitor) {
   1453         new Thread("Debugger connection") { //$NON-NLS-1$
   1454             @Override
   1455             public void run() {
   1456                 try {
   1457                     connectRemoteDebugger(debugPort, androidLaunch, monitor);
   1458                 } catch (CoreException e) {
   1459                     androidLaunch.stopLaunch();
   1460                 }
   1461                 monitor.done();
   1462             }
   1463         }.start();
   1464     }
   1465 
   1466     /**
   1467      * Sent when a new {@link AndroidDebugBridge} is started.
   1468      * <p/>
   1469      * This is sent from a non UI thread.
   1470      * @param bridge the new {@link AndroidDebugBridge} object.
   1471      *
   1472      * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
   1473      */
   1474     @Override
   1475     public void bridgeChanged(AndroidDebugBridge bridge) {
   1476         // The adb server has changed. We cancel any pending launches.
   1477         String message = "adb server change: cancelling '%1$s'!";
   1478         synchronized (sListLock) {
   1479             for (DelayedLaunchInfo launchInfo : mWaitingForReadyEmulatorList) {
   1480                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1481                     String.format(message, launchInfo.getLaunchAction().getLaunchDescription()));
   1482                 stopLaunch(launchInfo);
   1483             }
   1484             for (DelayedLaunchInfo launchInfo : mWaitingForDebuggerApplications) {
   1485                 AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1486                         String.format(message,
   1487                                 launchInfo.getLaunchAction().getLaunchDescription()));
   1488                 stopLaunch(launchInfo);
   1489             }
   1490 
   1491             mWaitingForReadyEmulatorList.clear();
   1492             mWaitingForDebuggerApplications.clear();
   1493         }
   1494     }
   1495 
   1496     /**
   1497      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
   1498      * <p/>
   1499      * This is sent from a non UI thread.
   1500      * @param device the new device.
   1501      *
   1502      * @see IDeviceChangeListener#deviceConnected(IDevice)
   1503      */
   1504     @Override
   1505     public void deviceConnected(IDevice device) {
   1506         synchronized (sListLock) {
   1507             // look if there's an app waiting for a device
   1508             if (mWaitingForEmulatorLaunches.size() > 0) {
   1509                 // get/remove first launch item from the list
   1510                 // FIXME: what if we have multiple launches waiting?
   1511                 DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0);
   1512                 mWaitingForEmulatorLaunches.remove(0);
   1513 
   1514                 // give the launch item its device for later use.
   1515                 launchInfo.setDevice(device);
   1516 
   1517                 // and move it to the other list
   1518                 mWaitingForReadyEmulatorList.add(launchInfo);
   1519 
   1520                 // and tell the user about it
   1521                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1522                         String.format("New emulator found: %1$s", device.getSerialNumber()));
   1523                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1524                         String.format("Waiting for HOME ('%1$s') to be launched...",
   1525                             AdtPlugin.getDefault().getPreferenceStore().getString(
   1526                                     AdtPrefs.PREFS_HOME_PACKAGE)));
   1527             }
   1528         }
   1529     }
   1530 
   1531     /**
   1532      * Sent when the a device is connected to the {@link AndroidDebugBridge}.
   1533      * <p/>
   1534      * This is sent from a non UI thread.
   1535      * @param device the new device.
   1536      *
   1537      * @see IDeviceChangeListener#deviceDisconnected(IDevice)
   1538      */
   1539     @Override
   1540     public void deviceDisconnected(IDevice device) {
   1541         // any pending launch on this device must be canceled.
   1542         String message = "%1$s disconnected! Cancelling '%2$s'!";
   1543         synchronized (sListLock) {
   1544             ArrayList<DelayedLaunchInfo> copyList =
   1545                 (ArrayList<DelayedLaunchInfo>) mWaitingForReadyEmulatorList.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             copyList = (ArrayList<DelayedLaunchInfo>) mWaitingForDebuggerApplications.clone();
   1555             for (DelayedLaunchInfo launchInfo : copyList) {
   1556                 if (launchInfo.getDevice() == device) {
   1557                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1558                             String.format(message, device.getSerialNumber(),
   1559                                     launchInfo.getLaunchAction().getLaunchDescription()));
   1560                     stopLaunch(launchInfo);
   1561                 }
   1562             }
   1563         }
   1564     }
   1565 
   1566     /**
   1567      * Sent when a device data changed, or when clients are started/terminated on the device.
   1568      * <p/>
   1569      * This is sent from a non UI thread.
   1570      * @param device the device that was updated.
   1571      * @param changeMask the mask indicating what changed.
   1572      *
   1573      * @see IDeviceChangeListener#deviceChanged(IDevice, int)
   1574      */
   1575     @Override
   1576     public void deviceChanged(IDevice device, int changeMask) {
   1577         // We could check if any starting device we care about is now ready, but we can wait for
   1578         // its home app to show up, so...
   1579     }
   1580 
   1581     /**
   1582      * Sent when an existing client information changed.
   1583      * <p/>
   1584      * This is sent from a non UI thread.
   1585      * @param client the updated client.
   1586      * @param changeMask the bit mask describing the changed properties. It can contain
   1587      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
   1588      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
   1589      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
   1590      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
   1591      *
   1592      * @see IClientChangeListener#clientChanged(Client, int)
   1593      */
   1594     @Override
   1595     public void clientChanged(final Client client, int changeMask) {
   1596         boolean connectDebugger = false;
   1597         if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) {
   1598             String applicationName = client.getClientData().getClientDescription();
   1599             if (applicationName != null) {
   1600                 IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
   1601                 String home = store.getString(AdtPrefs.PREFS_HOME_PACKAGE);
   1602 
   1603                 if (home.equals(applicationName)) {
   1604 
   1605                     // looks like home is up, get its device
   1606                     IDevice device = client.getDevice();
   1607 
   1608                     // look for application waiting for home
   1609                     synchronized (sListLock) {
   1610                         for (int i = 0; i < mWaitingForReadyEmulatorList.size(); ) {
   1611                             DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i);
   1612                             if (launchInfo.getDevice() == device) {
   1613                                 // it's match, remove from the list
   1614                                 mWaitingForReadyEmulatorList.remove(i);
   1615 
   1616                                 // We couldn't check earlier the API level of the device
   1617                                 // (it's asynchronous when the device boot, and usually
   1618                                 // deviceConnected is called before it's queried for its build info)
   1619                                 // so we check now
   1620                                 if (checkBuildInfo(launchInfo, device) == false) {
   1621                                     // device is not the proper API!
   1622                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1623                                             "Launch canceled!");
   1624                                     stopLaunch(launchInfo);
   1625                                     return;
   1626                                 }
   1627 
   1628                                 AdtPlugin.printToConsole(launchInfo.getProject(),
   1629                                         String.format("HOME is up on device '%1$s'",
   1630                                                 device.getSerialNumber()));
   1631 
   1632                                 // attempt to sync the new package onto the device.
   1633                                 if (syncApp(launchInfo, device)) {
   1634                                     // application package is sync'ed, lets attempt to launch it.
   1635                                     launchApp(launchInfo, device);
   1636                                 } else {
   1637                                     // failure! Cancel and return
   1638                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1639                                     "Launch canceled!");
   1640                                     stopLaunch(launchInfo);
   1641                                 }
   1642 
   1643                                 break;
   1644                             } else {
   1645                                 i++;
   1646                             }
   1647                         }
   1648                     }
   1649                 }
   1650 
   1651                 // check if it's already waiting for a debugger, and if so we connect to it.
   1652                 if (client.getClientData().getDebuggerConnectionStatus() == DebuggerStatus.WAITING) {
   1653                     // search for this client in the list;
   1654                     synchronized (sListLock) {
   1655                         int index = mUnknownClientsWaitingForDebugger.indexOf(client);
   1656                         if (index != -1) {
   1657                             connectDebugger = true;
   1658                             mUnknownClientsWaitingForDebugger.remove(client);
   1659                         }
   1660                     }
   1661                 }
   1662             }
   1663         }
   1664 
   1665         // if it's not home, it could be an app that is now in debugger mode that we're waiting for
   1666         // lets check it
   1667 
   1668         if ((changeMask & Client.CHANGE_DEBUGGER_STATUS) == Client.CHANGE_DEBUGGER_STATUS) {
   1669             ClientData clientData = client.getClientData();
   1670             String applicationName = client.getClientData().getClientDescription();
   1671             if (clientData.getDebuggerConnectionStatus() == DebuggerStatus.WAITING) {
   1672                 // Get the application name, and make sure its valid.
   1673                 if (applicationName == null) {
   1674                     // looks like we don't have the client yet, so we keep it around for when its
   1675                     // name becomes available.
   1676                     synchronized (sListLock) {
   1677                         mUnknownClientsWaitingForDebugger.add(client);
   1678                     }
   1679                     return;
   1680                 } else {
   1681                     connectDebugger = true;
   1682                 }
   1683             }
   1684         }
   1685 
   1686         if (connectDebugger) {
   1687             Log.d("adt", "Debugging " + client);
   1688             // now check it against the apps waiting for a debugger
   1689             String applicationName = client.getClientData().getClientDescription();
   1690             Log.d("adt", "App Name: " + applicationName);
   1691             synchronized (sListLock) {
   1692                 for (int i = 0; i < mWaitingForDebuggerApplications.size(); ) {
   1693                     final DelayedLaunchInfo launchInfo = mWaitingForDebuggerApplications.get(i);
   1694                     if (client.getDevice() == launchInfo.getDevice() &&
   1695                             applicationName.equals(launchInfo.getDebugPackageName())) {
   1696                         // this is a match. We remove the launch info from the list
   1697                         mWaitingForDebuggerApplications.remove(i);
   1698 
   1699                         // and connect the debugger.
   1700                         String msg = String.format(
   1701                                 "Attempting to connect debugger to '%1$s' on port %2$d",
   1702                                 launchInfo.getDebugPackageName(), client.getDebuggerListenPort());
   1703                         AdtPlugin.printToConsole(launchInfo.getProject(), msg);
   1704 
   1705                         new Thread("Debugger Connection") { //$NON-NLS-1$
   1706                             @Override
   1707                             public void run() {
   1708                                 try {
   1709                                     if (connectRemoteDebugger(
   1710                                             client.getDebuggerListenPort(),
   1711                                             launchInfo.getLaunch(),
   1712                                             launchInfo.getMonitor()) == false) {
   1713                                         return;
   1714                                     }
   1715                                 } catch (CoreException e) {
   1716                                     // well something went wrong.
   1717                                     AdtPlugin.printErrorToConsole(launchInfo.getProject(),
   1718                                             String.format("Launch error: %s", e.getMessage()));
   1719                                     // stop the launch
   1720                                     stopLaunch(launchInfo);
   1721                                 }
   1722 
   1723                                 launchInfo.getMonitor().done();
   1724                             }
   1725                         }.start();
   1726 
   1727                         // we're done processing this client.
   1728                         return;
   1729 
   1730                     } else {
   1731                         i++;
   1732                     }
   1733                 }
   1734             }
   1735 
   1736             // if we get here, we haven't found an app that we were launching, so we look
   1737             // for opened android projects that contains the app asking for a debugger.
   1738             // If we find one, we automatically connect to it.
   1739             IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName);
   1740 
   1741             if (project != null) {
   1742                 debugRunningApp(project, client.getDebuggerListenPort());
   1743             }
   1744         }
   1745     }
   1746 
   1747     /**
   1748      * Get the stderr/stdout outputs of a process and return when the process is done.
   1749      * Both <b>must</b> be read or the process will block on windows.
   1750      * @param process The process to get the output from
   1751      */
   1752     private void grabEmulatorOutput(final Process process) {
   1753         // read the lines as they come. if null is returned, it's
   1754         // because the process finished
   1755         new Thread("") { //$NON-NLS-1$
   1756             @Override
   1757             public void run() {
   1758                 // create a buffer to read the stderr output
   1759                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
   1760                 BufferedReader errReader = new BufferedReader(is);
   1761 
   1762                 try {
   1763                     while (true) {
   1764                         String line = errReader.readLine();
   1765                         if (line != null) {
   1766                             AdtPlugin.printErrorToConsole("Emulator", line);
   1767                         } else {
   1768                             break;
   1769                         }
   1770                     }
   1771                 } catch (IOException e) {
   1772                     // do nothing.
   1773                 }
   1774             }
   1775         }.start();
   1776 
   1777         new Thread("") { //$NON-NLS-1$
   1778             @Override
   1779             public void run() {
   1780                 InputStreamReader is = new InputStreamReader(process.getInputStream());
   1781                 BufferedReader outReader = new BufferedReader(is);
   1782 
   1783                 try {
   1784                     while (true) {
   1785                         String line = outReader.readLine();
   1786                         if (line != null) {
   1787                             AdtPlugin.printToConsole("Emulator", line);
   1788                         } else {
   1789                             break;
   1790                         }
   1791                     }
   1792                 } catch (IOException e) {
   1793                     // do nothing.
   1794                 }
   1795             }
   1796         }.start();
   1797     }
   1798 
   1799     /* (non-Javadoc)
   1800      * @see com.android.ide.eclipse.adt.launch.ILaunchController#stopLaunch(com.android.ide.eclipse.adt.launch.AndroidLaunchController.DelayedLaunchInfo)
   1801      */
   1802     @Override
   1803     public void stopLaunch(DelayedLaunchInfo launchInfo) {
   1804         launchInfo.getLaunch().stopLaunch();
   1805         synchronized (sListLock) {
   1806             mWaitingForReadyEmulatorList.remove(launchInfo);
   1807             mWaitingForDebuggerApplications.remove(launchInfo);
   1808         }
   1809     }
   1810 }
   1811