true if all controls are valid, and
* false if at least one is invalid
*/
- protected boolean validatePage() {
+ private boolean validatePage() {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
int status = validateProjectField(workspace);
@@ -1027,11 +1097,18 @@ public class NewProjectCreationPage extends WizardPage {
if (status == MSG_NONE) {
setStatus(null, MSG_NONE);
}
-
+
// Return false if there's an error so that the finish button be disabled.
return (status & MSG_ERROR) == 0;
}
+ /**
+ * Validates the page and updates the Next/Finish buttons
+ */
+ private void validatePageComplete() {
+ setPageComplete(validatePage());
+ }
+
/**
* Validates the project name field.
*
@@ -1039,19 +1116,19 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validateProjectField(IWorkspace workspace) {
// Validate project field
- String projectFieldContents = getProjectName();
- if (projectFieldContents.length() == 0) {
+ String projectName = mInfo.getProjectName();
+ if (projectName.length() == 0) {
return setStatus("Project name must be specified", MSG_ERROR);
}
// Limit the project name to shell-agnostic characters since it will be used to
// generate the final package
- if (!sProjectNamePattern.matcher(projectFieldContents).matches()) {
+ if (!sProjectNamePattern.matcher(projectName).matches()) {
return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
MSG_ERROR);
}
- IStatus nameStatus = workspace.validateName(projectFieldContents, IResource.PROJECT);
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
if (!nameStatus.isOK()) {
return setStatus(nameStatus.getMessage(), MSG_ERROR);
}
@@ -1061,6 +1138,13 @@ public class NewProjectCreationPage extends WizardPage {
MSG_ERROR);
}
+ if (mTestInfo != null &&
+ mTestInfo.getCreateTestProject() &&
+ projectName.equals(mTestInfo.getProjectName())) {
+ return setStatus("The main project name and the test project name must be different.",
+ MSG_WARNING);
+ }
+
return MSG_NONE;
}
@@ -1071,8 +1155,8 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validateLocationPath(IWorkspace workspace) {
Path path = new Path(getProjectLocation());
- if (isNewProject()) {
- if (!useDefaultLocation()) {
+ if (mInfo.isNewProject()) {
+ if (!mInfo.useDefaultLocation()) {
// If not using the default value validate the location.
URI uri = URIUtil.toURI(path.toOSString());
IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
@@ -1103,10 +1187,10 @@ public class NewProjectCreationPage extends WizardPage {
return setStatus("A directory name must be specified.", MSG_ERROR);
}
- File dest = path.append(getProjectName()).toFile();
+ File dest = path.append(mInfo.getProjectName()).toFile();
if (dest.exists()) {
return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
- getProjectName()), MSG_ERROR);
+ mInfo.getProjectName()), MSG_ERROR);
}
}
} else {
@@ -1115,7 +1199,7 @@ public class NewProjectCreationPage extends WizardPage {
if (!f.isDirectory()) {
return setStatus("An existing directory name must be specified.", MSG_ERROR);
}
-
+
// Check there's an android manifest in the directory
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
File manifestFile = new File(osPath);
@@ -1144,7 +1228,7 @@ public class NewProjectCreationPage extends WizardPage {
Activity[] activities = manifestData.getActivities();
if (activities == null || activities.length == 0) {
// This is acceptable now as long as no activity needs to be created
- if (isCreateActivity()) {
+ if (mInfo.isCreateActivity()) {
return setStatus(
String.format("No activity name defined in %1$s.", osPath),
MSG_ERROR);
@@ -1163,11 +1247,11 @@ public class NewProjectCreationPage extends WizardPage {
/**
* Validates the sdk target choice.
- *
+ *
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateSdkTarget() {
- if (getSdkTarget() == null) {
+ if (mInfo.getSdkTarget() == null) {
return setStatus("An SDK Target must be specified.", MSG_ERROR);
}
return MSG_NONE;
@@ -1175,29 +1259,29 @@ public class NewProjectCreationPage extends WizardPage {
/**
* Validates the sdk target choice.
- *
+ *
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateMinSdkVersionField() {
// If the min sdk version is empty, it is always accepted.
- if (getMinSdkVersion().length() == 0) {
+ if (mInfo.getMinSdkVersion().length() == 0) {
return MSG_NONE;
}
int version = AndroidManifestParser.INVALID_MIN_SDK;
try {
// If not empty, it must be a valid integer > 0
- version = Integer.parseInt(getMinSdkVersion());
+ version = Integer.parseInt(mInfo.getMinSdkVersion());
} catch (NumberFormatException e) {
// ignore
}
-
+
if (version < 1) {
return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR);
}
-
- if (getSdkTarget() != null && getSdkTarget().getApiVersionNumber() != version) {
+
+ if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) {
return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
MSG_WARNING);
}
@@ -1212,36 +1296,36 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validateActivityField() {
// Disregard if not creating an activity
- if (!isCreateActivity()) {
+ if (!mInfo.isCreateActivity()) {
return MSG_NONE;
}
// Validate activity field
- String activityFieldContents = getActivityName();
+ String activityFieldContents = mInfo.getActivityName();
if (activityFieldContents.length() == 0) {
return setStatus("Activity name must be specified.", MSG_ERROR);
}
// The activity field can actually contain part of a sub-package name
// or it can start with a dot "." to indicates it comes from the parent package name.
- String packageName = "";
+ String packageName = ""; //$NON-NLS-1$
int pos = activityFieldContents.lastIndexOf('.');
if (pos >= 0) {
packageName = activityFieldContents.substring(0, pos);
if (packageName.startsWith(".")) { //$NON-NLS-1$
packageName = packageName.substring(1);
}
-
+
activityFieldContents = activityFieldContents.substring(pos + 1);
}
-
+
// the activity field can contain a simple java identifier, or a
// package name or one that starts with a dot. So if it starts with a dot,
// ignore this dot -- the rest must look like a package name.
if (activityFieldContents.charAt(0) == '.') {
activityFieldContents = activityFieldContents.substring(1);
}
-
+
// Check it's a valid activity string
int result = MSG_NONE;
IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents,
@@ -1272,7 +1356,7 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validatePackageField() {
// Validate package field
- String packageFieldContents = getPackageName();
+ String packageFieldContents = mInfo.getPackageName();
if (packageFieldContents.length() == 0) {
return setStatus("Package name must be specified.", MSG_ERROR);
}
@@ -1312,16 +1396,16 @@ public class NewProjectCreationPage extends WizardPage {
private int validateSourceFolder() {
// This check does nothing when creating a new project.
// This check is also useless when no activity is present or created.
- if (isNewProject() || !isCreateActivity()) {
+ if (mInfo.isNewProject() || !mInfo.isCreateActivity()) {
return MSG_NONE;
}
- String osTarget = getActivityName();
-
+ String osTarget = mInfo.getActivityName();
+
if (osTarget.indexOf('.') == -1) {
- osTarget = getPackageName() + File.separator + osTarget;
+ osTarget = mInfo.getPackageName() + File.separator + osTarget;
} else if (osTarget.indexOf('.') == 0) {
- osTarget = getPackageName() + osTarget;
+ osTarget = mInfo.getPackageName() + osTarget;
}
osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
index d3eb9d403..afc67ed9e 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
@@ -20,6 +20,8 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.AndroidNature;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo;
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
@@ -39,6 +41,8 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -71,27 +75,54 @@ import java.util.Map.Entry;
* Note: this class is public so that it can be accessed from unit tests.
* It is however an internal class. Its API may change without notice.
* It should semantically be considered as a private final class.
- * Do not derive from this class.
+ * Do not derive from this class.
*/
public class NewProjectWizard extends Wizard implements INewWizard {
- private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
- private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
- private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
- private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
- private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
- private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
- private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
- private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
- private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
- private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
- private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
+ /**
+ * Indicates which pages should be available in the New Project Wizard.
+ */
+ protected enum AvailablePages {
+ /**
+ * Both the usual "Android Project" and the "Android Test Project" pages will
+ * be available. The first page displayed will be the former one and it can depend
+ * on the soon-to-be created normal project.
+ */
+ ANDROID_AND_TEST_PROJECT,
+ /**
+ * Only the "Android Test Project" page will be available. User will have to
+ * select an existing Android Project. If the selection matches such a project,
+ * it will be used as a default.
+ */
+ TEST_PROJECT_ONLY
+ }
- private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
- private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
- private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
- private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
+ private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
+ private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
+ private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
+ private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
+ private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
+ private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
+ private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
+ private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
+ private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
+ // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the
+ // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE.
+ private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_SELF = "TARGET_SELF"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_MAIN = "TARGET_MAIN"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING"; //$NON-NLS-1$
+ private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT"; //$NON-NLS-1$
+
+ private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
+ private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
+ private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
+ private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
+ private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY"; //$NON-NLS-1$
+ private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION"; //$NON-NLS-1$
private static final String BIN_DIRECTORY =
SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP;
@@ -117,6 +148,12 @@ public class NewProjectWizard extends Wizard implements INewWizard {
+ "uses-sdk.template"; //$NON-NLS-1$
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
+ "launcher_intent_filter.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY
+ + "test_uses-library.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY
+ + "test_instrumentation.template"; //$NON-NLS-1$
+
+
private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ "strings.template"; //$NON-NLS-1$
@@ -124,11 +161,11 @@ public class NewProjectWizard extends Wizard implements INewWizard {
+ "string.template"; //$NON-NLS-1$
private static final String ICON = "icon.png"; //$NON-NLS-1$
- private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
+ private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
- private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$
- private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
- private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
+ private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$
+ private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
+ private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
private static final String[] DEFAULT_DIRECTORIES = new String[] {
BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
@@ -136,15 +173,23 @@ public class NewProjectWizard extends Wizard implements INewWizard {
DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY};
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
- private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
- private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
- private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
-
- protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
+ private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
+ private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
+ private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
private NewProjectCreationPage mMainPage;
+ private NewTestProjectCreationPage mTestPage;
/** Package name available when the wizard completes. */
private String mPackageName;
+ private final AvailablePages mAvailablePages;
+
+ public NewProjectWizard() {
+ this(AvailablePages.ANDROID_AND_TEST_PROJECT);
+ }
+
+ protected NewProjectWizard(AvailablePages availablePages) {
+ mAvailablePages = availablePages;
+ }
/**
* Initializes this creation wizard using the passed workbench and object
@@ -155,13 +200,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
setWindowTitle("New Android Project");
setImageDescriptor();
- mMainPage = createMainPage();
- mMainPage.setTitle("New Android Project");
- mMainPage.setDescription("Creates a new Android Project resource.");
+ if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
+ mMainPage = createMainPage();
+ }
+ mTestPage = createTestPage();
}
-
+
/**
- * Creates the wizard page.
+ * Creates the main wizard page.
*
* Please do NOT override this method.
*
@@ -170,7 +216,20 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* to maintain compatibility between different versions of the plugin.
*/
protected NewProjectCreationPage createMainPage() {
- return new NewProjectCreationPage(MAIN_PAGE_NAME);
+ return new NewProjectCreationPage();
+ }
+
+ /**
+ * Creates the test wizard page.
+ *
+ * Please do NOT override this method.
+ *
+ * This is protected so that it can be overridden by unit tests.
+ * However the contract of this class is private and NO ATTEMPT will be made
+ * to maintain compatibility between different versions of the plugin.
+ */
+ protected NewTestProjectCreationPage createTestPage() {
+ return new NewTestProjectCreationPage();
}
// -- Methods inherited from org.eclipse.jface.wizard.Wizard --
@@ -182,7 +241,15 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*/
@Override
public void addPages() {
- addPage(mMainPage);
+ if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
+ addPage(mMainPage);
+ }
+ addPage(mTestPage);
+
+ if (mMainPage != null && mTestPage != null) {
+ mTestPage.setMainInfo(mMainPage.getMainInfo());
+ mMainPage.setTestInfo(mTestPage.getTestInfo());
+ }
}
/**
@@ -195,7 +262,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*/
@Override
public boolean performFinish() {
- if (!createAndroidProject()) {
+ if (!createAndroidProjects()) {
return false;
}
@@ -206,21 +273,21 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
// -- Public Fields --
-
- /** Returns the package name. Only valid once the wizard finishes. */
+
+ /** Returns the main project package name. Only valid once the wizard finishes. */
public String getPackageName() {
return mPackageName;
}
-
+
// -- Custom Methods --
/**
* Before actually creating the project for a new project (as opposed to using an
* existing project), we check if the target location is a directory that either does
* not exist or is empty.
- *
+ *
* If it's not empty, ask the user for confirmation.
- *
+ *
* @param destination The destination folder where the new project is to be created.
* @return True if the destination doesn't exist yet or is an empty directory or is
* accepted by the user.
@@ -234,33 +301,108 @@ public class NewProjectWizard extends Wizard implements INewWizard {
return true;
}
+ /**
+ * Structure that describes all the information needed to create a project.
+ * This is collected from the pages by {@link NewProjectWizard#createAndroidProjects()}
+ * and then used by
+ * {@link NewProjectWizard#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}.
+ */
+ private static class ProjectInfo {
+ private final IProject mProject;
+ private final IProjectDescription mDescription;
+ private final Map
+ * This method does not create the project resource; this is the
+ * responsibility of IProject::create invoked by the new
+ * project resource wizard.
+ *
true if all controls are valid, and
+ * false if at least one is invalid
+ */
+ private boolean validatePage() {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+
+ int status = MSG_NONE;
+
+ // there is nothing to validate if we're not going to create a test project
+ if (mInfo.getCreateTestProject()) {
+ status = validateProjectField(workspace);
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateLocationPath(workspace);
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateTestTarget();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateSdkTarget();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validatePackageField();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateMinSdkVersionField();
+ }
+ }
+ if (status == MSG_NONE) {
+ setStatus(null, MSG_NONE);
+ }
+
+ // Return false if there's an error so that the finish button be disabled.
+ return (status & MSG_ERROR) == 0;
+ }
+
+ /**
+ * Validates the page and updates the Next/Finish buttons
+ */
+ private void validatePageComplete() {
+ setPageComplete(validatePage());
+ }
+
+ /**
+ * Validates the test target (self, main project or existing project)
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateTestTarget() {
+ if (mInfo.isTestingExisting() && mInfo.getExistingTestedProject() == null) {
+ return setStatus("Please select an existing Android project as a test target.",
+ MSG_ERROR);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the project name field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateProjectField(IWorkspace workspace) {
+ // Validate project field
+ String projectName = mInfo.getProjectName();
+ if (projectName.length() == 0) {
+ return setStatus("Project name must be specified", MSG_ERROR);
+ }
+
+ // Limit the project name to shell-agnostic characters since it will be used to
+ // generate the final package
+ if (!sProjectNamePattern.matcher(projectName).matches()) {
+ return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
+ MSG_ERROR);
+ }
+
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
+ if (!nameStatus.isOK()) {
+ return setStatus(nameStatus.getMessage(), MSG_ERROR);
+ }
+
+ if (mMainInfo != null && projectName.equals(mMainInfo.getProjectName())) {
+ return setStatus("The main project name and the test project name must be different.",
+ MSG_ERROR);
+ }
+
+ if (getProjectHandle().exists()) {
+ return setStatus("A project with that name already exists in the workspace",
+ MSG_ERROR);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the location path field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateLocationPath(IWorkspace workspace) {
+ Path path = new Path(getProjectLocation());
+ if (!mInfo.useDefaultLocation()) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
+ uri);
+ if (!locationStatus.isOK()) {
+ return setStatus(locationStatus.getMessage(), MSG_ERROR);
+ } else {
+ // The location is valid as far as Eclipse is concerned (i.e. mostly not
+ // an existing workspace project.) Check it either doesn't exist or is
+ // a directory that is empty.
+ File f = path.toFile();
+ if (f.exists() && !f.isDirectory()) {
+ return setStatus("A directory name must be specified.", MSG_ERROR);
+ } else if (f.isDirectory()) {
+ // However if the directory exists, we should put a warning if it is not
+ // empty. We don't put an error (we'll ask the user again for confirmation
+ // before using the directory.)
+ String[] l = f.list();
+ if (l.length != 0) {
+ return setStatus("The selected output directory is not empty.",
+ MSG_WARNING);
+ }
+ }
+ }
+ } else {
+ // Otherwise validate the path string is not empty
+ if (getProjectLocation().length() == 0) {
+ return setStatus("A directory name must be specified.", MSG_ERROR);
+ }
+
+ File dest = path.append(mInfo.getProjectName()).toFile();
+ if (dest.exists()) {
+ return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
+ mInfo.getProjectName()), MSG_ERROR);
+ }
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the sdk target choice.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateSdkTarget() {
+ if (mInfo.getSdkTarget() == null) {
+ return setStatus("An SDK Target must be specified.", MSG_ERROR);
+ }
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the sdk target choice.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateMinSdkVersionField() {
+
+ // If the min sdk version is empty, it is always accepted.
+ if (mInfo.getMinSdkVersion().length() == 0) {
+ return MSG_NONE;
+ }
+
+ int version = AndroidManifestParser.INVALID_MIN_SDK;
+ try {
+ // If not empty, it must be a valid integer > 0
+ version = Integer.parseInt(mInfo.getMinSdkVersion());
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+
+ if (version < 1) {
+ return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR);
+ }
+
+ if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) {
+ return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
+ MSG_WARNING);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the package name field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validatePackageField() {
+ // Validate package field
+ String packageName = mInfo.getPackageName();
+ if (packageName.length() == 0) {
+ return setStatus("Project package name must be specified.", MSG_ERROR);
+ }
+
+ // Check it's a valid package string
+ int result = MSG_NONE;
+ IStatus status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
+ if (!status.isOK()) {
+ result = setStatus(String.format("Project package: %s", status.getMessage()),
+ status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
+ }
+
+ // The Android Activity Manager does not accept packages names with only one
+ // identifier. Check the package name has at least one dot in them (the previous rule
+ // validated that if such a dot exist, it's not the first nor last characters of the
+ // string.)
+ if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
+ return setStatus("Project package name must have at least two identifiers.", MSG_ERROR);
+ }
+
+ // Check that the target package name is valid too
+ packageName = mInfo.getTargetPackageName();
+ if (packageName.length() == 0) {
+ return setStatus("Target package name must be specified.", MSG_ERROR);
+ }
+
+ // Check it's a valid package string
+ status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
+ if (!status.isOK()) {
+ result = setStatus(String.format("Target package: %s", status.getMessage()),
+ status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
+ }
+
+ if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
+ return setStatus("Target name must have at least two identifiers.", MSG_ERROR);
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets the error message for the wizard with the given message icon.
+ *
+ * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING.
+ * @return As a convenience, always returns messageType so that the caller can return
+ * immediately.
+ */
+ private int setStatus(String message, int messageType) {
+ if (message == null) {
+ setErrorMessage(null);
+ setMessage(null);
+ } else if (!message.equals(getMessage())) {
+ setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR);
+ }
+ return messageType;
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java
new file mode 100755
index 000000000..dd5cf7684
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.wizards.newproject;
+
+
+/**
+ * A "New Test Android Project" Wizard.
+ *
+ * This is really the {@link NewProjectWizard} that only displays the "test project" page.
+ */
+public class NewTestProjectWizard extends NewProjectWizard {
+
+ public NewTestProjectWizard() {
+ super(AvailablePages.TEST_PROJECT_ONLY);
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template b/tools/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
index b43e75f2b..5d5741356 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
@@ -5,6 +5,8 @@
android:versionName="1.0">