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