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