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