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