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.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
    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 
    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
    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
    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      */
    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      */
    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      */
    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 
    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