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.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.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 ManifestData manifestData = AndroidManifestHelper.parseForData(project); 226 227 if (manifestData == null) { 228 AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!"); 229 androidLaunch.stopLaunch(); 230 return; 231 } 232 233 doLaunch(configuration, mode, monitor, project, androidLaunch, config, controller, 234 applicationPackage, manifestData); 235 } 236 doLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, AndroidLaunchConfiguration config, AndroidLaunchController controller, IFile applicationPackage, ManifestData manifestData)237 protected void doLaunch(ILaunchConfiguration configuration, String mode, 238 IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, 239 AndroidLaunchConfiguration config, AndroidLaunchController controller, 240 IFile applicationPackage, ManifestData manifestData) { 241 242 String activityName = null; 243 244 if (config.mLaunchAction == ACTION_ACTIVITY) { 245 // Get the activity name defined in the config 246 activityName = getActivityName(configuration); 247 248 // Get the full activity list and make sure the one we got matches. 249 Activity[] activities = manifestData.getActivities(); 250 251 // first we check that there are, in fact, activities. 252 if (activities.length == 0) { 253 // if the activities list is null, then the manifest is empty 254 // and we can't launch the app. We'll revert to a sync-only launch 255 AdtPlugin.printErrorToConsole(project, 256 "The Manifest defines no activity!", 257 "The launch will only sync the application package on the device!"); 258 config.mLaunchAction = ACTION_DO_NOTHING; 259 } else if (activityName == null) { 260 // if the activity we got is null, we look for the default one. 261 AdtPlugin.printErrorToConsole(project, 262 "No activity specified! Getting the launcher activity."); 263 Activity launcherActivity = manifestData.getLauncherActivity(); 264 if (launcherActivity != null) { 265 activityName = launcherActivity.getName(); 266 } 267 268 // if there's no default activity. We revert to a sync-only launch. 269 if (activityName == null) { 270 revertToNoActionLaunch(project, config); 271 } 272 } else { 273 274 // check the one we got from the config matches any from the list 275 boolean match = false; 276 for (Activity a : activities) { 277 if (a != null && a.getName().equals(activityName)) { 278 match = true; 279 break; 280 } 281 } 282 283 // if we didn't find a match, we revert to the default activity if any. 284 if (match == false) { 285 AdtPlugin.printErrorToConsole(project, 286 "The specified activity does not exist! Getting the launcher activity."); 287 Activity launcherActivity = manifestData.getLauncherActivity(); 288 if (launcherActivity != null) { 289 activityName = launcherActivity.getName(); 290 } else { 291 // if there's no default activity. We revert to a sync-only launch. 292 revertToNoActionLaunch(project, config); 293 } 294 } 295 } 296 } else if (config.mLaunchAction == ACTION_DEFAULT) { 297 Activity launcherActivity = manifestData.getLauncherActivity(); 298 if (launcherActivity != null) { 299 activityName = launcherActivity.getName(); 300 } 301 302 // if there's no default activity. We revert to a sync-only launch. 303 if (activityName == null) { 304 revertToNoActionLaunch(project, config); 305 } 306 } 307 308 IAndroidLaunchAction launchAction = null; 309 if (config.mLaunchAction == ACTION_DO_NOTHING || activityName == null) { 310 launchAction = new EmptyLaunchAction(); 311 } else { 312 launchAction = new ActivityLaunchAction(activityName, controller); 313 } 314 315 // everything seems fine, we ask the launch controller to handle 316 // the rest 317 controller.launch(project, mode, applicationPackage,manifestData.getPackage(), 318 manifestData.getPackage(), manifestData.getDebuggable(), 319 manifestData.getMinSdkVersionString(), launchAction, config, androidLaunch, 320 monitor); 321 } 322 323 @Override buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor)324 public boolean buildForLaunch(ILaunchConfiguration configuration, 325 String mode, IProgressMonitor monitor) throws CoreException { 326 327 // need to check we have everything 328 IProject project = getProject(configuration); 329 330 if (project != null) { 331 // force an incremental build to be sure the resources will 332 // be updated if they were not saved before the launch was launched. 333 return true; 334 } 335 336 throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 337 1 /* code, unused */, "Can't find the project!", null /* exception */)); 338 } 339 340 /** 341 * {@inheritDoc} 342 * @throws CoreException 343 */ 344 @Override getLaunch(ILaunchConfiguration configuration, String mode)345 public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) 346 throws CoreException { 347 return new AndroidLaunch(configuration, mode, null); 348 } 349 350 /** 351 * Returns the IProject object matching the name found in the configuration 352 * object under the name 353 * <code>IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME</code> 354 * @param configuration 355 * @return The IProject object or null 356 */ getProject(ILaunchConfiguration configuration)357 private IProject getProject(ILaunchConfiguration configuration){ 358 // get the project name from the config 359 String projectName; 360 try { 361 projectName = configuration.getAttribute( 362 IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); 363 } catch (CoreException e) { 364 return null; 365 } 366 367 // get the current workspace 368 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 369 370 // and return the project with the name from the config 371 return workspace.getRoot().getProject(projectName); 372 } 373 374 /** 375 * Checks the project is an android project. 376 * @param project The project to check 377 * @return true if the project is an android SDK. 378 * @throws CoreException 379 */ checkAndroidProject(IProject project)380 private boolean checkAndroidProject(IProject project) throws CoreException { 381 // check if the project is a java and an android project. 382 if (project.hasNature(JavaCore.NATURE_ID) == false) { 383 String msg = String.format("%1$s is not a Java project!", project.getName()); 384 AdtPlugin.displayError("Android Launch", msg); 385 return false; 386 } 387 388 if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { 389 String msg = String.format("%1$s is not an Android project!", project.getName()); 390 AdtPlugin.displayError("Android Launch", msg); 391 return false; 392 } 393 394 return true; 395 } 396 397 398 /** 399 * Returns the name of the activity. 400 */ getActivityName(ILaunchConfiguration configuration)401 private String getActivityName(ILaunchConfiguration configuration) { 402 String empty = ""; 403 String activityName; 404 try { 405 activityName = configuration.getAttribute(ATTR_ACTIVITY, empty); 406 } catch (CoreException e) { 407 return null; 408 } 409 410 return (activityName != empty) ? activityName : null; 411 } 412 revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config)413 private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) { 414 AdtPlugin.printErrorToConsole(project, 415 "No Launcher activity found!", 416 "The launch will only sync the application package on the device!"); 417 config.mLaunchAction = ACTION_DO_NOTHING; 418 } 419 } 420