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