1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.launch; 18 19 import com.android.ddmlib.AndroidDebugBridge; 20 import com.android.ide.common.xml.ManifestData; 21 import com.android.ide.common.xml.ManifestData.Activity; 22 import com.android.ide.eclipse.adt.AdtConstants; 23 import com.android.ide.eclipse.adt.AdtPlugin; 24 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode; 25 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 26 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 27 28 import org.eclipse.core.resources.IFile; 29 import org.eclipse.core.resources.IProject; 30 import org.eclipse.core.resources.IWorkspace; 31 import org.eclipse.core.resources.ResourcesPlugin; 32 import org.eclipse.core.runtime.CoreException; 33 import org.eclipse.core.runtime.IProgressMonitor; 34 import org.eclipse.debug.core.ILaunch; 35 import org.eclipse.debug.core.ILaunchConfiguration; 36 import org.eclipse.debug.core.model.LaunchConfigurationDelegate; 37 import org.eclipse.jdt.core.JavaCore; 38 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; 39 40 /** 41 * Implementation of an eclipse LauncConfigurationDelegate to launch android 42 * application in debug. 43 */ 44 public class LaunchConfigDelegate extends LaunchConfigurationDelegate { 45 final static int INVALID_DEBUG_PORT = -1; 46 47 public final static String ANDROID_LAUNCH_TYPE_ID = 48 "com.android.ide.eclipse.adt.debug.LaunchConfigType"; //$NON-NLS-1$ 49 50 /** Target mode parameters: true is automatic, false is manual */ 51 public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$ 52 public static final TargetMode DEFAULT_TARGET_MODE = TargetMode.AUTO; 53 54 /** Flag indicating whether the last used device should be used for future launches. */ 55 public static final String ATTR_REUSE_LAST_USED_DEVICE = 56 AdtPlugin.PLUGIN_ID + ".reuse.last.used.device"; //$NON-NLS-1$ 57 58 /** Device on which the last launch happened. */ 59 public static final String ATTR_LAST_USED_DEVICE = 60 AdtPlugin.PLUGIN_ID + ".last.used.device"; //$NON-NLS-1$ 61 62 /** 63 * Launch action: 64 * <ul> 65 * <li>0: launch default activity</li> 66 * <li>1: launch specified activity. See {@link #ATTR_ACTIVITY}</li> 67 * <li>2: Do Nothing</li> 68 * </ul> 69 */ 70 public final static String ATTR_LAUNCH_ACTION = AdtPlugin.PLUGIN_ID + ".action"; //$NON-NLS-1$ 71 72 /** Default launch action. This launches the activity that is setup to be found in the HOME 73 * screen. 74 */ 75 public final static int ACTION_DEFAULT = 0; 76 /** Launch action starting a specific activity. */ 77 public final static int ACTION_ACTIVITY = 1; 78 /** Launch action that does nothing. */ 79 public final static int ACTION_DO_NOTHING = 2; 80 /** Default launch action value. */ 81 public final static int DEFAULT_LAUNCH_ACTION = ACTION_DEFAULT; 82 83 /** 84 * Activity to be launched if {@link #ATTR_LAUNCH_ACTION} is 1 85 */ 86 public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ 87 88 public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$ 89 90 public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ 91 92 /** 93 * Index of the default network speed setting for the emulator.<br> 94 * Get the emulator option with <code>EmulatorConfigTab.getSpeed(index)</code> 95 */ 96 public static final int DEFAULT_SPEED = 0; 97 98 public static final String ATTR_DELAY = AdtPlugin.PLUGIN_ID + ".delay"; //$NON-NLS-1$ 99 100 /** 101 * Index of the default network latency setting for the emulator.<br> 102 * Get the emulator option with <code>EmulatorConfigTab.getDelay(index)</code> 103 */ 104 public static final int DEFAULT_DELAY = 0; 105 106 public static final String ATTR_COMMANDLINE = AdtPlugin.PLUGIN_ID + ".commandline"; //$NON-NLS-1$ 107 108 public static final String ATTR_WIPE_DATA = AdtPlugin.PLUGIN_ID + ".wipedata"; //$NON-NLS-1$ 109 public static final boolean DEFAULT_WIPE_DATA = false; 110 111 public static final String ATTR_NO_BOOT_ANIM = AdtPlugin.PLUGIN_ID + ".nobootanim"; //$NON-NLS-1$ 112 public static final boolean DEFAULT_NO_BOOT_ANIM = false; 113 114 public static final String ATTR_DEBUG_PORT = 115 AdtPlugin.PLUGIN_ID + ".debugPort"; //$NON-NLS-1$ 116 117 @Override launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)118 public void launch(ILaunchConfiguration configuration, String mode, 119 ILaunch launch, IProgressMonitor monitor) throws CoreException { 120 // We need to check if it's a standard launch or if it's a launch 121 // to debug an application already running. 122 int debugPort = AndroidLaunchController.getPortForConfig(configuration); 123 124 // get the project 125 IProject project = getProject(configuration); 126 127 // first we make sure the launch is of the proper type 128 AndroidLaunch androidLaunch = null; 129 if (launch instanceof AndroidLaunch) { 130 androidLaunch = (AndroidLaunch)launch; 131 } else { 132 // wrong type, not sure how we got there, but we don't do 133 // anything else 134 AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!"); 135 return; 136 } 137 138 // if we have a valid debug port, this means we're debugging an app 139 // that's already launched. 140 if (debugPort != INVALID_DEBUG_PORT) { 141 AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor); 142 return; 143 } 144 145 if (project == null) { 146 AdtPlugin.printErrorToConsole("Couldn't get project object!"); 147 androidLaunch.stopLaunch(); 148 return; 149 } 150 151 // make sure the project and its dependencies are built 152 // and PostCompilerBuilder runs. 153 // This is a synchronous call which returns when the 154 // build is done. 155 ProjectHelper.doFullIncrementalDebugBuild(project, monitor); 156 157 // check if the project has errors, and abort in this case. 158 if (ProjectHelper.hasError(project, true)) { 159 AdtPlugin.displayError("Android Launch", 160 "Your project contains error(s), please fix them before running your application."); 161 return; 162 } 163 164 AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$ 165 AdtPlugin.printToConsole(project, "Android Launch!"); 166 167 // check if the project is using the proper sdk. 168 // if that throws an exception, we simply let it propagate to the caller. 169 if (checkAndroidProject(project) == false) { 170 AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!"); 171 androidLaunch.stopLaunch(); 172 return; 173 } 174 175 // Check adb status and abort if needed. 176 AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); 177 if (bridge == null || bridge.isConnected() == false) { 178 try { 179 int connections = -1; 180 int restarts = -1; 181 if (bridge != null) { 182 connections = bridge.getConnectionAttemptCount(); 183 restarts = bridge.getRestartAttemptCount(); 184 } 185 186 // if we get -1, the device monitor is not even setup (anymore?). 187 // We need to ask the user to restart eclipse. 188 // This shouldn't happen, but it's better to let the user know in case it does. 189 if (connections == -1 || restarts == -1) { 190 AdtPlugin.printErrorToConsole(project, 191 "The connection to adb is down, and a severe error has occured.", 192 "You must restart adb and Eclipse.", 193 String.format( 194 "Please ensure that adb is correctly located at '%1$s' and can be executed.", 195 AdtPlugin.getOsAbsoluteAdb())); 196 return; 197 } 198 199 if (restarts == 0) { 200 AdtPlugin.printErrorToConsole(project, 201 "Connection with adb was interrupted.", 202 String.format("%1$s attempts have been made to reconnect.", connections), 203 "You may want to manually restart adb from the Devices view."); 204 } else { 205 AdtPlugin.printErrorToConsole(project, 206 "Connection with adb was interrupted, and attempts to reconnect have failed.", 207 String.format("%1$s attempts have been made to restart adb.", restarts), 208 "You may want to manually restart adb from the Devices view."); 209 210 } 211 return; 212 } finally { 213 androidLaunch.stopLaunch(); 214 } 215 } 216 217 // since adb is working, we let the user know 218 // TODO have a verbose mode for launch with more info (or some of the less useful info we now have). 219 AdtPlugin.printToConsole(project, "adb is running normally."); 220 221 // make a config class 222 AndroidLaunchConfiguration config = new AndroidLaunchConfiguration(); 223 224 // fill it with the config coming from the ILaunchConfiguration object 225 config.set(configuration); 226 227 // get the launch controller singleton 228 AndroidLaunchController controller = AndroidLaunchController.getInstance(); 229 230 // get the application package 231 IFile applicationPackage = ProjectHelper.getApplicationPackage(project); 232 if (applicationPackage == null) { 233 androidLaunch.stopLaunch(); 234 return; 235 } 236 237 // we need some information from the manifest 238 ManifestData manifestData = AndroidManifestHelper.parseForData(project); 239 240 if (manifestData == null) { 241 AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!"); 242 androidLaunch.stopLaunch(); 243 return; 244 } 245 246 doLaunch(configuration, mode, monitor, project, androidLaunch, config, controller, 247 applicationPackage, manifestData); 248 } 249 doLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, AndroidLaunchConfiguration config, AndroidLaunchController controller, IFile applicationPackage, ManifestData manifestData)250 protected void doLaunch(ILaunchConfiguration configuration, String mode, 251 IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, 252 AndroidLaunchConfiguration config, AndroidLaunchController controller, 253 IFile applicationPackage, ManifestData manifestData) { 254 255 String activityName = null; 256 257 if (config.mLaunchAction == ACTION_ACTIVITY) { 258 // Get the activity name defined in the config 259 activityName = getActivityName(configuration); 260 261 // Get the full activity list and make sure the one we got matches. 262 Activity[] activities = manifestData.getActivities(); 263 264 // first we check that there are, in fact, activities. 265 if (activities.length == 0) { 266 // if the activities list is null, then the manifest is empty 267 // and we can't launch the app. We'll revert to a sync-only launch 268 AdtPlugin.printErrorToConsole(project, 269 "The Manifest defines no activity!", 270 "The launch will only sync the application package on the device!"); 271 config.mLaunchAction = ACTION_DO_NOTHING; 272 } else if (activityName == null) { 273 // if the activity we got is null, we look for the default one. 274 AdtPlugin.printErrorToConsole(project, 275 "No activity specified! Getting the launcher activity."); 276 Activity launcherActivity = manifestData.getLauncherActivity(); 277 if (launcherActivity != null) { 278 activityName = launcherActivity.getName(); 279 } 280 281 // if there's no default activity. We revert to a sync-only launch. 282 if (activityName == null) { 283 revertToNoActionLaunch(project, config); 284 } 285 } else { 286 287 // check the one we got from the config matches any from the list 288 boolean match = false; 289 for (Activity a : activities) { 290 if (a != null && a.getName().equals(activityName)) { 291 match = true; 292 break; 293 } 294 } 295 296 // if we didn't find a match, we revert to the default activity if any. 297 if (match == false) { 298 AdtPlugin.printErrorToConsole(project, 299 "The specified activity does not exist! Getting the launcher activity."); 300 Activity launcherActivity = manifestData.getLauncherActivity(); 301 if (launcherActivity != null) { 302 activityName = launcherActivity.getName(); 303 } else { 304 // if there's no default activity. We revert to a sync-only launch. 305 revertToNoActionLaunch(project, config); 306 } 307 } 308 } 309 } else if (config.mLaunchAction == ACTION_DEFAULT) { 310 Activity launcherActivity = manifestData.getLauncherActivity(); 311 if (launcherActivity != null) { 312 activityName = launcherActivity.getName(); 313 } 314 315 // if there's no default activity. We revert to a sync-only launch. 316 if (activityName == null) { 317 revertToNoActionLaunch(project, config); 318 } 319 } 320 321 IAndroidLaunchAction launchAction = null; 322 if (config.mLaunchAction == ACTION_DO_NOTHING || activityName == null) { 323 launchAction = new EmptyLaunchAction(); 324 } else { 325 launchAction = new ActivityLaunchAction(activityName, controller); 326 } 327 328 // everything seems fine, we ask the launch controller to handle 329 // the rest 330 controller.launch(project, mode, applicationPackage,manifestData.getPackage(), 331 manifestData.getPackage(), manifestData.getDebuggable(), 332 manifestData.getMinSdkVersionString(), launchAction, config, androidLaunch, 333 monitor); 334 } 335 336 @Override buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor)337 public boolean buildForLaunch(ILaunchConfiguration configuration, 338 String mode, IProgressMonitor monitor) throws CoreException { 339 // if this returns true, this forces a full workspace rebuild which is not 340 // what we want. 341 // Instead in the #launch method, we'll rebuild only the launching project. 342 return false; 343 } 344 345 /** 346 * {@inheritDoc} 347 * @throws CoreException 348 */ 349 @Override getLaunch(ILaunchConfiguration configuration, String mode)350 public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) 351 throws CoreException { 352 return new AndroidLaunch(configuration, mode, null); 353 } 354 355 /** 356 * Returns the IProject object matching the name found in the configuration 357 * object under the name 358 * <code>IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME</code> 359 * @param configuration 360 * @return The IProject object or null 361 */ getProject(ILaunchConfiguration configuration)362 private IProject getProject(ILaunchConfiguration configuration){ 363 // get the project name from the config 364 String projectName; 365 try { 366 projectName = configuration.getAttribute( 367 IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); 368 } catch (CoreException e) { 369 return null; 370 } 371 372 // get the current workspace 373 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 374 375 // and return the project with the name from the config 376 return workspace.getRoot().getProject(projectName); 377 } 378 379 /** 380 * Checks the project is an android project. 381 * @param project The project to check 382 * @return true if the project is an android SDK. 383 * @throws CoreException 384 */ checkAndroidProject(IProject project)385 private boolean checkAndroidProject(IProject project) throws CoreException { 386 // check if the project is a java and an android project. 387 if (project.hasNature(JavaCore.NATURE_ID) == false) { 388 String msg = String.format("%1$s is not a Java project!", project.getName()); 389 AdtPlugin.displayError("Android Launch", msg); 390 return false; 391 } 392 393 if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 394 String msg = String.format("%1$s is not an Android project!", project.getName()); 395 AdtPlugin.displayError("Android Launch", msg); 396 return false; 397 } 398 399 return true; 400 } 401 402 403 /** 404 * Returns the name of the activity. 405 */ getActivityName(ILaunchConfiguration configuration)406 private String getActivityName(ILaunchConfiguration configuration) { 407 String empty = ""; 408 String activityName; 409 try { 410 activityName = configuration.getAttribute(ATTR_ACTIVITY, empty); 411 } catch (CoreException e) { 412 return null; 413 } 414 415 return (activityName != empty) ? activityName : null; 416 } 417 revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config)418 private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) { 419 AdtPlugin.printErrorToConsole(project, 420 "No Launcher activity found!", 421 "The launch will only sync the application package on the device!"); 422 config.mLaunchAction = ACTION_DO_NOTHING; 423 } 424 } 425