Home | History | Annotate | Download | only in newproject
      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 /*
     18  * References:
     19  * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
     20  * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
     21  */
     22 
     23 package com.android.ide.eclipse.adt.internal.wizards.newproject;
     24 
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
     27 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
     28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     29 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
     30 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo;
     31 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.MainInfo;
     32 import com.android.sdklib.IAndroidTarget;
     33 import com.android.sdklib.SdkConstants;
     34 import com.android.sdklib.xml.ManifestData;
     35 import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
     36 
     37 import org.eclipse.core.filesystem.URIUtil;
     38 import org.eclipse.core.resources.IProject;
     39 import org.eclipse.core.resources.IResource;
     40 import org.eclipse.core.resources.IWorkspace;
     41 import org.eclipse.core.resources.ResourcesPlugin;
     42 import org.eclipse.core.runtime.IPath;
     43 import org.eclipse.core.runtime.IStatus;
     44 import org.eclipse.core.runtime.Path;
     45 import org.eclipse.core.runtime.Platform;
     46 import org.eclipse.jdt.core.IJavaProject;
     47 import org.eclipse.jdt.core.JavaConventions;
     48 import org.eclipse.jface.wizard.WizardPage;
     49 import org.eclipse.osgi.util.TextProcessor;
     50 import org.eclipse.swt.SWT;
     51 import org.eclipse.swt.custom.ScrolledComposite;
     52 import org.eclipse.swt.events.ControlAdapter;
     53 import org.eclipse.swt.events.ControlEvent;
     54 import org.eclipse.swt.events.ModifyEvent;
     55 import org.eclipse.swt.events.ModifyListener;
     56 import org.eclipse.swt.events.SelectionAdapter;
     57 import org.eclipse.swt.events.SelectionEvent;
     58 import org.eclipse.swt.graphics.Rectangle;
     59 import org.eclipse.swt.layout.GridData;
     60 import org.eclipse.swt.layout.GridLayout;
     61 import org.eclipse.swt.widgets.Button;
     62 import org.eclipse.swt.widgets.Composite;
     63 import org.eclipse.swt.widgets.Control;
     64 import org.eclipse.swt.widgets.DirectoryDialog;
     65 import org.eclipse.swt.widgets.Event;
     66 import org.eclipse.swt.widgets.Group;
     67 import org.eclipse.swt.widgets.Label;
     68 import org.eclipse.swt.widgets.Listener;
     69 import org.eclipse.swt.widgets.Text;
     70 
     71 import java.io.File;
     72 import java.net.URI;
     73 import java.util.ArrayList;
     74 import java.util.regex.Pattern;
     75 
     76 /**
     77  * NewAndroidProjectCreationPage is a project creation page that provides the
     78  * following fields:
     79  * <ul>
     80  * <li> Project name
     81  * <li> SDK Target
     82  * <li> Application name
     83  * <li> Package name
     84  * <li> Activity name
     85  * </ul>
     86  * Note: this class is public so that it can be accessed from unit tests.
     87  * It is however an internal class. Its API may change without notice.
     88  * It should semantically be considered as a private final class.
     89  * Do not derive from this class.
     90  */
     91 public class NewTestProjectCreationPage extends WizardPage {
     92 
     93     // constants
     94     static final String TEST_PAGE_NAME = "newAndroidTestProjectPage"; //$NON-NLS-1$
     95 
     96     /** Initial value for all name fields (project, activity, application, package). Used
     97      * whenever a value is requested before controls are created. */
     98     private static final String INITIAL_NAME = "";  //$NON-NLS-1$
     99     /** Initial value for the Use Default Location check box. */
    100     private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
    101     /** Initial value for the Create Test Project check box. */
    102     private static final boolean INITIAL_CREATE_TEST_PROJECT = false;
    103 
    104 
    105     /** Pattern for characters accepted in a project name. Since this will be used as a
    106      * directory name, we're being a bit conservative on purpose. It cannot start with a space. */
    107     private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$");  //$NON-NLS-1$
    108     /** Last user-browsed location, static so that it be remembered for the whole session */
    109     private static String sCustomLocationOsPath = "";  //$NON-NLS-1$
    110 
    111     private final int MSG_NONE = 0;
    112     private final int MSG_WARNING = 1;
    113     private final int MSG_ERROR = 2;
    114 
    115     /** Structure with the externally visible information from this Test Project page. */
    116     private final TestInfo mInfo = new TestInfo();
    117     /** Structure with the externally visible information from the Main Project page.
    118      *  This is null if there's no such page, meaning the test project page is standalone. */
    119     private IMainInfo mMainInfo;
    120 
    121     // widgets
    122     private Text mProjectNameField;
    123     private Text mPackageNameField;
    124     private Text mApplicationNameField;
    125     private Button mUseDefaultLocation;
    126     private Label mLocationLabel;
    127     private Text mLocationPathField;
    128     private Button mBrowseButton;
    129     private Text mMinSdkVersionField;
    130     private SdkTargetSelector mSdkTargetSelector;
    131     private ITargetChangeListener mSdkTargetChangeListener;
    132     private Button mCreateTestProjectField;
    133     private Text mTestedProjectNameField;
    134     private Button mProjectBrowseButton;
    135     private ProjectChooserHelper mProjectChooserHelper;
    136     private Button mTestSelfProjectRadio;
    137     private Button mTestExistingProjectRadio;
    138 
    139     /** A list of composites that are disabled when the "Create Test Project" toggle is off. */
    140     private ArrayList<Composite> mToggleComposites = new ArrayList<Composite>();
    141 
    142     private boolean mInternalProjectNameUpdate;
    143     private boolean mInternalLocationPathUpdate;
    144     private boolean mInternalPackageNameUpdate;
    145     private boolean mInternalApplicationNameUpdate;
    146     private boolean mInternalMinSdkVersionUpdate;
    147     private boolean mInternalSdkTargetUpdate;
    148     private IProject mExistingTestedProject;
    149     private boolean mProjectNameModifiedByUser;
    150     private boolean mApplicationNameModifiedByUser;
    151     private boolean mPackageNameModifiedByUser;
    152     private boolean mMinSdkVersionModifiedByUser;
    153     private boolean mSdkTargetModifiedByUser;
    154 
    155     private Label mTestTargetPackageLabel;
    156 
    157     private String mLastExistingPackageName;
    158 
    159 
    160     /**
    161      * Creates a new project creation wizard page.
    162      */
    163     public NewTestProjectCreationPage() {
    164         super(TEST_PAGE_NAME);
    165         setPageComplete(false);
    166         setTitle("New Android Test Project");
    167         setDescription("Creates a new Android Test Project resource.");
    168     }
    169 
    170     // --- Getters used by NewProjectWizard ---
    171 
    172     /**
    173      * Structure that collects all externally visible information from this page.
    174      * This is used by the calling wizard to actually do the work or by other pages.
    175      */
    176     public class TestInfo {
    177 
    178         /** Returns true if a new Test Project should be created. */
    179         public boolean getCreateTestProject() {
    180             return mCreateTestProjectField == null ? true : mCreateTestProjectField.getSelection();
    181         }
    182 
    183         /**
    184          * Returns the current project location path as entered by the user, or its
    185          * anticipated initial value. Note that if the default has been returned the
    186          * path in a project description used to create a project should not be set.
    187          *
    188          * @return the project location path or its anticipated initial value.
    189          */
    190         public IPath getLocationPath() {
    191             return new Path(getProjectLocation());
    192         }
    193 
    194         /** Returns the value of the project name field with leading and trailing spaces removed. */
    195         public String getProjectName() {
    196             return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
    197         }
    198 
    199         /** Returns the value of the package name field with spaces trimmed. */
    200         public String getPackageName() {
    201             return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
    202         }
    203 
    204         /** Returns the value of the test target package name field with spaces trimmed. */
    205         public String getTargetPackageName() {
    206             return mTestTargetPackageLabel == null ? INITIAL_NAME
    207                                                    : mTestTargetPackageLabel.getText().trim();
    208         }
    209 
    210         /** Returns the value of the min sdk version field with spaces trimmed. */
    211         public String getMinSdkVersion() {
    212             return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim();  //$NON-NLS-1$
    213         }
    214 
    215         /** Returns the value of the application name field with spaces trimmed. */
    216         public String getApplicationName() {
    217             // Return the name of the activity as default application name.
    218             return mApplicationNameField == null ? "" : mApplicationNameField.getText().trim();  //$NON-NLS-1$
    219         }
    220 
    221         /** Returns the value of the Use Default Location field. */
    222         public boolean useDefaultLocation() {
    223             return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
    224                                                : mUseDefaultLocation.getSelection();
    225         }
    226 
    227         /** Returns the the default "src" constant. */
    228         public String getSourceFolder() {
    229             return SdkConstants.FD_SOURCES;
    230         }
    231 
    232         /** Returns the current sdk target or null if none has been selected yet. */
    233         public IAndroidTarget getSdkTarget() {
    234             return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
    235         }
    236 
    237         public boolean isTestingSelf() {
    238             return mMainInfo == null &&
    239                 (mTestSelfProjectRadio == null ? false : mTestSelfProjectRadio.getSelection());
    240         }
    241 
    242         public boolean isTestingMain() {
    243             return mMainInfo != null;
    244         }
    245 
    246         public boolean isTestingExisting() {
    247             return mMainInfo == null &&
    248                 (mTestExistingProjectRadio == null ? false
    249                                                    : mTestExistingProjectRadio.getSelection());
    250         }
    251 
    252         public IProject getExistingTestedProject() {
    253             return mExistingTestedProject;
    254         }
    255     }
    256 
    257     /**
    258      * Returns a {@link TestInfo} structure that collects all externally visible information
    259      * from this page. This is used by the calling wizard to actually do the work or by other pages.
    260      */
    261     public TestInfo getTestInfo() {
    262         return mInfo;
    263     }
    264 
    265     /**
    266      * Grabs the {@link MainInfo} structure with visible parameters from the main project page.
    267      * This may be null.
    268      */
    269     public void setMainInfo(IMainInfo mainInfo) {
    270         mMainInfo = mainInfo;
    271     }
    272 
    273     // --- UI creation ---
    274 
    275     /**
    276      * Creates the top level control for this dialog page under the given parent
    277      * composite.
    278      *
    279      * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
    280      */
    281     public void createControl(Composite parent) {
    282         final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
    283         scrolledComposite.setFont(parent.getFont());
    284         scrolledComposite.setExpandHorizontal(true);
    285         scrolledComposite.setExpandVertical(true);
    286         initializeDialogUnits(parent);
    287 
    288         final Composite composite = new Composite(scrolledComposite, SWT.NULL);
    289         composite.setFont(parent.getFont());
    290         scrolledComposite.setContent(composite);
    291 
    292         composite.setLayout(new GridLayout());
    293         composite.setLayoutData(new GridData(GridData.FILL_BOTH));
    294 
    295         createToggleTestProject(composite);
    296         createTestProjectGroup(composite);
    297         createLocationGroup(composite);
    298         createTestTargetGroup(composite);
    299         createTargetGroup(composite);
    300         createPropertiesGroup(composite);
    301 
    302         // Update state the first time
    303         enableLocationWidgets();
    304 
    305         scrolledComposite.addControlListener(new ControlAdapter() {
    306             @Override
    307             public void controlResized(ControlEvent e) {
    308                 Rectangle r = scrolledComposite.getClientArea();
    309                 scrolledComposite.setMinSize(composite.computeSize(r.width, SWT.DEFAULT));
    310             }
    311         });
    312 
    313         // Show description the first time
    314         setErrorMessage(null);
    315         setMessage(null);
    316         setControl(scrolledComposite);
    317 
    318         // Validate. This will complain about the first empty field.
    319         validatePageComplete();
    320     }
    321 
    322     /**
    323      * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
    324      * the dialog is made visible and to also update the enabled/disabled state of some
    325      * controls (doing so in createControl doesn't always change their state somehow.)
    326      */
    327     @Override
    328     public void setVisible(boolean visible) {
    329         super.setVisible(visible);
    330         if (visible) {
    331             mProjectNameField.setFocus();
    332             validatePageComplete();
    333             onCreateTestProjectToggle();
    334             onExistingProjectChanged();
    335         }
    336     }
    337 
    338     @Override
    339     public void dispose() {
    340 
    341         if (mSdkTargetChangeListener != null) {
    342             AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
    343             mSdkTargetChangeListener = null;
    344         }
    345 
    346         super.dispose();
    347     }
    348 
    349     /**
    350      * Creates the "create test project" checkbox but only if there's a main page in the wizard.
    351      *
    352      * @param parent the parent composite
    353      */
    354     private final void createToggleTestProject(Composite parent) {
    355 
    356         if (mMainInfo != null) {
    357             mCreateTestProjectField = new Button(parent, SWT.CHECK);
    358             mCreateTestProjectField.setText("Create a Test Project");
    359             mCreateTestProjectField.setToolTipText("Select this if you also want to create a Test Project.");
    360             mCreateTestProjectField.setSelection(INITIAL_CREATE_TEST_PROJECT);
    361             mCreateTestProjectField.addSelectionListener(new SelectionAdapter() {
    362                 @Override
    363                 public void widgetSelected(SelectionEvent e) {
    364                     onCreateTestProjectToggle();
    365                 }
    366             });
    367         }
    368     }
    369 
    370     /**
    371      * Creates the group for the project name:
    372      * [label: "Project Name"] [text field]
    373      *
    374      * @param parent the parent composite
    375      */
    376     private final void createTestProjectGroup(Composite parent) {
    377         Composite group = new Composite(parent, SWT.NONE);
    378         GridLayout layout = new GridLayout();
    379         layout.numColumns = 2;
    380         group.setLayout(layout);
    381         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    382 
    383         mToggleComposites.add(group);
    384 
    385         // --- test project name ---
    386 
    387         // new project label
    388         String tooltip = "Name of the Eclipse test project to create. It cannot be empty.";
    389         Label label = new Label(group, SWT.NONE);
    390         label.setText("Test Project Name:");
    391         label.setFont(parent.getFont());
    392         label.setToolTipText(tooltip);
    393 
    394         // new project name entry field
    395         mProjectNameField = new Text(group, SWT.BORDER);
    396         GridData data = new GridData(GridData.FILL_HORIZONTAL);
    397         mProjectNameField.setToolTipText(tooltip);
    398         mProjectNameField.setLayoutData(data);
    399         mProjectNameField.setFont(parent.getFont());
    400         mProjectNameField.addListener(SWT.Modify, new Listener() {
    401             public void handleEvent(Event event) {
    402                 if (!mInternalProjectNameUpdate) {
    403                     mProjectNameModifiedByUser = true;
    404                 }
    405                 updateLocationPathField(null);
    406             }
    407         });
    408     }
    409 
    410     private final void createLocationGroup(Composite parent) {
    411 
    412         // --- project location ---
    413 
    414         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
    415         group.setLayout(new GridLayout(3, /* num columns */
    416                 false /* columns of not equal size */));
    417         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    418         group.setFont(parent.getFont());
    419         group.setText("Content");
    420 
    421         mToggleComposites.add(group);
    422 
    423         mUseDefaultLocation = new Button(group, SWT.CHECK);
    424         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
    425         gd.horizontalSpan = 3;
    426         mUseDefaultLocation.setLayoutData(gd);
    427         mUseDefaultLocation.setText("Use default location");
    428         mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION);
    429 
    430         mUseDefaultLocation.addSelectionListener(new SelectionAdapter() {
    431             @Override
    432             public void widgetSelected(SelectionEvent e) {
    433                 super.widgetSelected(e);
    434                 enableLocationWidgets();
    435                 validatePageComplete();
    436             }
    437         });
    438 
    439 
    440         mLocationLabel = new Label(group, SWT.NONE);
    441         mLocationLabel.setText("Location:");
    442 
    443         mLocationPathField = new Text(group, SWT.BORDER);
    444         GridData data = new GridData(GridData.FILL, /* horizontal alignment */
    445                 GridData.BEGINNING, /* vertical alignment */
    446                 true,  /* grabExcessHorizontalSpace */
    447                 false, /* grabExcessVerticalSpace */
    448                 1,     /* horizontalSpan */
    449                 1);    /* verticalSpan */
    450         mLocationPathField.setLayoutData(data);
    451         mLocationPathField.setFont(parent.getFont());
    452         mLocationPathField.addListener(SWT.Modify, new Listener() {
    453            public void handleEvent(Event event) {
    454                onLocationPathFieldModified();
    455             }
    456         });
    457 
    458         mBrowseButton = new Button(group, SWT.PUSH);
    459         mBrowseButton.setText("Browse...");
    460         setButtonLayoutData(mBrowseButton);
    461         mBrowseButton.addSelectionListener(new SelectionAdapter() {
    462             @Override
    463             public void widgetSelected(SelectionEvent e) {
    464                 onOpenDirectoryBrowser();
    465             }
    466         });
    467     }
    468 
    469     /**
    470      * Creates the group for Test Target options.
    471      *
    472      * There are two different modes here:
    473      * <ul>
    474      * <li>When mMainInfo exists, this is part of a new Android Project. In which case
    475      * the new test is tied to the soon-to-be main project and there is actually no choice.
    476      * <li>When mMainInfo does not exist, this is a standalone new test project. In this case
    477      * we offer 2 options for the test target: self test or against an existing Android project.
    478      * </ul>
    479      *
    480      * @param parent the parent composite
    481      */
    482     private final void createTestTargetGroup(Composite parent) {
    483 
    484         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
    485         GridLayout layout = new GridLayout();
    486         layout.numColumns = 3;
    487         group.setLayout(layout);
    488         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    489         group.setFont(parent.getFont());
    490         group.setText("Test Target");
    491 
    492         mToggleComposites.add(group);
    493 
    494         if (mMainInfo == null) {
    495             // Standalone mode: choose between self-test and existing-project test
    496 
    497             Label label = new Label(group, SWT.NONE);
    498             label.setText("Select the project to test:");
    499             GridData gd = new GridData(GridData.FILL_HORIZONTAL);
    500             gd.horizontalSpan = 3;
    501             label.setLayoutData(gd);
    502 
    503             mTestSelfProjectRadio = new Button(group, SWT.RADIO);
    504             mTestSelfProjectRadio.setText("This project");
    505             mTestSelfProjectRadio.setSelection(false);
    506             gd = new GridData(GridData.FILL_HORIZONTAL);
    507             gd.horizontalSpan = 3;
    508             mTestSelfProjectRadio.setLayoutData(gd);
    509 
    510             mTestExistingProjectRadio = new Button(group, SWT.RADIO);
    511             mTestExistingProjectRadio.setText("An existing Android project");
    512             mTestExistingProjectRadio.setSelection(mMainInfo == null);
    513             mTestExistingProjectRadio.addSelectionListener(new SelectionAdapter() {
    514                 @Override
    515                 public void widgetSelected(SelectionEvent e) {
    516                     onExistingProjectChanged();
    517                 }
    518             });
    519 
    520             String tooltip = "The existing Android Project that is being tested.";
    521 
    522             mTestedProjectNameField = new Text(group, SWT.BORDER);
    523             mTestedProjectNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    524             mTestedProjectNameField.setToolTipText(tooltip);
    525             mTestedProjectNameField.addModifyListener(new ModifyListener() {
    526                 public void modifyText(ModifyEvent e) {
    527                     onProjectFieldUpdated();
    528                 }
    529             });
    530 
    531             mProjectBrowseButton = new Button(group, SWT.NONE);
    532             mProjectBrowseButton.setText("Browse...");
    533             mProjectBrowseButton.setToolTipText("Allows you to select the Android project to test.");
    534             mProjectBrowseButton.addSelectionListener(new SelectionAdapter() {
    535                @Override
    536                 public void widgetSelected(SelectionEvent e) {
    537                    onProjectBrowse();
    538                 }
    539             });
    540 
    541             mProjectChooserHelper = new ProjectChooserHelper(parent.getShell(), null /*filter*/);
    542         } else {
    543             // Part of NPW mode: no selection.
    544 
    545         }
    546 
    547         // package label line
    548 
    549         Label label = new Label(group, SWT.NONE);
    550         label.setText("Test Target Package:");
    551         mTestTargetPackageLabel = new Label(group, SWT.NONE);
    552         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
    553         gd.horizontalSpan = 2;
    554         mTestTargetPackageLabel.setLayoutData(gd);
    555     }
    556 
    557     /**
    558      * Creates the target group.
    559      * It only contains an SdkTargetSelector.
    560      */
    561     private void createTargetGroup(Composite parent) {
    562         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
    563         // Layout has 1 column
    564         group.setLayout(new GridLayout());
    565         group.setLayoutData(new GridData(GridData.FILL_BOTH));
    566         group.setFont(parent.getFont());
    567         group.setText("Build Target");
    568 
    569         mToggleComposites.add(group);
    570 
    571         // The selector is created without targets. They are added below in the change listener.
    572         mSdkTargetSelector = new SdkTargetSelector(group, null);
    573 
    574         mSdkTargetChangeListener = new ITargetChangeListener() {
    575             public void onSdkLoaded() {
    576                 // Update the sdk target selector with the new targets
    577 
    578                 // get the targets from the sdk
    579                 IAndroidTarget[] targets = null;
    580                 if (Sdk.getCurrent() != null) {
    581                     targets = Sdk.getCurrent().getTargets();
    582                 }
    583                 mSdkTargetSelector.setTargets(targets);
    584 
    585                 // If there's only one target, select it
    586                 if (targets != null && targets.length == 1) {
    587                     mSdkTargetSelector.setSelection(targets[0]);
    588                 }
    589             }
    590 
    591             public void onProjectTargetChange(IProject changedProject) {
    592                 // Ignore
    593             }
    594 
    595             public void onTargetLoaded(IAndroidTarget target) {
    596                 // Ignore
    597             }
    598         };
    599 
    600         AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
    601 
    602         // Invoke it once to initialize the targets
    603         mSdkTargetChangeListener.onSdkLoaded();
    604 
    605         mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
    606             @Override
    607             public void widgetSelected(SelectionEvent e) {
    608                 onSdkTargetModified();
    609                 updateLocationPathField(null);
    610                 validatePageComplete();
    611             }
    612         });
    613     }
    614 
    615     /**
    616      * Creates the group for the project properties:
    617      * - Package name [text field]
    618      * - Activity name [text field]
    619      * - Application name [text field]
    620      *
    621      * @param parent the parent composite
    622      */
    623     private final void createPropertiesGroup(Composite parent) {
    624         // package specification group
    625         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
    626         GridLayout layout = new GridLayout();
    627         layout.numColumns = 2;
    628         group.setLayout(layout);
    629         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    630         group.setFont(parent.getFont());
    631         group.setText("Properties");
    632 
    633         mToggleComposites.add(group);
    634 
    635         // new application label
    636         Label label = new Label(group, SWT.NONE);
    637         label.setText("Application name:");
    638         label.setFont(parent.getFont());
    639         label.setToolTipText("Name of the Application. This is a free string. It can be empty.");
    640 
    641         // new application name entry field
    642         mApplicationNameField = new Text(group, SWT.BORDER);
    643         GridData data = new GridData(GridData.FILL_HORIZONTAL);
    644         mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty.");
    645         mApplicationNameField.setLayoutData(data);
    646         mApplicationNameField.setFont(parent.getFont());
    647         mApplicationNameField.addListener(SWT.Modify, new Listener() {
    648            public void handleEvent(Event event) {
    649                if (!mInternalApplicationNameUpdate) {
    650                    mApplicationNameModifiedByUser = true;
    651                }
    652            }
    653         });
    654 
    655         // new package label
    656         label = new Label(group, SWT.NONE);
    657         label.setText("Package name:");
    658         label.setFont(parent.getFont());
    659         label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
    660 
    661         // new package name entry field
    662         mPackageNameField = new Text(group, SWT.BORDER);
    663         data = new GridData(GridData.FILL_HORIZONTAL);
    664         mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
    665         mPackageNameField.setLayoutData(data);
    666         mPackageNameField.setFont(parent.getFont());
    667         mPackageNameField.addListener(SWT.Modify, new Listener() {
    668             public void handleEvent(Event event) {
    669                 if (!mInternalPackageNameUpdate) {
    670                     mPackageNameModifiedByUser = true;
    671                 }
    672                 onPackageNameFieldModified();
    673             }
    674         });
    675 
    676         // min sdk version label
    677         label = new Label(group, SWT.NONE);
    678         label.setText("Min SDK Version:");
    679         label.setFont(parent.getFont());
    680         label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
    681 
    682         // min sdk version entry field
    683         mMinSdkVersionField = new Text(group, SWT.BORDER);
    684         data = new GridData(GridData.FILL_HORIZONTAL);
    685         label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
    686         mMinSdkVersionField.setLayoutData(data);
    687         mMinSdkVersionField.setFont(parent.getFont());
    688         mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
    689             public void handleEvent(Event event) {
    690                 onMinSdkVersionFieldModified();
    691                 validatePageComplete();
    692             }
    693         });
    694     }
    695 
    696 
    697     //--- Internal getters & setters ------------------
    698 
    699     /** Returns the location path field value with spaces trimmed. */
    700     private String getLocationPathFieldValue() {
    701         return mLocationPathField == null ? "" : mLocationPathField.getText().trim();  //$NON-NLS-1$
    702     }
    703 
    704     /** Returns the current project location, depending on the Use Default Location check box. */
    705     private String getProjectLocation() {
    706         if (mInfo.useDefaultLocation()) {
    707             return Platform.getLocation().toString();
    708         } else {
    709             return getLocationPathFieldValue();
    710         }
    711     }
    712 
    713     /**
    714      * Creates a project resource handle for the current project name field
    715      * value.
    716      * <p>
    717      * This method does not create the project resource; this is the
    718      * responsibility of <code>IProject::create</code> invoked by the new
    719      * project resource wizard.
    720      * </p>
    721      *
    722      * @return the new project resource handle
    723      */
    724     private IProject getProjectHandle() {
    725         return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
    726     }
    727 
    728     // --- UI Callbacks ----
    729 
    730     /**
    731      * Callback invoked when the user toggles the "Test target: Existing Android Project"
    732      * checkbox. It enables or disable the UI to select an existing project.
    733      */
    734     private void onExistingProjectChanged() {
    735         if (mInfo.isTestingExisting()) {
    736             boolean enabled = mTestExistingProjectRadio.getSelection();
    737             mTestedProjectNameField.setEnabled(enabled);
    738             mProjectBrowseButton.setEnabled(enabled);
    739             setExistingProject(mInfo.getExistingTestedProject());
    740             validatePageComplete();
    741         }
    742     }
    743 
    744     /**
    745      * Tries to load the defaults from the main page if possible.
    746      */
    747     private void useMainProjectInformation() {
    748         if (mInfo.isTestingMain() && mMainInfo != null) {
    749 
    750             String projName = String.format("%1$sTest", mMainInfo.getProjectName());
    751             String appName = String.format("%1$sTest", mMainInfo.getApplicationName());
    752 
    753             String packageName = mMainInfo.getPackageName();
    754             if (packageName == null) {
    755                 packageName = "";  //$NON-NLS-1$
    756             }
    757 
    758             updateTestTargetPackageField(packageName);
    759 
    760             if (!mProjectNameModifiedByUser) {
    761                 mInternalProjectNameUpdate = true;
    762                 mProjectNameField.setText(projName);  //$NON-NLS-1$
    763                 mInternalProjectNameUpdate = false;
    764             }
    765 
    766             if (!mApplicationNameModifiedByUser) {
    767                 mInternalApplicationNameUpdate = true;
    768                 mApplicationNameField.setText(appName);
    769                 mInternalApplicationNameUpdate = false;
    770             }
    771 
    772             if (!mPackageNameModifiedByUser) {
    773                 mInternalPackageNameUpdate = true;
    774                 packageName += ".test";  //$NON-NLS-1$
    775                 mPackageNameField.setText(packageName);
    776                 mInternalPackageNameUpdate = false;
    777             }
    778 
    779             if (!mSdkTargetModifiedByUser) {
    780                 mInternalSdkTargetUpdate = true;
    781                 mSdkTargetSelector.setSelection(mMainInfo.getSdkTarget());
    782                 mInternalSdkTargetUpdate = false;
    783             }
    784 
    785             if (!mMinSdkVersionModifiedByUser) {
    786                 mInternalMinSdkVersionUpdate = true;
    787                 mMinSdkVersionField.setText(mMainInfo.getMinSdkVersion());
    788                 mInternalMinSdkVersionUpdate = false;
    789             }
    790         }
    791     }
    792 
    793     /**
    794      * Callback invoked when the user edits the project text field.
    795      */
    796     private void onProjectFieldUpdated() {
    797         String project = mTestedProjectNameField.getText();
    798 
    799         // Is this a valid project?
    800         IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/);
    801         for (IJavaProject p : projects) {
    802             if (p.getProject().getName().equals(project)) {
    803                 setExistingProject(p.getProject());
    804                 return;
    805             }
    806         }
    807     }
    808 
    809     /**
    810      * Callback called when the user uses the "Browse Projects" button.
    811      */
    812     private void onProjectBrowse() {
    813         IJavaProject p = mProjectChooserHelper.chooseJavaProject(mTestedProjectNameField.getText(),
    814                 null /*message*/);
    815         if (p != null) {
    816             setExistingProject(p.getProject());
    817             mTestedProjectNameField.setText(mExistingTestedProject.getName());
    818         }
    819     }
    820 
    821     private void setExistingProject(IProject project) {
    822         mExistingTestedProject = project;
    823 
    824         // Try to update the application, package, sdk target and minSdkVersion accordingly
    825         if (project != null &&
    826                 (!mApplicationNameModifiedByUser ||
    827                  !mPackageNameModifiedByUser     ||
    828                  !mSdkTargetModifiedByUser       ||
    829                  !mMinSdkVersionModifiedByUser)) {
    830 
    831             ManifestData manifestData = AndroidManifestHelper.parseForData(project);
    832             if (manifestData != null) {
    833                 String appName = String.format("%1$sTest", project.getName());
    834                 String packageName = manifestData.getPackage();
    835                 String minSdkVersion = manifestData.getMinSdkVersionString();
    836                 IAndroidTarget sdkTarget = null;
    837                 if (Sdk.getCurrent() != null) {
    838                     sdkTarget = Sdk.getCurrent().getTarget(project);
    839                 }
    840 
    841                 if (packageName == null) {
    842                     packageName = "";  //$NON-NLS-1$
    843                 }
    844                 mLastExistingPackageName = packageName;
    845 
    846                 if (!mProjectNameModifiedByUser) {
    847                     mInternalProjectNameUpdate = true;
    848                     mProjectNameField.setText(appName);
    849                     mInternalProjectNameUpdate = false;
    850                 }
    851 
    852                 if (!mApplicationNameModifiedByUser) {
    853                     mInternalApplicationNameUpdate = true;
    854                     mApplicationNameField.setText(appName);
    855                     mInternalApplicationNameUpdate = false;
    856                 }
    857 
    858                 if (!mPackageNameModifiedByUser) {
    859                     mInternalPackageNameUpdate = true;
    860                     packageName += ".test";  //$NON-NLS-1$
    861                     mPackageNameField.setText(packageName);  //$NON-NLS-1$
    862                     mInternalPackageNameUpdate = false;
    863                 }
    864 
    865                 if (!mSdkTargetModifiedByUser && sdkTarget != null) {
    866                     mInternalSdkTargetUpdate = true;
    867                     mSdkTargetSelector.setSelection(sdkTarget);
    868                     mInternalSdkTargetUpdate = false;
    869                 }
    870 
    871                 if (!mMinSdkVersionModifiedByUser) {
    872                     mInternalMinSdkVersionUpdate = true;
    873                     if (minSdkVersion != null) {
    874                         mMinSdkVersionField.setText(minSdkVersion);
    875                     }
    876                     if (sdkTarget == null) {
    877                         updateSdkSelectorToMatchMinSdkVersion();
    878                     }
    879                     mInternalMinSdkVersionUpdate = false;
    880                 }
    881             }
    882         }
    883 
    884         updateTestTargetPackageField(mLastExistingPackageName);
    885         validatePageComplete();
    886     }
    887 
    888     /**
    889      * Display a directory browser and update the location path field with the selected path
    890      */
    891     private void onOpenDirectoryBrowser() {
    892 
    893         String existing_dir = getLocationPathFieldValue();
    894 
    895         // Disable the path if it doesn't exist
    896         if (existing_dir.length() == 0) {
    897             existing_dir = null;
    898         } else {
    899             File f = new File(existing_dir);
    900             if (!f.exists()) {
    901                 existing_dir = null;
    902             }
    903         }
    904 
    905         DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
    906         dd.setMessage("Browse for folder");
    907         dd.setFilterPath(existing_dir);
    908         String abs_dir = dd.open();
    909 
    910         if (abs_dir != null) {
    911             updateLocationPathField(abs_dir);
    912             validatePageComplete();
    913         }
    914     }
    915 
    916     /**
    917      * Callback when the "create test project" checkbox is changed.
    918      * It enables or disables all UI groups accordingly.
    919      */
    920     private void onCreateTestProjectToggle() {
    921         boolean enabled = mInfo.getCreateTestProject();
    922         for (Composite c : mToggleComposites) {
    923             enableControl(c, enabled);
    924         }
    925         mSdkTargetSelector.setEnabled(enabled);
    926 
    927         if (enabled) {
    928             useMainProjectInformation();
    929         }
    930         validatePageComplete();
    931     }
    932 
    933     /** Enables or disables controls; recursive for composite controls. */
    934     private void enableControl(Control c, boolean enabled) {
    935         c.setEnabled(enabled);
    936         if (c instanceof Composite)
    937         for (Control c2 : ((Composite) c).getChildren()) {
    938             enableControl(c2, enabled);
    939         }
    940     }
    941 
    942     /**
    943      * Enables or disable the location widgets depending on the user selection:
    944      * the location path is enabled when using the "existing source" mode (i.e. not new project)
    945      * or in new project mode with the "use default location" turned off.
    946      */
    947     private void enableLocationWidgets() {
    948         boolean use_default = mInfo.useDefaultLocation();
    949         boolean location_enabled = !use_default;
    950 
    951         mLocationLabel.setEnabled(location_enabled);
    952         mLocationPathField.setEnabled(location_enabled);
    953         mBrowseButton.setEnabled(location_enabled);
    954 
    955         updateLocationPathField(null);
    956     }
    957 
    958     /**
    959      * Updates the location directory path field.
    960      * <br/>
    961      * When custom user selection is enabled, use the abs_dir argument if not null and also
    962      * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the
    963      * user selection to be remembered when the user switches from default to custom.
    964      * <br/>
    965      * When custom user selection is disabled, use the workspace default location with the
    966      * current project name. This does not change the internally cached abs_dir.
    967      *
    968      * @param abs_dir A new absolute directory path or null to use the default.
    969      */
    970     private void updateLocationPathField(String abs_dir) {
    971         boolean use_default = mInfo.useDefaultLocation();
    972         boolean custom_location = !use_default;
    973 
    974         if (!mInternalLocationPathUpdate) {
    975             mInternalLocationPathUpdate = true;
    976             if (custom_location) {
    977                 if (abs_dir != null) {
    978                     // We get here if the user selected a directory with the "Browse" button.
    979                     sCustomLocationOsPath = TextProcessor.process(abs_dir);
    980                 }
    981                 if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) {
    982                     mLocationPathField.setText(sCustomLocationOsPath);
    983                 }
    984             } else {
    985                 String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
    986                 value = TextProcessor.process(value);
    987                 if (!mLocationPathField.getText().equals(value)) {
    988                     mLocationPathField.setText(value);
    989                 }
    990             }
    991             validatePageComplete();
    992             mInternalLocationPathUpdate = false;
    993         }
    994     }
    995 
    996     /**
    997      * The location path field is either modified internally (from updateLocationPathField)
    998      * or manually by the user when the custom_location mode is not set.
    999      *
   1000      * Ignore the internal modification. When modified by the user, memorize the choice and
   1001      * validate the page.
   1002      */
   1003     private void onLocationPathFieldModified() {
   1004         if (!mInternalLocationPathUpdate) {
   1005             // When the updates doesn't come from updateLocationPathField, it must be the user
   1006             // editing the field manually, in which case we want to save the value internally
   1007             String newPath = getLocationPathFieldValue();
   1008             sCustomLocationOsPath = newPath;
   1009             validatePageComplete();
   1010         }
   1011     }
   1012 
   1013     /**
   1014      * The package name field is either modified internally (from extractNamesFromAndroidManifest)
   1015      * or manually by the user when the custom_location mode is not set.
   1016      *
   1017      * Ignore the internal modification. When modified by the user, memorize the choice and
   1018      * validate the page.
   1019      */
   1020     private void onPackageNameFieldModified() {
   1021         updateTestTargetPackageField(null);
   1022         validatePageComplete();
   1023     }
   1024 
   1025     /**
   1026      * Changes the {@link #mTestTargetPackageLabel} field.
   1027      *
   1028      * When using the "self-test" option, the packageName argument is ignored and the
   1029      * current value from the project package is used.
   1030      *
   1031      * Otherwise the packageName is used if it is not null.
   1032      */
   1033     private void updateTestTargetPackageField(String packageName) {
   1034         if (mInfo.isTestingSelf()) {
   1035             mTestTargetPackageLabel.setText(mInfo.getPackageName());
   1036 
   1037         } else if (packageName != null) {
   1038             mTestTargetPackageLabel.setText(packageName);
   1039         }
   1040     }
   1041 
   1042     /**
   1043      * Called when the min sdk version field has been modified.
   1044      *
   1045      * Ignore the internal modifications. When modified by the user, try to match
   1046      * a target with the same API level.
   1047      */
   1048     private void onMinSdkVersionFieldModified() {
   1049         if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
   1050             return;
   1051         }
   1052 
   1053         updateSdkSelectorToMatchMinSdkVersion();
   1054 
   1055         mMinSdkVersionModifiedByUser = true;
   1056     }
   1057 
   1058     /**
   1059      * Try to find an SDK Target that matches the current MinSdkVersion.
   1060      *
   1061      * There can be multiple targets with the same sdk api version, so don't change
   1062      * it if it's already at the right version. Otherwise pick the first target
   1063      * that matches.
   1064      */
   1065     private void updateSdkSelectorToMatchMinSdkVersion() {
   1066         String minSdkVersion = mInfo.getMinSdkVersion();
   1067 
   1068         IAndroidTarget curr_target = mInfo.getSdkTarget();
   1069         if (curr_target != null && curr_target.getVersion().equals(minSdkVersion)) {
   1070             return;
   1071         }
   1072 
   1073         for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
   1074             if (target.getVersion().equals(minSdkVersion)) {
   1075                 mSdkTargetSelector.setSelection(target);
   1076                 return;
   1077             }
   1078         }
   1079     }
   1080 
   1081     /**
   1082      * Called when an SDK target is modified.
   1083      *
   1084      * Also changes the minSdkVersion field to reflect the sdk api level that has
   1085      * just been selected.
   1086      */
   1087     private void onSdkTargetModified() {
   1088         if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
   1089             return;
   1090         }
   1091 
   1092         IAndroidTarget target = mInfo.getSdkTarget();
   1093 
   1094         if (target != null) {
   1095             mInternalMinSdkVersionUpdate = true;
   1096             mMinSdkVersionField.setText(target.getVersion().getApiString());
   1097             mInternalMinSdkVersionUpdate = false;
   1098         }
   1099 
   1100         mSdkTargetModifiedByUser = true;
   1101     }
   1102 
   1103     /**
   1104      * Returns whether this page's controls currently all contain valid values.
   1105      *
   1106      * @return <code>true</code> if all controls are valid, and
   1107      *         <code>false</code> if at least one is invalid
   1108      */
   1109     private boolean validatePage() {
   1110         IWorkspace workspace = ResourcesPlugin.getWorkspace();
   1111 
   1112         int status = MSG_NONE;
   1113 
   1114         // there is nothing to validate if we're not going to create a test project
   1115         if (mInfo.getCreateTestProject()) {
   1116             status = validateProjectField(workspace);
   1117             if ((status & MSG_ERROR) == 0) {
   1118                 status |= validateLocationPath(workspace);
   1119             }
   1120             if ((status & MSG_ERROR) == 0) {
   1121                 status |= validateTestTarget();
   1122             }
   1123             if ((status & MSG_ERROR) == 0) {
   1124                 status |= validateSdkTarget();
   1125             }
   1126             if ((status & MSG_ERROR) == 0) {
   1127                 status |= validatePackageField();
   1128             }
   1129             if ((status & MSG_ERROR) == 0) {
   1130                 status |= validateMinSdkVersionField();
   1131             }
   1132         }
   1133         if (status == MSG_NONE)  {
   1134             setStatus(null, MSG_NONE);
   1135         }
   1136 
   1137         // Return false if there's an error so that the finish button be disabled.
   1138         return (status & MSG_ERROR) == 0;
   1139     }
   1140 
   1141     /**
   1142      * Validates the page and updates the Next/Finish buttons
   1143      */
   1144     private void validatePageComplete() {
   1145         setPageComplete(validatePage());
   1146     }
   1147 
   1148     /**
   1149      * Validates the test target (self, main project or existing project)
   1150      *
   1151      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1152      */
   1153     private int validateTestTarget() {
   1154         if (mInfo.isTestingExisting() && mInfo.getExistingTestedProject() == null) {
   1155             return setStatus("Please select an existing Android project as a test target.",
   1156                     MSG_ERROR);
   1157         }
   1158 
   1159         return MSG_NONE;
   1160     }
   1161 
   1162     /**
   1163      * Validates the project name field.
   1164      *
   1165      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1166      */
   1167     private int validateProjectField(IWorkspace workspace) {
   1168         // Validate project field
   1169         String projectName = mInfo.getProjectName();
   1170         if (projectName.length() == 0) {
   1171             return setStatus("Project name must be specified", MSG_ERROR);
   1172         }
   1173 
   1174         // Limit the project name to shell-agnostic characters since it will be used to
   1175         // generate the final package
   1176         if (!sProjectNamePattern.matcher(projectName).matches()) {
   1177             return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
   1178                     MSG_ERROR);
   1179         }
   1180 
   1181         IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
   1182         if (!nameStatus.isOK()) {
   1183             return setStatus(nameStatus.getMessage(), MSG_ERROR);
   1184         }
   1185 
   1186         if (mMainInfo != null && projectName.equals(mMainInfo.getProjectName())) {
   1187             return setStatus("The main project name and the test project name must be different.",
   1188                     MSG_ERROR);
   1189         }
   1190 
   1191         if (getProjectHandle().exists()) {
   1192             return setStatus("A project with that name already exists in the workspace",
   1193                     MSG_ERROR);
   1194         }
   1195 
   1196         return MSG_NONE;
   1197     }
   1198 
   1199     /**
   1200      * Validates the location path field.
   1201      *
   1202      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1203      */
   1204     private int validateLocationPath(IWorkspace workspace) {
   1205         Path path = new Path(getProjectLocation());
   1206         if (!mInfo.useDefaultLocation()) {
   1207             // If not using the default value validate the location.
   1208             URI uri = URIUtil.toURI(path.toOSString());
   1209             IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
   1210                     uri);
   1211             if (!locationStatus.isOK()) {
   1212                 return setStatus(locationStatus.getMessage(), MSG_ERROR);
   1213             } else {
   1214                 // The location is valid as far as Eclipse is concerned (i.e. mostly not
   1215                 // an existing workspace project.) Check it either doesn't exist or is
   1216                 // a directory that is empty.
   1217                 File f = path.toFile();
   1218                 if (f.exists() && !f.isDirectory()) {
   1219                     return setStatus("A directory name must be specified.", MSG_ERROR);
   1220                 } else if (f.isDirectory()) {
   1221                     // However if the directory exists, we should put a warning if it is not
   1222                     // empty. We don't put an error (we'll ask the user again for confirmation
   1223                     // before using the directory.)
   1224                     String[] l = f.list();
   1225                     if (l.length != 0) {
   1226                         return setStatus("The selected output directory is not empty.",
   1227                                 MSG_WARNING);
   1228                     }
   1229                 }
   1230             }
   1231         } else {
   1232             // Otherwise validate the path string is not empty
   1233             if (getProjectLocation().length() == 0) {
   1234                 return setStatus("A directory name must be specified.", MSG_ERROR);
   1235             }
   1236 
   1237             File dest = path.append(mInfo.getProjectName()).toFile();
   1238             if (dest.exists()) {
   1239                 return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
   1240                         mInfo.getProjectName()), MSG_ERROR);
   1241             }
   1242         }
   1243 
   1244         return MSG_NONE;
   1245     }
   1246 
   1247     /**
   1248      * Validates the sdk target choice.
   1249      *
   1250      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1251      */
   1252     private int validateSdkTarget() {
   1253         if (mInfo.getSdkTarget() == null) {
   1254             return setStatus("An SDK Target must be specified.", MSG_ERROR);
   1255         }
   1256         return MSG_NONE;
   1257     }
   1258 
   1259     /**
   1260      * Validates the sdk target choice.
   1261      *
   1262      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1263      */
   1264     private int validateMinSdkVersionField() {
   1265 
   1266         // If the min sdk version is empty, it is always accepted.
   1267         if (mInfo.getMinSdkVersion().length() == 0) {
   1268             return MSG_NONE;
   1269         }
   1270 
   1271         if (mInfo.getSdkTarget() != null &&
   1272                 mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) {
   1273             return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
   1274                     mInfo.getSdkTarget().getVersion().isPreview() ? MSG_ERROR : MSG_WARNING);
   1275         }
   1276 
   1277         return MSG_NONE;
   1278     }
   1279 
   1280     /**
   1281      * Validates the package name field.
   1282      *
   1283      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
   1284      */
   1285     private int validatePackageField() {
   1286         // Validate package field
   1287         String packageName = mInfo.getPackageName();
   1288         if (packageName.length() == 0) {
   1289             return setStatus("Project package name must be specified.", MSG_ERROR);
   1290         }
   1291 
   1292         // Check it's a valid package string
   1293         int result = MSG_NONE;
   1294         IStatus status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
   1295         if (!status.isOK()) {
   1296             result = setStatus(String.format("Project package: %s", status.getMessage()),
   1297                         status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
   1298         }
   1299 
   1300         // The Android Activity Manager does not accept packages names with only one
   1301         // identifier. Check the package name has at least one dot in them (the previous rule
   1302         // validated that if such a dot exist, it's not the first nor last characters of the
   1303         // string.)
   1304         if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
   1305             return setStatus("Project package name must have at least two identifiers.", MSG_ERROR);
   1306         }
   1307 
   1308         // Check that the target package name is valid too
   1309         packageName = mInfo.getTargetPackageName();
   1310         if (packageName.length() == 0) {
   1311             return setStatus("Target package name must be specified.", MSG_ERROR);
   1312         }
   1313 
   1314         // Check it's a valid package string
   1315         status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
   1316         if (!status.isOK()) {
   1317             result = setStatus(String.format("Target package: %s", status.getMessage()),
   1318                         status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
   1319         }
   1320 
   1321         if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
   1322             return setStatus("Target name must have at least two identifiers.", MSG_ERROR);
   1323         }
   1324 
   1325         return result;
   1326     }
   1327 
   1328     /**
   1329      * Sets the error message for the wizard with the given message icon.
   1330      *
   1331      * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING.
   1332      * @return As a convenience, always returns messageType so that the caller can return
   1333      *         immediately.
   1334      */
   1335     private int setStatus(String message, int messageType) {
   1336         if (message == null) {
   1337             setErrorMessage(null);
   1338             setMessage(null);
   1339         } else if (!message.equals(getMessage())) {
   1340             setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR);
   1341         }
   1342         return messageType;
   1343     }
   1344 
   1345 }
   1346