• 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.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