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