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