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