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