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