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