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