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