Home | History | Annotate | Download | only in launch
      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 
    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 
    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
    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
    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      */
    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      */
    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      */
    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 
    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