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 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 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 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 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 */ 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 */ 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 */ 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 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