• 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.common.xml.ManifestData;
21 import com.android.ide.common.xml.ManifestData.Activity;
22 import com.android.ide.eclipse.adt.AdtConstants;
23 import com.android.ide.eclipse.adt.AdtPlugin;
24 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode;
25 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
26 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
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 
109     @Override
launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)110     public void launch(ILaunchConfiguration configuration, String mode,
111             ILaunch launch, IProgressMonitor monitor) throws CoreException {
112         // We need to check if it's a standard launch or if it's a launch
113         // to debug an application already running.
114         int debugPort = AndroidLaunchController.getPortForConfig(configuration);
115 
116         // get the project
117         IProject project = getProject(configuration);
118 
119         // first we make sure the launch is of the proper type
120         AndroidLaunch androidLaunch = null;
121         if (launch instanceof AndroidLaunch) {
122             androidLaunch = (AndroidLaunch)launch;
123         } else {
124             // wrong type, not sure how we got there, but we don't do
125             // anything else
126             AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!");
127             return;
128         }
129 
130         // if we have a valid debug port, this means we're debugging an app
131         // that's already launched.
132         if (debugPort != INVALID_DEBUG_PORT) {
133             AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor);
134             return;
135         }
136 
137         if (project == null) {
138             AdtPlugin.printErrorToConsole("Couldn't get project object!");
139             androidLaunch.stopLaunch();
140             return;
141         }
142 
143         // make sure the project and its dependencies are built
144         // and PostCompilerBuilder runs.
145         // This is a synchronous call which returns when the
146         // build is done.
147         ProjectHelper.doFullIncrementalDebugBuild(project, monitor);
148 
149         // check if the project has errors, and abort in this case.
150         if (ProjectHelper.hasError(project, true)) {
151             AdtPlugin.displayError("Android Launch",
152                     "Your project contains error(s), please fix them before running your application.");
153             return;
154         }
155 
156         AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$
157         AdtPlugin.printToConsole(project, "Android Launch!");
158 
159         // check if the project is using the proper sdk.
160         // if that throws an exception, we simply let it propagate to the caller.
161         if (checkAndroidProject(project) == false) {
162             AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!");
163             androidLaunch.stopLaunch();
164             return;
165         }
166 
167         // Check adb status and abort if needed.
168         AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
169         if (bridge == null || bridge.isConnected() == false) {
170             try {
171                 int connections = -1;
172                 int restarts = -1;
173                 if (bridge != null) {
174                     connections = bridge.getConnectionAttemptCount();
175                     restarts = bridge.getRestartAttemptCount();
176                 }
177 
178                 // if we get -1, the device monitor is not even setup (anymore?).
179                 // We need to ask the user to restart eclipse.
180                 // This shouldn't happen, but it's better to let the user know in case it does.
181                 if (connections == -1 || restarts == -1) {
182                     AdtPlugin.printErrorToConsole(project,
183                             "The connection to adb is down, and a severe error has occured.",
184                             "You must restart adb and Eclipse.",
185                             String.format(
186                                     "Please ensure that adb is correctly located at '%1$s' and can be executed.",
187                                     AdtPlugin.getOsAbsoluteAdb()));
188                     return;
189                 }
190 
191                 if (restarts == 0) {
192                     AdtPlugin.printErrorToConsole(project,
193                             "Connection with adb was interrupted.",
194                             String.format("%1$s attempts have been made to reconnect.", connections),
195                             "You may want to manually restart adb from the Devices view.");
196                 } else {
197                     AdtPlugin.printErrorToConsole(project,
198                             "Connection with adb was interrupted, and attempts to reconnect have failed.",
199                             String.format("%1$s attempts have been made to restart adb.", restarts),
200                             "You may want to manually restart adb from the Devices view.");
201 
202                 }
203                 return;
204             } finally {
205                 androidLaunch.stopLaunch();
206             }
207         }
208 
209         // since adb is working, we let the user know
210         // TODO have a verbose mode for launch with more info (or some of the less useful info we now have).
211         AdtPlugin.printToConsole(project, "adb is running normally.");
212 
213         // make a config class
214         AndroidLaunchConfiguration config = new AndroidLaunchConfiguration();
215 
216         // fill it with the config coming from the ILaunchConfiguration object
217         config.set(configuration);
218 
219         // get the launch controller singleton
220         AndroidLaunchController controller = AndroidLaunchController.getInstance();
221 
222         // get the application package
223         IFile applicationPackage = ProjectHelper.getApplicationPackage(project);
224         if (applicationPackage == null) {
225             androidLaunch.stopLaunch();
226             return;
227         }
228 
229         // we need some information from the manifest
230         ManifestData manifestData = AndroidManifestHelper.parseForData(project);
231 
232         if (manifestData == null) {
233             AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!");
234             androidLaunch.stopLaunch();
235             return;
236         }
237 
238         doLaunch(configuration, mode, monitor, project, androidLaunch, config, controller,
239                 applicationPackage, manifestData);
240     }
241 
doLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch, AndroidLaunchConfiguration config, AndroidLaunchController controller, IFile applicationPackage, ManifestData manifestData)242     protected void doLaunch(ILaunchConfiguration configuration, String mode,
243             IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch,
244             AndroidLaunchConfiguration config, AndroidLaunchController controller,
245             IFile applicationPackage, ManifestData manifestData) {
246 
247        String activityName = null;
248 
249         if (config.mLaunchAction == ACTION_ACTIVITY) {
250             // Get the activity name defined in the config
251             activityName = getActivityName(configuration);
252 
253             // Get the full activity list and make sure the one we got matches.
254             Activity[] activities = manifestData.getActivities();
255 
256             // first we check that there are, in fact, activities.
257             if (activities.length == 0) {
258                 // if the activities list is null, then the manifest is empty
259                 // and we can't launch the app. We'll revert to a sync-only launch
260                 AdtPlugin.printErrorToConsole(project,
261                         "The Manifest defines no activity!",
262                         "The launch will only sync the application package on the device!");
263                 config.mLaunchAction = ACTION_DO_NOTHING;
264             } else if (activityName == null) {
265                 // if the activity we got is null, we look for the default one.
266                 AdtPlugin.printErrorToConsole(project,
267                         "No activity specified! Getting the launcher activity.");
268                 Activity launcherActivity = manifestData.getLauncherActivity();
269                 if (launcherActivity != null) {
270                     activityName = launcherActivity.getName();
271                 }
272 
273                 // if there's no default activity. We revert to a sync-only launch.
274                 if (activityName == null) {
275                     revertToNoActionLaunch(project, config);
276                 }
277             } else {
278 
279                 // check the one we got from the config matches any from the list
280                 boolean match = false;
281                 for (Activity a : activities) {
282                     if (a != null && a.getName().equals(activityName)) {
283                         match = true;
284                         break;
285                     }
286                 }
287 
288                 // if we didn't find a match, we revert to the default activity if any.
289                 if (match == false) {
290                     AdtPlugin.printErrorToConsole(project,
291                             "The specified activity does not exist! Getting the launcher activity.");
292                     Activity launcherActivity = manifestData.getLauncherActivity();
293                     if (launcherActivity != null) {
294                         activityName = launcherActivity.getName();
295                     } else {
296                         // if there's no default activity. We revert to a sync-only launch.
297                         revertToNoActionLaunch(project, config);
298                     }
299                 }
300             }
301         } else if (config.mLaunchAction == ACTION_DEFAULT) {
302             Activity launcherActivity = manifestData.getLauncherActivity();
303             if (launcherActivity != null) {
304                 activityName = launcherActivity.getName();
305             }
306 
307             // if there's no default activity. We revert to a sync-only launch.
308             if (activityName == null) {
309                 revertToNoActionLaunch(project, config);
310             }
311         }
312 
313         IAndroidLaunchAction launchAction = null;
314         if (config.mLaunchAction == ACTION_DO_NOTHING || activityName == null) {
315             launchAction = new EmptyLaunchAction();
316         } else {
317             launchAction = new ActivityLaunchAction(activityName, controller);
318         }
319 
320         // everything seems fine, we ask the launch controller to handle
321         // the rest
322         controller.launch(project, mode, applicationPackage,manifestData.getPackage(),
323                 manifestData.getPackage(), manifestData.getDebuggable(),
324                 manifestData.getMinSdkVersionString(), launchAction, config, androidLaunch,
325                 monitor);
326     }
327 
328     @Override
buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor)329     public boolean buildForLaunch(ILaunchConfiguration configuration,
330             String mode, IProgressMonitor monitor) throws CoreException {
331         // if this returns true, this forces a full workspace rebuild which is not
332         // what we want.
333         // Instead in the #launch method, we'll rebuild only the launching project.
334         return false;
335     }
336 
337     /**
338      * {@inheritDoc}
339      * @throws CoreException
340      */
341     @Override
getLaunch(ILaunchConfiguration configuration, String mode)342     public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
343             throws CoreException {
344         return new AndroidLaunch(configuration, mode, null);
345     }
346 
347     /**
348      * Returns the IProject object matching the name found in the configuration
349      * object under the name
350      * <code>IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME</code>
351      * @param configuration
352      * @return The IProject object or null
353      */
getProject(ILaunchConfiguration configuration)354     private IProject getProject(ILaunchConfiguration configuration){
355         // get the project name from the config
356         String projectName;
357         try {
358             projectName = configuration.getAttribute(
359                     IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
360         } catch (CoreException e) {
361             return null;
362         }
363 
364         // get the current workspace
365         IWorkspace workspace = ResourcesPlugin.getWorkspace();
366 
367         // and return the project with the name from the config
368         return workspace.getRoot().getProject(projectName);
369     }
370 
371     /**
372      * Checks the project is an android project.
373      * @param project The project to check
374      * @return true if the project is an android SDK.
375      * @throws CoreException
376      */
checkAndroidProject(IProject project)377     private boolean checkAndroidProject(IProject project) throws CoreException {
378         // check if the project is a java and an android project.
379         if (project.hasNature(JavaCore.NATURE_ID) == false) {
380             String msg = String.format("%1$s is not a Java project!", project.getName());
381             AdtPlugin.displayError("Android Launch", msg);
382             return false;
383         }
384 
385         if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
386             String msg = String.format("%1$s is not an Android project!", project.getName());
387             AdtPlugin.displayError("Android Launch", msg);
388             return false;
389         }
390 
391         return true;
392     }
393 
394 
395     /**
396      * Returns the name of the activity.
397      */
getActivityName(ILaunchConfiguration configuration)398     private String getActivityName(ILaunchConfiguration configuration) {
399         String empty = "";
400         String activityName;
401         try {
402             activityName = configuration.getAttribute(ATTR_ACTIVITY, empty);
403         } catch (CoreException e) {
404             return null;
405         }
406 
407         return (activityName != empty) ? activityName : null;
408     }
409 
revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config)410     private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) {
411         AdtPlugin.printErrorToConsole(project,
412                 "No Launcher activity found!",
413                 "The launch will only sync the application package on the device!");
414         config.mLaunchAction = ACTION_DO_NOTHING;
415     }
416 }
417