• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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