Home | History | Annotate | Download | only in newproject
      1 /*
      2  * Copyright (C) 2011 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.wizards.newproject;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.Nullable;
     21 import com.android.ide.common.xml.ManifestData;
     22 import com.android.ide.common.xml.ManifestData.Activity;
     23 import com.android.ide.eclipse.adt.AdtConstants;
     24 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     25 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     26 import com.android.sdklib.IAndroidTarget;
     27 import com.android.sdklib.internal.project.ProjectProperties;
     28 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     29 import com.android.utils.Pair;
     30 import com.android.xml.AndroidManifest;
     31 
     32 import org.eclipse.core.resources.IProject;
     33 import org.eclipse.core.runtime.Path;
     34 import org.eclipse.core.runtime.Platform;
     35 import org.eclipse.ui.IWorkingSet;
     36 
     37 import java.io.File;
     38 import java.util.ArrayList;
     39 import java.util.List;
     40 
     41 /**
     42  * The {@link NewProjectWizardState} holds the state used by the various pages
     43  * in the {@link NewProjectWizard} and its variations, and it can also be used
     44  * to pass project information to the {@link NewProjectCreator}.
     45  */
     46 public class NewProjectWizardState {
     47     /** The mode to run the wizard in: creating test, or sample, or plain project */
     48     public Mode mode;
     49 
     50     /**
     51      * If true, the project should be created from an existing codebase (pointed
     52      * to by the {@link #projectLocation} or in the case of sample projects, the
     53      * {@link #chosenSample}. Otherwise, create a brand new project from scratch.
     54      */
     55     public boolean useExisting;
     56 
     57     /**
     58      * Whether new projects should be created into the default project location
     59      * (e.g. in the Eclipse workspace) or not
     60      */
     61     public boolean useDefaultLocation = true;
     62 
     63     /** The build target SDK */
     64     public IAndroidTarget target;
     65     /** True if the user has manually modified the target */
     66     public boolean targetModifiedByUser;
     67 
     68     /** The location to store projects into */
     69     public File projectLocation = new File(Platform.getLocation().toOSString());
     70     /** True if the project location name has been manually edited by the user */
     71     public boolean projectLocationModifiedByUser;
     72 
     73     /** The name of the project */
     74     public String projectName = ""; //$NON-NLS-1$
     75     /** True if the project name has been manually edited by the user */
     76     public boolean projectNameModifiedByUser;
     77 
     78     /** The application name */
     79     public String applicationName;
     80     /** True if the application name has been manually edited by the user */
     81     public boolean applicationNameModifiedByUser;
     82 
     83     /** The package path */
     84     public String packageName;
     85     /** True if the package name has been manually edited by the user */
     86     public boolean packageNameModifiedByUser;
     87 
     88     /** True if a new activity should be created */
     89     public boolean createActivity;
     90 
     91     /** The name of the new activity to be created */
     92     public String activityName;
     93     /** True if the activity name has been manually edited by the user */
     94     public boolean activityNameModifiedByUser;
     95 
     96     /** The minimum SDK version to use with the project (may be null or blank) */
     97     public String minSdk;
     98     /** True if the minimum SDK version has been manually edited by the user */
     99     public boolean minSdkModifiedByUser;
    100     /**
    101      * A list of paths to each of the available samples for the current SDK.
    102      * The pair is (String: sample display name => File: sample directory).
    103      * Note we want a list, not a map since we might have duplicates.
    104      * */
    105     public List<Pair<String, File>> samples = new ArrayList<Pair<String, File>>();
    106     /** Path to the currently chosen sample */
    107     public File chosenSample;
    108 
    109     /** The name of the source folder, relative to the project root */
    110     public String sourceFolder = SdkConstants.FD_SOURCES;
    111     /** The set of chosen working sets to use when creating the project */
    112     public IWorkingSet[] workingSets = new IWorkingSet[0];
    113 
    114     /**
    115      * A reference to a different project that the current test project will be
    116      * testing.
    117      */
    118     public IProject testedProject;
    119     /**
    120      * If true, this test project should be testing itself, otherwise it will be
    121      * testing the project pointed to by {@link #testedProject}.
    122      */
    123     public boolean testingSelf;
    124 
    125     // NOTE: These apply only to creating paired projects; when isTest is true
    126     // we're using
    127     // the normal fields above
    128     /**
    129      * If true, create a test project along with this plain project which will
    130      * be testing the plain project. (This flag only applies when creating
    131      * normal projects.)
    132      */
    133     public boolean createPairProject;
    134     /**
    135      * The application name of the test application (only applies when
    136      * {@link #createPairProject} is true)
    137      */
    138     public String testApplicationName;
    139     /**
    140      * True if the testing application name has been modified by the user (only
    141      * applies when {@link #createPairProject} is true)
    142      */
    143     public boolean testApplicationNameModified;
    144     /**
    145      * The package name of the test application (only applies when
    146      * {@link #createPairProject} is true)
    147      */
    148     public String testPackageName;
    149     /**
    150      * True if the testing package name has been modified by the user (only
    151      * applies when {@link #createPairProject} is true)
    152      */
    153     public boolean testPackageModified;
    154     /**
    155      * The project name of the test project (only applies when
    156      * {@link #createPairProject} is true)
    157      */
    158     public String testProjectName;
    159     /**
    160      * True if the testing project name has been modified by the user (only
    161      * applies when {@link #createPairProject} is true)
    162      */
    163     public boolean testProjectModified;
    164     /** Package name of the tested app */
    165     public String testTargetPackageName;
    166 
    167     /**
    168      * Copy project into workspace? This flag only applies when importing
    169      * projects (creating projects from existing source)
    170      */
    171     public boolean copyIntoWorkspace;
    172 
    173     /**
    174      * List of projects to be imported. Null if not importing projects.
    175      */
    176     @Nullable
    177     public List<ImportedProject> importProjects;
    178 
    179     /**
    180      * Creates a new {@link NewProjectWizardState}
    181      *
    182      * @param mode the mode to run the wizard in
    183      */
    184     public NewProjectWizardState(Mode mode) {
    185         this.mode = mode;
    186         if (mode == Mode.SAMPLE) {
    187             useExisting = true;
    188         } else if (mode == Mode.TEST) {
    189             createActivity = false;
    190         }
    191     }
    192 
    193     /**
    194      * Extract information (package name, application name, minimum SDK etc) from
    195      * the given Android project.
    196      *
    197      * @param path the path to the project to extract information from
    198      */
    199     public void extractFromAndroidManifest(Path path) {
    200         String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
    201         if (!(new File(osPath).exists())) {
    202             return;
    203         }
    204 
    205         ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
    206         if (manifestData == null) {
    207             return;
    208         }
    209 
    210         String newPackageName = null;
    211         Activity activity = null;
    212         String newActivityName = null;
    213         String minSdkVersion = null;
    214         try {
    215             newPackageName = manifestData.getPackage();
    216             minSdkVersion = manifestData.getMinSdkVersionString();
    217 
    218             // try to get the first launcher activity. If none, just take the first activity.
    219             activity = manifestData.getLauncherActivity();
    220             if (activity == null) {
    221                 Activity[] activities = manifestData.getActivities();
    222                 if (activities != null && activities.length > 0) {
    223                     activity = activities[0];
    224                 }
    225             }
    226         } catch (Exception e) {
    227             // ignore exceptions
    228         }
    229 
    230         if (newPackageName != null && newPackageName.length() > 0) {
    231             packageName = newPackageName;
    232         }
    233 
    234         if (activity != null) {
    235             newActivityName = AndroidManifest.extractActivityName(activity.getName(),
    236                     newPackageName);
    237         }
    238 
    239         if (newActivityName != null && newActivityName.length() > 0) {
    240             activityName = newActivityName;
    241             // we are "importing" an existing activity, not creating a new one
    242             createActivity = false;
    243 
    244             // If project name and application names are empty, use the activity
    245             // name as a default. If the activity name has dots, it's a part of a
    246             // package specification and only the last identifier must be used.
    247             if (newActivityName.indexOf('.') != -1) {
    248                 String[] ids = newActivityName.split(AdtConstants.RE_DOT);
    249                 newActivityName = ids[ids.length - 1];
    250             }
    251             if (projectName == null || projectName.length() == 0 ||
    252                     !projectNameModifiedByUser) {
    253                 projectName = newActivityName;
    254                 projectNameModifiedByUser = false;
    255             }
    256             if (applicationName == null || applicationName.length() == 0 ||
    257                     !applicationNameModifiedByUser) {
    258                 applicationNameModifiedByUser = false;
    259                 applicationName = newActivityName;
    260             }
    261         } else {
    262             activityName = ""; //$NON-NLS-1$
    263 
    264             // There is no activity name to use to fill in the project and application
    265             // name. However if there's a package name, we can use this as a base.
    266             if (newPackageName != null && newPackageName.length() > 0) {
    267                 // Package name is a java identifier, so it's most suitable for
    268                 // an application name.
    269 
    270                 if (applicationName == null || applicationName.length() == 0 ||
    271                         !applicationNameModifiedByUser) {
    272                     applicationName = newPackageName;
    273                 }
    274 
    275                 // For the project name, remove any dots
    276                 newPackageName = newPackageName.replace('.', '_');
    277                 if (projectName == null || projectName.length() == 0 ||
    278                         !projectNameModifiedByUser) {
    279                     projectName = newPackageName;
    280                 }
    281 
    282             }
    283         }
    284 
    285         if (mode == Mode.ANY && useExisting) {
    286             updateSdkTargetToMatchProject(path.toFile());
    287         }
    288 
    289         minSdk = minSdkVersion;
    290         minSdkModifiedByUser = false;
    291     }
    292 
    293     /**
    294      * Try to find an SDK Target that matches the current MinSdkVersion.
    295      *
    296      * There can be multiple targets with the same sdk api version, so don't change
    297      * it if it's already at the right version. Otherwise pick the first target
    298      * that matches.
    299      */
    300     public void updateSdkTargetToMatchMinSdkVersion() {
    301         IAndroidTarget currentTarget = target;
    302         if (currentTarget != null && currentTarget.getVersion().equals(minSdk)) {
    303             return;
    304         }
    305 
    306         Sdk sdk = Sdk.getCurrent();
    307         if (sdk != null) {
    308             IAndroidTarget[] targets = sdk.getTargets();
    309             for (IAndroidTarget t : targets) {
    310                 if (t.getVersion().equals(minSdk)) {
    311                     target = t;
    312                     return;
    313                 }
    314             }
    315         }
    316     }
    317 
    318     /**
    319      * Updates the SDK to reflect the SDK required by the project at the given
    320      * location
    321      *
    322      * @param location the location of the project
    323      */
    324     public void updateSdkTargetToMatchProject(File location) {
    325         // Select the target matching the manifest's sdk or build properties, if any
    326         IAndroidTarget foundTarget = null;
    327         // This is the target currently in the UI
    328         IAndroidTarget currentTarget = target;
    329         String projectPath = location.getPath();
    330 
    331         // If there's a current target defined, we do not allow to change it when
    332         // operating in the create-from-sample mode -- since the available sample list
    333         // is tied to the current target, so changing it would invalidate the project we're
    334         // trying to load in the first place.
    335         if (!targetModifiedByUser) {
    336             ProjectProperties p = ProjectProperties.load(projectPath,
    337                     PropertyType.PROJECT);
    338             if (p != null) {
    339                 String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
    340                 IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v);
    341                 // We can change the current target if:
    342                 // - we found a new desired target
    343                 // - there is no current target
    344                 // - or the current target can't run the desired target
    345                 if (desiredTarget != null &&
    346                         (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) {
    347                     foundTarget = desiredTarget;
    348                 }
    349             }
    350 
    351             Sdk sdk = Sdk.getCurrent();
    352             IAndroidTarget[] targets = null;
    353             if (sdk != null) {
    354                 targets = sdk.getTargets();
    355             }
    356             if (targets == null) {
    357                 targets = new IAndroidTarget[0];
    358             }
    359 
    360             if (foundTarget == null && minSdk != null) {
    361                 // Otherwise try to match the requested min-sdk-version if we find an
    362                 // exact match, regardless of the currently selected target.
    363                 for (IAndroidTarget existingTarget : targets) {
    364                     if (existingTarget != null &&
    365                             existingTarget.getVersion().equals(minSdk)) {
    366                         foundTarget = existingTarget;
    367                         break;
    368                     }
    369                 }
    370             }
    371 
    372             if (foundTarget == null) {
    373                 // Or last attempt, try to match a sample project location and use it
    374                 // if we find an exact match, regardless of the currently selected target.
    375                 for (IAndroidTarget existingTarget : targets) {
    376                     if (existingTarget != null &&
    377                             projectPath.startsWith(existingTarget.getLocation())) {
    378                         foundTarget = existingTarget;
    379                         break;
    380                     }
    381                 }
    382             }
    383         }
    384 
    385         if (foundTarget != null) {
    386             target = foundTarget;
    387         }
    388     }
    389 
    390     /**
    391      * Type of project being offered/created by the wizard
    392      */
    393     public enum Mode {
    394         /** Create a sample project. Testing options are not presented. */
    395         SAMPLE,
    396 
    397         /**
    398          * Create a test project, either testing itself or some other project.
    399          * Note that even if in the {@link #ANY} mode, a test project can be
    400          * created as a *paired* project with the main project, so this flag
    401          * only means that we are creating *just* a test project
    402          */
    403         TEST,
    404 
    405         /**
    406          * Create an Android project, which can be a plain project, optionally
    407          * with a paired test project, or a sample project (the first page
    408          * contains toggles for choosing which
    409          */
    410         ANY;
    411     }
    412 }
    413