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 launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)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 doLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, AndroidLaunchConfiguration config, AndroidLaunchController controller, IFile applicationPackage, ManifestData manifestData)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 buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor)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 getLaunch(ILaunchConfiguration configuration, String mode)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 */ getProject(ILaunchConfiguration configuration)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 */ checkAndroidProject(IProject project)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 */ getActivityName(ILaunchConfiguration configuration)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 revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config)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