aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2009-05-07 19:27:46 -0700
committerRaphael <raphael@google.com>2009-05-08 14:06:44 -0700
commit7b9b3d1cc71be8d8d748f46951fbde64446cde31 (patch)
tree5ac83b88cec8f5c367bef59ad9f9e917c8ae269b /eclipse/plugins/com.android.ide.eclipse.adt
parentd80cb19594014ea765eb50419764fd2d029ffb2b (diff)
downloadsdk-7b9b3d1cc71be8d8d748f46951fbde64446cde31.zip
sdk-7b9b3d1cc71be8d8d748f46951fbde64446cde31.tar.gz
sdk-7b9b3d1cc71be8d8d748f46951fbde64446cde31.tar.bz2
ADT #1715616: "New Android Test Project" wizard.
Also updates eclipse/changes.txt
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml44
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java6
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java34
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java57
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java472
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java564
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java1349
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java30
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template1
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template1
15 files changed, 2210 insertions, 416 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index dc6062d..1d0a18d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -90,7 +90,7 @@
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
- icon="icons/android.png"
+ icon="icons/new_adt_project.png"
id="com.android.ide.eclipse.adt.project.NewProjectWizard"
name="Android Project"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
@@ -98,10 +98,22 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
- class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
+ class="com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
- icon="icons/android.png"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.project.NewTestProjectWizard"
+ name="Android Test Project"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="true">
+ </wizard>
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_xml.png"
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
name="Android XML File"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
@@ -220,11 +232,22 @@
value="com.android.ide.eclipse.adt.AndroidNature">
</filter>
<action
- class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
+ class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
enablesFor="1"
- id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
+ icon="icons/new_xml.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
label="New Resource File..."
- menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1">
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"
+ tooltip="Opens a wizard to help create a new Android XML Resource file">
+ </action>
+ <action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ enablesFor="1"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ label="New Test Project..."
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"
+ tooltip="Opens a wizard to help create a new Android Test Project">
</action>
<action
class="com.android.ide.eclipse.adt.project.ExportAction"
@@ -484,6 +507,15 @@
tooltip="Opens a wizard to help create a new Android XML file">
</action>
<action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ label="New Android Test Project"
+ style="push"
+ toolbarPath="android_project"
+ tooltip="Opens a wizard to help create a new Android Test Project">
+ </action>
+ <action
class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
icon="icons/new_adt_project.png"
id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
index e091b13..08890a2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
@@ -71,6 +71,22 @@ public final class ProjectHelper {
}
/**
+ * Adds the corresponding source folder to the project's class path entries.
+ *
+ * @param javaProject The java project of which path entries to update.
+ * @param new_entry The parent source folder to remove.
+ * @throws JavaModelException
+ */
+ public static void addEntryToClasspath(
+ IJavaProject javaProject, IClasspathEntry new_entry)
+ throws JavaModelException {
+
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ entries = addEntryToClasspath(entries, new_entry);
+ javaProject.setRawClasspath(entries, new NullProgressMonitor());
+ }
+
+ /**
* Remove a classpath entry from the array.
* @param entries The class path entries to read. A copy will be returned
* @param index The index to remove.
@@ -265,6 +281,8 @@ public final class ProjectHelper {
// If needed, check and fix compiler compliance and source compatibility
ProjectHelper.checkAndFixCompilerCompliance(javaProject);
}
+
+
/**
* Checks the project compiler compliance level is supported.
* @param javaProject The project to check
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
index 7303b02..0a365e6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
@@ -200,7 +200,8 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
label.setText("Configuration:");
mConfigSelector = new ConfigurationSelector(group);
- GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
+ GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
+ gd.horizontalSpan = 2;
gd.widthHint = ConfigurationSelector.WIDTH_HINT;
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
mConfigSelector.setLayoutData(gd);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
index e0d0d5e..49766d1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
@@ -4,7 +4,7 @@
* 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
@@ -23,7 +23,9 @@ import org.eclipse.ui.IWorkbenchWizard;
/**
* Delegate for the toolbar action "Android Project".
- * It displays the Android New Project wizard.
+ * It displays the Android New Project wizard to create a new Android Project (not a test project).
+ *
+ * @see NewTestProjectAction
*/
public class NewProjectAction extends OpenWizardAction {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java
new file mode 100755
index 0000000..9cdf098
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.actions;
+
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IWorkbenchWizard;
+
+/**
+ * Delegate for the toolbar action "Android Test Project".
+ * It displays the Android New Project wizard to create a new Test Project.
+ */
+public class NewTestProjectAction extends OpenWizardAction {
+
+ @Override
+ protected IWorkbenchWizard instanciateWizard(IAction action) {
+ return new NewTestProjectWizard();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
index d1530d4..3ae66df 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
@@ -22,7 +22,9 @@ import org.eclipse.jface.action.IAction;
import org.eclipse.ui.IWorkbenchWizard;
/**
- * Delegate for the toolbar action "Android Project".
+ * Delegate for the toolbar action "Android Project" or for the
+ * project > Android Project context menu.
+ *
* It displays the Android New XML file wizard.
*/
public class NewXmlFileAction extends OpenWizardAction {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java
deleted file mode 100644
index 20cfc82..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2008 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.actions;
-
-import com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard;
-
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.wizard.WizardDialog;
-import org.eclipse.ui.IObjectActionDelegate;
-import org.eclipse.ui.IWorkbench;
-import org.eclipse.ui.IWorkbenchPart;
-
-public class NewXmlFileWizardAction implements IObjectActionDelegate {
-
- private ISelection mSelection;
- private IWorkbench mWorkbench;
-
- /**
- * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
- */
- public void setActivePart(IAction action, IWorkbenchPart targetPart) {
- mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
- }
-
- public void run(IAction action) {
- if (mSelection instanceof IStructuredSelection) {
- IStructuredSelection selection = (IStructuredSelection)mSelection;
-
- // call the new xml file wizard on the current selection.
- NewXmlFileWizard wizard = new NewXmlFileWizard();
- wizard.init(mWorkbench, selection);
- WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(),
- wizard);
- dialog.open();
- }
- }
-
- public void selectionChanged(IAction action, ISelection selection) {
- this.mSelection = selection;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
index 3f0393d..c905d6a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
@@ -4,7 +4,7 @@
* 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
@@ -28,6 +28,7 @@ import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
@@ -43,7 +44,8 @@ import org.eclipse.ui.internal.util.Util;
* An abstract action that displays one of our wizards.
* Derived classes must provide the actual wizard to display.
*/
-/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate {
+/*package*/ abstract class OpenWizardAction
+ implements IWorkbenchWindowActionDelegate, IObjectActionDelegate {
/**
* The wizard dialog width, extracted from {@link NewWizardShortcutAction}
@@ -60,6 +62,9 @@ import org.eclipse.ui.internal.util.Util;
/** The result from the dialog */
private int mDialogResult;
+ private ISelection mSelection;
+ private IWorkbench mWorkbench;
+
/** Returns the wizard that was created by {@link #run(IAction)}. */
public IWorkbenchWizard getWizard() {
return mWizard;
@@ -70,7 +75,7 @@ import org.eclipse.ui.internal.util.Util;
public int getDialogResult() {
return mDialogResult;
}
-
+
/* (non-Javadoc)
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
*/
@@ -89,19 +94,23 @@ import org.eclipse.ui.internal.util.Util;
* Opens and display the Android New Project Wizard.
* <p/>
* Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}.
- *
+ *
* @param action The action that got us here. Can be null when used internally.
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run(IAction action) {
// get the workbench and the current window
- IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbench workbench = mWorkbench != null ? mWorkbench : PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
-
+
// This code from NewWizardShortcutAction#run() gets the current window selection
// and converts it to a workbench structured selection for the wizard, if possible.
- ISelection selection = window.getSelectionService().getSelection();
+ ISelection selection = mSelection;
+ if (selection == null) {
+ selection = window.getSelectionService().getSelection();
+ }
+
IStructuredSelection selectionToPass = StructuredSelection.EMPTY;
if (selection instanceof IStructuredSelection) {
selectionToPass = (IStructuredSelection) selection;
@@ -123,16 +132,16 @@ import org.eclipse.ui.internal.util.Util;
// Create the wizard and initialize it with the selection
mWizard = instanciateWizard(action);
mWizard.init(workbench, selectionToPass);
-
+
// It's not visible yet until a dialog is created and opened
Shell parent = window.getShell();
WizardDialogEx dialog = new WizardDialogEx(parent, mWizard);
dialog.create();
-
+
if (mWizard instanceof IUpdateWizardDialog) {
((IUpdateWizardDialog) mWizard).updateWizardDialog(dialog);
}
-
+
// This code comes straight from NewWizardShortcutAction#run()
Point defaultSize = dialog.getShell().getSize();
dialog.getShell().setSize(
@@ -140,13 +149,13 @@ import org.eclipse.ui.internal.util.Util;
Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y));
window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(),
IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT);
-
+
mDialogResult = dialog.open();
}
/**
* Called by {@link #run(IAction)} to instantiate the actual wizard.
- *
+ *
* @param action The action parameter from {@link #run(IAction)}.
* This can be null.
* @return A new wizard instance. Must not be null.
@@ -157,7 +166,13 @@ import org.eclipse.ui.internal.util.Util;
* @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
*/
public void selectionChanged(IAction action, ISelection selection) {
- // pass
+ mSelection = selection;
}
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IObjectActionDelegate#setActivePart(org.eclipse.jface.action.IAction, org.eclipse.ui.IWorkbenchPart)
+ */
+ public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+ mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
index 20aa68b..e666390 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
@@ -25,6 +25,7 @@ package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
@@ -79,11 +80,13 @@ import java.util.regex.Pattern;
* 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 NewProjectCreationPage extends WizardPage {
// constants
+ private static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
+
/** Initial value for all name fields (project, activity, application, package). Used
* whenever a value is requested before controls are created. */
private static final String INITIAL_NAME = ""; //$NON-NLS-1$
@@ -94,7 +97,7 @@ public class NewProjectCreationPage extends WizardPage {
private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
/** Initial value for the Create Activity check box. */
private static final boolean INITIAL_CREATE_ACTIVITY = true;
-
+
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose. It cannot start with a space. */
@@ -106,7 +109,13 @@ public class NewProjectCreationPage extends WizardPage {
private final int MSG_NONE = 0;
private final int MSG_WARNING = 1;
private final int MSG_ERROR = 2;
-
+
+ /** Structure with the externally visible information from this Main Project page. */
+ private final MainInfo mInfo = new MainInfo();
+ /** Structure with the externally visible information from the Test Project page.
+ * This is null if there's no such page, meaning the main project page is standalone. */
+ private TestInfo mTestInfo;
+
private String mUserPackageName = ""; //$NON-NLS-1$
private String mUserActivityName = ""; //$NON-NLS-1$
private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY;
@@ -128,98 +137,160 @@ public class NewProjectCreationPage extends WizardPage {
private ITargetChangeListener mSdkTargetChangeListener;
private boolean mInternalLocationPathUpdate;
- protected boolean mInternalProjectNameUpdate;
- protected boolean mInternalApplicationNameUpdate;
+ private boolean mInternalProjectNameUpdate;
+ private boolean mInternalApplicationNameUpdate;
private boolean mInternalCreateActivityUpdate;
private boolean mInternalActivityNameUpdate;
- protected boolean mProjectNameModifiedByUser;
- protected boolean mApplicationNameModifiedByUser;
+ private boolean mProjectNameModifiedByUser;
+ private boolean mApplicationNameModifiedByUser;
private boolean mInternalMinSdkVersionUpdate;
- private boolean mMinSdkVersionModifiedByUser;
/**
* Creates a new project creation wizard page.
- *
- * @param pageName the name of this page
*/
- public NewProjectCreationPage(String pageName) {
- super(pageName);
+ public NewProjectCreationPage() {
+ super(MAIN_PAGE_NAME);
setPageComplete(false);
+ setTitle("New Android Project");
+ setDescription("Creates a new Android Project resource.");
}
// --- Getters used by NewProjectWizard ---
+
/**
- * Returns the current project location path as entered by the user, or its
- * anticipated initial value. Note that if the default has been returned the
- * path in a project description used to create a project should not be set.
- *
- * @return the project location path or its anticipated initial value.
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ * <p/>
+ * This interface is provided so that the adt-test counterpart can override the returned
+ * information.
*/
- public IPath getLocationPath() {
- return new Path(getProjectLocation());
+ public interface IMainInfo {
+ public IPath getLocationPath();
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName();
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName();
+ /** Returns the value of the activity name field with spaces trimmed. */
+ public String getActivityName();
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion();
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName();
+ /** Returns the value of the "Create New Project" radio. */
+ public boolean isNewProject();
+ /** Returns the value of the "Create Activity" checkbox. */
+ public boolean isCreateActivity();
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation();
+ /** Returns the internal source folder (for the "existing project" mode) or the default
+ * "src" constant. */
+ public String getSourceFolder();
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget();
}
- /** Returns the value of the project name field with leading and trailing spaces removed. */
- public String getProjectName() {
- return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
- }
- /** Returns the value of the package name field with spaces trimmed. */
- public String getPackageName() {
- return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
- }
+ /**
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public class MainInfo implements IMainInfo {
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ public IPath getLocationPath() {
+ return new Path(getProjectLocation());
+ }
- /** Returns the value of the activity name field with spaces trimmed. */
- public String getActivityName() {
- return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim();
- }
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName() {
+ return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
+ }
- /** Returns the value of the min sdk version field with spaces trimmed. */
- public String getMinSdkVersion() {
- return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim();
- }
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName() {
+ return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
+ }
- /** Returns the value of the application name field with spaces trimmed. */
- public String getApplicationName() {
- // Return the name of the activity as default application name.
- return mApplicationNameField == null ? getActivityName()
- : mApplicationNameField.getText().trim();
+ /** Returns the value of the activity name field with spaces trimmed. */
+ public String getActivityName() {
+ return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim();
+ }
- }
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion() {
+ return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$
+ }
- /** Returns the value of the "Create New Project" radio. */
- public boolean isNewProject() {
- return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT
- : mCreateNewProjectRadio.getSelection();
- }
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName() {
+ // Return the name of the activity as default application name.
+ return mApplicationNameField == null ? getActivityName()
+ : mApplicationNameField.getText().trim();
- /** Returns the value of the "Create Activity" checkbox. */
- public boolean isCreateActivity() {
- return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
- : mCreateActivityCheck.getSelection();
- }
+ }
- /** Returns the value of the Use Default Location field. */
- public boolean useDefaultLocation() {
- return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
- : mUseDefaultLocation.getSelection();
- }
+ /** Returns the value of the "Create New Project" radio. */
+ public boolean isNewProject() {
+ return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT
+ : mCreateNewProjectRadio.getSelection();
+ }
- /** Returns the internal source folder (for the "existing project" mode) or the default
- * "src" constant. */
- public String getSourceFolder() {
- if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) {
- return SdkConstants.FD_SOURCES;
- } else {
- return mSourceFolder;
+ /** Returns the value of the "Create Activity" checkbox. */
+ public boolean isCreateActivity() {
+ return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
+ : mCreateActivityCheck.getSelection();
+ }
+
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation() {
+ return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
+ : mUseDefaultLocation.getSelection();
}
+
+ /** Returns the internal source folder (for the "existing project" mode) or the default
+ * "src" constant. */
+ public String getSourceFolder() {
+ if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) {
+ return SdkConstants.FD_SOURCES;
+ } else {
+ return mSourceFolder;
+ }
+ }
+
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget() {
+ return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+ }
+ }
+
+ /**
+ * Returns a {@link MainInfo} structure that collects all externally visible information
+ * from this page, to be used by the calling wizard or by other pages.
+ */
+ public IMainInfo getMainInfo() {
+ return mInfo;
}
-
- /** Returns the current sdk target or null if none has been selected yet. */
- public IAndroidTarget getSdkTarget() {
- return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+
+ /**
+ * Grabs the {@link TestInfo} structure that collects externally visible fields from the
+ * test project page. This may be null.
+ */
+ public void setTestInfo(TestInfo testInfo) {
+ mTestInfo = testInfo;
}
/**
@@ -231,6 +302,7 @@ public class NewProjectCreationPage extends WizardPage {
super.setVisible(visible);
if (visible) {
mProjectNameField.setFocus();
+ validatePageComplete();
}
}
@@ -265,17 +337,17 @@ public class NewProjectCreationPage extends WizardPage {
setControl(composite);
// Validate. This will complain about the first empty field.
- setPageComplete(validatePage());
+ validatePageComplete();
}
-
+
@Override
public void dispose() {
-
+
if (mSdkTargetChangeListener != null) {
AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
mSdkTargetChangeListener = null;
}
-
+
super.dispose();
}
@@ -328,7 +400,7 @@ public class NewProjectCreationPage extends WizardPage {
Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
// Layout has 4 columns of non-equal size
group.setLayout(new GridLayout());
- group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
group.setFont(parent.getFont());
group.setText("Contents");
@@ -349,7 +421,7 @@ public class NewProjectCreationPage extends WizardPage {
super.widgetSelected(e);
enableLocationWidgets();
extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
+ validatePageComplete();
}
};
@@ -358,9 +430,9 @@ public class NewProjectCreationPage extends WizardPage {
mUseDefaultLocation.addSelectionListener(location_listener);
Composite location_group = new Composite(group, SWT.NONE);
- location_group.setLayout(new GridLayout(4, /* num columns */
+ location_group.setLayout(new GridLayout(3, /* num columns */
false /* columns of not equal size */));
- location_group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ location_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
location_group.setFont(parent.getFont());
mLocationLabel = new Label(location_group, SWT.NONE);
@@ -371,7 +443,7 @@ public class NewProjectCreationPage extends WizardPage {
GridData.BEGINNING, /* vertical alignment */
true, /* grabExcessHorizontalSpace */
false, /* grabExcessVerticalSpace */
- 2, /* horizontalSpan */
+ 1, /* horizontalSpan */
1); /* verticalSpan */
mLocationPathField.setLayoutData(data);
mLocationPathField.setFont(parent.getFont());
@@ -387,7 +459,7 @@ public class NewProjectCreationPage extends WizardPage {
mBrowseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- openDirectoryBrowser();
+ onOpenDirectoryBrowser();
}
});
}
@@ -403,7 +475,7 @@ public class NewProjectCreationPage extends WizardPage {
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
group.setText("Build Target");
-
+
// The selector is created without targets. They are added below in the change listener.
mSdkTargetSelector = new SdkTargetSelector(group, null);
@@ -428,52 +500,23 @@ public class NewProjectCreationPage extends WizardPage {
}
}
};
-
+
AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
-
+
// Invoke it once to initialize the targets
mSdkTargetChangeListener.onTargetsLoaded();
-
+
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSdkTargetModified();
updateLocationPathField(null);
- setPageComplete(validatePage());
+ validatePageComplete();
}
});
}
/**
- * Display a directory browser and update the location path field with the selected path
- */
- private void openDirectoryBrowser() {
-
- String existing_dir = getLocationPathFieldValue();
-
- // Disable the path if it doesn't exist
- if (existing_dir.length() == 0) {
- existing_dir = null;
- } else {
- File f = new File(existing_dir);
- if (!f.exists()) {
- existing_dir = null;
- }
- }
-
- DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
- dd.setMessage("Browse for folder");
- dd.setFilterPath(existing_dir);
- String abs_dir = dd.open();
-
- if (abs_dir != null) {
- updateLocationPathField(abs_dir);
- extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
- }
- }
-
- /**
* Creates the group for the project properties:
* - Package name [text field]
* - Activity name [text field]
@@ -508,7 +551,7 @@ public class NewProjectCreationPage extends WizardPage {
if (!mInternalApplicationNameUpdate) {
mApplicationNameModifiedByUser = true;
}
- }
+ }
});
// new package label
@@ -569,7 +612,7 @@ public class NewProjectCreationPage extends WizardPage {
mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
public void handleEvent(Event event) {
onMinSdkVersionFieldModified();
- setPageComplete(validatePage());
+ validatePageComplete();
}
});
}
@@ -579,12 +622,12 @@ public class NewProjectCreationPage extends WizardPage {
/** Returns the location path field value with spaces trimmed. */
private String getLocationPathFieldValue() {
- return mLocationPathField == null ? "" : mLocationPathField.getText().trim();
+ return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
}
/** Returns the current project location, depending on the Use Default Location check box. */
- public String getProjectLocation() {
- if (isNewProject() && useDefaultLocation()) {
+ private String getProjectLocation() {
+ if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
return Platform.getLocation().toString();
} else {
return getLocationPathFieldValue();
@@ -603,22 +646,51 @@ public class NewProjectCreationPage extends WizardPage {
* @return the new project resource handle
*/
private IProject getProjectHandle() {
- return ResourcesPlugin.getWorkspace().getRoot().getProject(getProjectName());
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
}
// --- UI Callbacks ----
/**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existing_dir = getLocationPathFieldValue();
+
+ // Disable the path if it doesn't exist
+ if (existing_dir.length() == 0) {
+ existing_dir = null;
+ } else {
+ File f = new File(existing_dir);
+ if (!f.exists()) {
+ existing_dir = null;
+ }
+ }
+
+ DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
+ dd.setMessage("Browse for folder");
+ dd.setFilterPath(existing_dir);
+ String abs_dir = dd.open();
+
+ if (abs_dir != null) {
+ updateLocationPathField(abs_dir);
+ extractNamesFromAndroidManifest();
+ validatePageComplete();
+ }
+ }
+
+ /**
* Enables or disable the location widgets depending on the user selection:
* the location path is enabled when using the "existing source" mode (i.e. not new project)
* or in new project mode with the "use default location" turned off.
*/
private void enableLocationWidgets() {
- boolean is_new_project = isNewProject();
- boolean use_default = useDefaultLocation();
+ boolean is_new_project = mInfo.isNewProject();
+ boolean use_default = mInfo.useDefaultLocation();
boolean location_enabled = !is_new_project || !use_default;
- boolean create_activity = isCreateActivity();
-
+ boolean create_activity = mInfo.isCreateActivity();
+
mUseDefaultLocation.setEnabled(is_new_project);
mLocationLabel.setEnabled(location_enabled);
@@ -646,8 +718,8 @@ public class NewProjectCreationPage extends WizardPage {
* @param abs_dir A new absolute directory path or null to use the default.
*/
private void updateLocationPathField(String abs_dir) {
- boolean is_new_project = isNewProject();
- boolean use_default = useDefaultLocation();
+ boolean is_new_project = mInfo.isNewProject();
+ boolean use_default = mInfo.useDefaultLocation();
boolean custom_location = !is_new_project || !use_default;
if (!mInternalLocationPathUpdate) {
@@ -663,7 +735,7 @@ public class NewProjectCreationPage extends WizardPage {
} else if (sAutoComputeCustomLocation ||
(!is_new_project && !new File(sCustomLocationOsPath).isDirectory())) {
// By default select the samples directory of the current target
- IAndroidTarget target = getSdkTarget();
+ IAndroidTarget target = mInfo.getSdkTarget();
if (target != null) {
sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES);
}
@@ -682,13 +754,13 @@ public class NewProjectCreationPage extends WizardPage {
mLocationPathField.setText(sCustomLocationOsPath);
}
} else {
- String value = Platform.getLocation().append(getProjectName()).toString();
+ String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
value = TextProcessor.process(value);
if (!mLocationPathField.getText().equals(value)) {
mLocationPathField.setText(value);
}
}
- setPageComplete(validatePage());
+ validatePageComplete();
mInternalLocationPathUpdate = false;
}
}
@@ -711,7 +783,7 @@ public class NewProjectCreationPage extends WizardPage {
newPath.equals(sCustomLocationOsPath);
sCustomLocationOsPath = newPath;
extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
+ validatePageComplete();
}
}
@@ -723,9 +795,9 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onPackageNameFieldModified() {
- if (isNewProject()) {
- mUserPackageName = getPackageName();
- setPageComplete(validatePage());
+ if (mInfo.isNewProject()) {
+ mUserPackageName = mInfo.getPackageName();
+ validatePageComplete();
}
}
@@ -737,10 +809,10 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onCreateActivityCheckModified() {
- if (isNewProject() && !mInternalCreateActivityUpdate) {
- mUserCreateActivityCheck = isCreateActivity();
+ if (mInfo.isNewProject() && !mInternalCreateActivityUpdate) {
+ mUserCreateActivityCheck = mInfo.isCreateActivity();
}
- setPageComplete(validatePage());
+ validatePageComplete();
}
/**
@@ -751,15 +823,15 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onActivityNameFieldModified() {
- if (isNewProject() && !mInternalActivityNameUpdate) {
- mUserActivityName = getActivityName();
- setPageComplete(validatePage());
+ if (mInfo.isNewProject() && !mInternalActivityNameUpdate) {
+ mUserActivityName = mInfo.getActivityName();
+ validatePageComplete();
}
}
/**
* Called when the min sdk version field has been modified.
- *
+ *
* Ignore the internal modifications. When modified by the user, try to match
* a target with the same API level.
*/
@@ -769,16 +841,16 @@ public class NewProjectCreationPage extends WizardPage {
}
try {
- int version = Integer.parseInt(getMinSdkVersion());
-
+ int version = Integer.parseInt(mInfo.getMinSdkVersion());
+
// Before changing, compare with the currently selected one, if any.
// There can be multiple targets with the same sdk api version, so don't change
// it if it's already at the right version.
- IAndroidTarget curr_target = getSdkTarget();
+ IAndroidTarget curr_target = mInfo.getSdkTarget();
if (curr_target != null && curr_target.getApiVersionNumber() == version) {
return;
}
-
+
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getApiVersionNumber() == version) {
mSdkTargetSelector.setSelection(target);
@@ -788,20 +860,18 @@ public class NewProjectCreationPage extends WizardPage {
} catch (NumberFormatException e) {
// ignore
}
-
- mMinSdkVersionModifiedByUser = true;
}
-
+
/**
* Called when an SDK target is modified.
- *
- * If the minSdkVersion field hasn't been modified by the user yet, we change it
- * to reflect the sdk api level that has just been selected.
+ *
+ * Also changes the minSdkVersion field to reflect the sdk api level that has
+ * just been selected.
*/
private void onSdkTargetModified() {
- IAndroidTarget target = getSdkTarget();
-
- if (target != null && !mMinSdkVersionModifiedByUser) {
+ IAndroidTarget target = mInfo.getSdkTarget();
+
+ if (target != null) {
mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber()));
mInternalMinSdkVersionUpdate = false;
@@ -814,7 +884,7 @@ public class NewProjectCreationPage extends WizardPage {
* entered before.
*/
private void updatePackageAndActivityFields() {
- if (isNewProject()) {
+ if (mInfo.isNewProject()) {
if (mUserPackageName.length() > 0 &&
!mPackageNameField.getText().equals(mUserPackageName)) {
mPackageNameField.setText(mUserPackageName);
@@ -826,7 +896,7 @@ public class NewProjectCreationPage extends WizardPage {
mActivityNameField.setText(mUserActivityName);
mInternalActivityNameUpdate = false;
}
-
+
if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) {
mInternalCreateActivityUpdate = true;
mCreateActivityCheck.setSelection(mUserCreateActivityCheck);
@@ -841,7 +911,7 @@ public class NewProjectCreationPage extends WizardPage {
* can actually be found in the custom user directory.
*/
private void extractNamesFromAndroidManifest() {
- if (isNewProject()) {
+ if (mInfo.isNewProject()) {
return;
}
@@ -853,12 +923,12 @@ public class NewProjectCreationPage extends WizardPage {
Path path = new Path(f.getPath());
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
-
+
AndroidManifestParser manifestData = AndroidManifestParser.parseForData(osPath);
if (manifestData == null) {
return;
}
-
+
String packageName = null;
Activity activity = null;
String activityName = null;
@@ -882,7 +952,7 @@ public class NewProjectCreationPage extends WizardPage {
if (packageName != null && packageName.length() > 0) {
mPackageNameField.setText(packageName);
}
-
+
if (activity != null) {
activityName = AndroidManifestParser.extractActivityName(activity.getName(),
packageName);
@@ -922,7 +992,7 @@ public class NewProjectCreationPage extends WizardPage {
mCreateActivityCheck.setSelection(false);
mInternalCreateActivityUpdate = false;
mInternalActivityNameUpdate = false;
-
+
// There is no activity name to use to fill in the project and application
// name. However if there's a package name, we can use this as a base.
if (packageName != null && packageName.length() > 0) {
@@ -944,13 +1014,13 @@ public class NewProjectCreationPage extends WizardPage {
mProjectNameField.setText(packageName);
mInternalProjectNameUpdate = false;
}
-
+
}
}
// Select the target matching the manifest's sdk or build properties, if any
boolean foundTarget = false;
-
+
ProjectProperties p = ProjectProperties.create(projectLocation, null);
if (p != null) {
// Check the {build|default}.properties files if present
@@ -976,7 +1046,7 @@ public class NewProjectCreationPage extends WizardPage {
// ignore
}
}
-
+
if (!foundTarget) {
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (projectLocation.startsWith(target.getLocation())) {
@@ -990,8 +1060,8 @@ public class NewProjectCreationPage extends WizardPage {
if (!foundTarget) {
mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText(
- minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
- Integer.toString(minSdkVersion)); //$NON-NLS-1$
+ minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" //$NON-NLS-1$
+ : Integer.toString(minSdkVersion));
mInternalMinSdkVersionUpdate = false;
}
}
@@ -1002,7 +1072,7 @@ public class NewProjectCreationPage extends WizardPage {
* @return <code>true</code> if all controls are valid, and
* <code>false</code> if at least one is invalid
*/
- protected boolean validatePage() {
+ private boolean validatePage() {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
int status = validateProjectField(workspace);
@@ -1027,31 +1097,38 @@ 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.
*
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
index d3eb9d4..afc67ed 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
+++ b/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$
-
- 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$
+ /**
+ * 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 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.
* <p/>
* Please do NOT override this method.
* <p/>
@@ -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.
+ * <p/>
+ * Please do NOT override this method.
+ * <p/>
+ * 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.
@@ -235,32 +302,107 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
+ * 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<String, Object> mParameters;
+ private final HashMap<String, String> mDictionary;
+
+ public ProjectInfo(IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ HashMap<String, String> dictionary) {
+ mProject = project;
+ mDescription = description;
+ mParameters = parameters;
+ mDictionary = dictionary;
+ }
+
+ public IProject getProject() {
+ return mProject;
+ }
+
+ public IProjectDescription getDescription() {
+ return mDescription;
+ }
+
+ public Map<String, Object> getParameters() {
+ return mParameters;
+ }
+
+ public HashMap<String, String> getDictionary() {
+ return mDictionary;
+ }
+ }
+
+ /**
* Creates the android project.
* @return True if the project could be created.
*/
- private boolean createAndroidProject() {
+ private boolean createAndroidProjects() {
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ if (mMainPage != null && mainData == null) {
+ return false;
+ }
+
+ final ProjectInfo testData = collectTestPageInfo();
+ if (mTestPage != null && testData == null) {
+ return false;
+ }
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, mainData, testData);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Collects all the parameters needed to create the main project.
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no main page.
+ */
+ private ProjectInfo collectMainPageInfo() {
+ if (mMainPage == null) {
+ return null;
+ }
+
+ IMainInfo info = mMainPage.getMainInfo();
+
IWorkspace workspace = ResourcesPlugin.getWorkspace();
- final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
+ final IProject project = workspace.getRoot().getProject(info.getProjectName());
final IProjectDescription description = workspace.newProjectDescription(project.getName());
-
// keep some variables to make them available once the wizard closes
- mPackageName = mMainPage.getPackageName();
+ mPackageName = info.getPackageName();
final Map<String, Object> parameters = new HashMap<String, Object>();
- parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
+ parameters.put(PARAM_PROJECT, info.getProjectName());
parameters.put(PARAM_PACKAGE, mPackageName);
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
- parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject());
- parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
- parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
- parameters.put(PARAM_MIN_SDK_VERSION, mMainPage.getMinSdkVersion());
-
- if (mMainPage.isCreateActivity()) {
+ parameters.put(PARAM_IS_NEW_PROJECT, info.isNewProject());
+ parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
+ parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
+ parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
+
+ if (info.isCreateActivity()) {
// An activity name can be of the form ".package.Class" or ".Class".
// The initial dot is ignored, as it is always added later in the templates.
- String activityName = mMainPage.getActivityName();
+ String activityName = info.getActivityName();
if (activityName.startsWith(".")) { //$NON-NLS-1$
activityName = activityName.substring(1);
}
@@ -269,31 +411,81 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// create a dictionary of string that will contain name+content.
// we'll put all the strings into values/strings.xml
- final HashMap<String, String> stringDictionary = new HashMap<String, String>();
- stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName());
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, info.getApplicationName());
- IPath path = mMainPage.getLocationPath();
+ IPath path = info.getLocationPath();
IPath defaultLocation = Platform.getLocation();
if (!path.equals(defaultLocation)) {
description.setLocation(path);
}
-
- if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() &&
+
+ if (info.isNewProject() && !info.useDefaultLocation() &&
!validateNewProjectLocationIsEmpty(path)) {
- return false;
+ return null;
}
- // Create a monitored operation to create the actual project
- WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
- @Override
- protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
- createProjectAsync(project, description, monitor, parameters, stringDictionary);
- }
- };
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
- // Run the operation in a different thread
- runAsyncOperation(op);
- return true;
+ /**
+ * Collects all the parameters needed to create the test project.
+ *
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no test page.
+ */
+ private ProjectInfo collectTestPageInfo() {
+ if (mTestPage == null) {
+ return null;
+ }
+
+ TestInfo info = mTestPage.getTestInfo();
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProject project = workspace.getRoot().getProject(info.getProjectName());
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, info.getProjectName());
+ parameters.put(PARAM_PACKAGE, info.getPackageName());
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, true);
+ parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
+ parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
+ parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
+
+ // Test-specific parameters
+ parameters.put(PARAM_TEST_TARGET_PACKAGE, info.getTargetPackageName());
+
+ if (info.isTestingSelf()) {
+ parameters.put(PARAM_TARGET_SELF, true);
+ }
+ if (info.isTestingMain()) {
+ parameters.put(PARAM_TARGET_MAIN, true);
+ }
+ if (info.isTestingExisting()) {
+ parameters.put(PARAM_TARGET_EXISTING, true);
+ parameters.put(PARAM_REFERENCE_PROJECT, info.getExistingTestedProject());
+ }
+
+ // create a dictionary of string that will contain name+content.
+ // we'll put all the strings into values/strings.xml
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, info.getApplicationName());
+
+ IPath path = info.getLocationPath();
+ IPath defaultLocation = Platform.getLocation();
+ if (!path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (!info.useDefaultLocation() && !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
}
/**
@@ -328,82 +520,46 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
- * Creates the actual project, sets its nature and adds the required folders
- * and files to it. This is run asynchronously in a different thread.
+ * Creates the actual project(s). This is run asynchronously in a different thread.
*
- * @param project The project to create.
- * @param description A description of the project.
* @param monitor An existing monitor.
- * @param parameters Template parameters.
- * @param stringDictionary String definition.
+ * @param mainData Data for main project. Can be null.
* @throws InvocationTargetException to wrap any unmanaged exception and
* return it to the calling thread. The method can fail if it fails
* to create or modify the project or if it is canceled by the user.
*/
- private void createProjectAsync(IProject project, IProjectDescription description,
- IProgressMonitor monitor, Map<String, Object> parameters,
- Map<String, String> stringDictionary)
- throws InvocationTargetException {
+ private void createProjectAsync(IProgressMonitor monitor,
+ ProjectInfo mainData,
+ ProjectInfo testData)
+ throws InvocationTargetException {
monitor.beginTask("Create Android Project", 100);
try {
- // Create project and open it
- project.create(description, new SubProgressMonitor(monitor, 10));
- if (monitor.isCanceled()) throw new OperationCanceledException();
- project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
-
- // Add the Java and android nature to the project
- AndroidNature.setupProjectNatures(project, monitor);
-
- // Create folders in the project if they don't already exist
- addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
- String[] sourceFolders = new String[] {
- (String) parameters.get(PARAM_SRC_FOLDER),
- GEN_SRC_DIRECTORY
- };
- addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
-
- // Create the resource folders in the project if they don't already exist.
- addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
-
- // Setup class path: mark folders as source folders
- IJavaProject javaProject = JavaCore.create(project);
- for (String sourceFolder : sourceFolders) {
- setupSourceFolder(javaProject, sourceFolder, monitor);
- }
-
- // Mark the gen source folder as derived
- IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
- if (genSrcFolder.exists()) {
- genSrcFolder.setDerived(true);
+ IProject mainProject = null;
+
+ if (mainData != null) {
+ mainProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ mainData.getProject(),
+ mainData.getDescription(),
+ mainData.getParameters(),
+ mainData.getDictionary());
}
- if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
- // Create files in the project if they don't already exist
- addManifest(project, parameters, stringDictionary, monitor);
-
- // add the default app icon
- addIcon(project, monitor);
-
- // Create the default package components
- addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor);
+ if (testData != null) {
- // add the string definition file if needed
- if (stringDictionary.size() > 0) {
- addStringDictionaryFile(project, stringDictionary, monitor);
+ Map<String, Object> parameters = testData.getParameters();
+ if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
+ parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
}
- // Set output location
- javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
- monitor);
+ createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ testData.getProject(),
+ testData.getDescription(),
+ parameters,
+ testData.getDictionary());
}
- Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
- null /* apkConfigMap*/);
-
- // Fix the project to make sure all properties are as expected.
- // Necessary for existing projects and good for new ones to.
- ProjectHelper.fixProject(project);
-
} catch (CoreException e) {
throw new InvocationTargetException(e);
} catch (IOException e) {
@@ -414,6 +570,111 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
+ * Creates the actual project, sets its nature and adds the required folders
+ * and files to it. This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @param project The project to create.
+ * @param description A description of the project.
+ * @param parameters Template parameters.
+ * @param dictionary String definition.
+ * @return The project newly created
+ */
+ private IProject createEclipseProject(IProgressMonitor monitor,
+ IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ Map<String, String> dictionary)
+ throws CoreException, IOException {
+
+ // Create project and open it
+ project.create(description, new SubProgressMonitor(monitor, 10));
+ if (monitor.isCanceled()) throw new OperationCanceledException();
+
+ project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
+
+ // Add the Java and android nature to the project
+ AndroidNature.setupProjectNatures(project, monitor);
+
+ // Create folders in the project if they don't already exist
+ addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
+ String[] sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
+
+ // Create the resource folders in the project if they don't already exist.
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
+
+ // Setup class path: mark folders as source folders
+ IJavaProject javaProject = JavaCore.create(project);
+ for (String sourceFolder : sourceFolders) {
+ setupSourceFolder(javaProject, sourceFolder, monitor);
+ }
+
+ // Mark the gen source folder as derived
+ IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
+ if (genSrcFolder.exists()) {
+ genSrcFolder.setDerived(true);
+ }
+
+ if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
+ // Create files in the project if they don't already exist
+ addManifest(project, parameters, dictionary, monitor);
+
+ // add the default app icon
+ addIcon(project, monitor);
+
+ // Create the default package components
+ addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
+
+ // add the string definition file if needed
+ if (dictionary.size() > 0) {
+ addStringDictionaryFile(project, dictionary, monitor);
+ }
+
+ // Set output location
+ javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
+ monitor);
+ }
+
+ // Create the reference to the target project
+ if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) {
+ IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT);
+ if (refProject != null) {
+ IProjectDescription desc = project.getDescription();
+
+ // Add out reference to the existing project reference.
+ // We just created a project with no references so we don't need to expand
+ // the currently-empty current list.
+ desc.setReferencedProjects(new IProject[] { refProject });
+
+ project.setDescription(desc, IResource.KEEP_HISTORY, new SubProgressMonitor(monitor, 10));
+
+ IClasspathEntry entry = JavaCore.newProjectEntry(
+ refProject.getFullPath(), //path
+ new IAccessRule[0], //accessRules
+ false, //combineAccessRules
+ new IClasspathAttribute[0], //extraAttributes
+ false //isExported
+
+ );
+ ProjectHelper.addEntryToClasspath(javaProject, entry);
+ }
+ }
+
+ Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
+ null /* apkConfigMap*/);
+
+ // Fix the project to make sure all properties are as expected.
+ // Necessary for existing projects and good for new ones to.
+ ProjectHelper.fixProject(project);
+
+ return project;
+ }
+
+ /**
* Adds default directories to the project.
*
* @param project The Java Project to update.
@@ -442,7 +703,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
- * @param stringDictionary String List to be added to a string definition
+ * @param dictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
@@ -450,7 +711,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addManifest(IProject project, Map<String, Object> parameters,
- Map<String, String> stringDictionary, IProgressMonitor monitor)
+ Map<String, String> dictionary, IProgressMonitor monitor)
throws CoreException, IOException {
// get IFile to the manifest and check if it's not already there.
@@ -466,23 +727,42 @@ public class NewProjectWizard extends Wizard implements INewWizard {
if (parameters.containsKey(PARAM_ACTIVITY)) {
// now get the activity template
String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
-
+
// Replace all keyword parameters to make main activity.
String activities = replaceParameters(activityTemplate, parameters);
-
+
// set the intent.
String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
-
+
// set the intent to the main activity
activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
-
+
// set the activity(ies) in the manifest
manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
} else {
// remove the activity(ies) from the manifest
- manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, "");
+ manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); //$NON-NLS-1$
+ }
+
+ // Handle the case of the test projects
+ if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) {
+ // Set the uses-library needed by the test project
+ String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY);
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, usesLibrary);
+
+ // Set the instrumentation element needed by the test project
+ String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, instru);
+
+ // Replace PARAM_TEST_TARGET_PACKAGE itself now
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ } else {
+ // remove the unused entries
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, ""); //$NON-NLS-1$
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, ""); //$NON-NLS-1$
}
-
+
String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
if (minSdkVersion != null && minSdkVersion.length() > 0) {
String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
@@ -584,7 +864,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
- * @param stringDictionary String List to be added to a string definition
+ * @param dictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
@@ -592,12 +872,12 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addSampleCode(IProject project, String sourceFolder,
- Map<String, Object> parameters, Map<String, String> stringDictionary,
+ Map<String, Object> parameters, Map<String, String> dictionary,
IProgressMonitor monitor) throws CoreException, IOException {
// create the java package directories.
IFolder pkgFolder = project.getFolder(sourceFolder);
String packageName = (String) parameters.get(PARAM_PACKAGE);
-
+
// The PARAM_ACTIVITY key will be absent if no activity should be created,
// in which case activityName will be null.
String activityName = (String) parameters.get(PARAM_ACTIVITY);
@@ -610,7 +890,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
int pos = packageName.lastIndexOf('.');
activityName = packageName.substring(pos + 1);
packageName = packageName.substring(0, pos);
-
+
// Also update the values used in the JAVA_FILE_TEMPLATE below
// (but not the ones from the manifest so don't change the caller's dictionary)
java_activity_parameters = new HashMap<String, Object>(parameters);
@@ -643,9 +923,9 @@ public class NewProjectWizard extends Wizard implements INewWizard {
if (!file.exists()) {
copyFile(LAYOUT_TEMPLATE, file, parameters, monitor);
if (activityName != null) {
- stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
+ dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
} else {
- stringDictionary.put(STRING_HELLO_WORLD, "Hello World!");
+ dictionary.put(STRING_HELLO_WORLD, "Hello World!");
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java
new file mode 100755
index 0000000..7b99053
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/*
+ * References:
+ * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
+ * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
+ */
+
+package com.android.ide.eclipse.adt.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.MainInfo;
+import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.ProjectChooserHelper;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdkuilib.SdkTargetSelector;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaConventions;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * NewAndroidProjectCreationPage is a project creation page that provides the
+ * following fields:
+ * <ul>
+ * <li> Project name
+ * <li> SDK Target
+ * <li> Application name
+ * <li> Package name
+ * <li> Activity name
+ * </ul>
+ * 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.
+ */
+public class NewTestProjectCreationPage extends WizardPage {
+
+ // constants
+ static final String TEST_PAGE_NAME = "newAndroidTestProjectPage"; //$NON-NLS-1$
+
+ /** Initial value for all name fields (project, activity, application, package). Used
+ * whenever a value is requested before controls are created. */
+ private static final String INITIAL_NAME = ""; //$NON-NLS-1$
+ /** Initial value for the Use Default Location check box. */
+ private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
+ /** Initial value for the Create Test Project check box. */
+ private static final boolean INITIAL_CREATE_TEST_PROJECT = false;
+
+
+ /** Pattern for characters accepted in a project name. Since this will be used as a
+ * directory name, we're being a bit conservative on purpose. It cannot start with a space. */
+ private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$
+ /** Last user-browsed location, static so that it be remembered for the whole session */
+ private static String sCustomLocationOsPath = ""; //$NON-NLS-1$
+
+ private final int MSG_NONE = 0;
+ private final int MSG_WARNING = 1;
+ private final int MSG_ERROR = 2;
+
+ /** Structure with the externally visible information from this Test Project page. */
+ private final TestInfo mInfo = new TestInfo();
+ /** Structure with the externally visible information from the Main Project page.
+ * This is null if there's no such page, meaning the test project page is standalone. */
+ private IMainInfo mMainInfo;
+
+ // widgets
+ private Text mProjectNameField;
+ private Text mPackageNameField;
+ private Text mApplicationNameField;
+ private Button mUseDefaultLocation;
+ private Label mLocationLabel;
+ private Text mLocationPathField;
+ private Button mBrowseButton;
+ private Text mMinSdkVersionField;
+ private SdkTargetSelector mSdkTargetSelector;
+ private ITargetChangeListener mSdkTargetChangeListener;
+ private Button mCreateTestProjectField;
+ private Text mTestedProjectNameField;
+ private Button mProjectBrowseButton;
+ private ProjectChooserHelper mProjectChooserHelper;
+ private Button mTestSelfProjectRadio;
+ private Button mTestExistingProjectRadio;
+
+ /** A list of composites that are disabled when the "Create Test Project" toggle is off. */
+ private ArrayList<Composite> mToggleComposites = new ArrayList<Composite>();
+
+ private boolean mInternalProjectNameUpdate;
+ private boolean mInternalLocationPathUpdate;
+ private boolean mInternalPackageNameUpdate;
+ private boolean mInternalApplicationNameUpdate;
+ private boolean mInternalMinSdkVersionUpdate;
+ private boolean mInternalSdkTargetUpdate;
+ private IProject mExistingTestedProject;
+ private boolean mProjectNameModifiedByUser;
+ private boolean mApplicationNameModifiedByUser;
+ private boolean mPackageNameModifiedByUser;
+ private boolean mMinSdkVersionModifiedByUser;
+ private boolean mSdkTargetModifiedByUser;
+
+ private Label mTestTargetPackageLabel;
+
+ private String mLastExistingPackageName;
+
+
+ /**
+ * Creates a new project creation wizard page.
+ */
+ public NewTestProjectCreationPage() {
+ super(TEST_PAGE_NAME);
+ setPageComplete(false);
+ setTitle("New Android Test Project");
+ setDescription("Creates a new Android Test Project resource.");
+ }
+
+ // --- Getters used by NewProjectWizard ---
+
+ /**
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public class TestInfo {
+
+ /** Returns true if a new Test Project should be created. */
+ public boolean getCreateTestProject() {
+ return mCreateTestProjectField == null ? true : mCreateTestProjectField.getSelection();
+ }
+
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ public IPath getLocationPath() {
+ return new Path(getProjectLocation());
+ }
+
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName() {
+ return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
+ }
+
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName() {
+ return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
+ }
+
+ /** Returns the value of the test target package name field with spaces trimmed. */
+ public String getTargetPackageName() {
+ return mTestTargetPackageLabel == null ? INITIAL_NAME
+ : mTestTargetPackageLabel.getText().trim();
+ }
+
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion() {
+ return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName() {
+ // Return the name of the activity as default application name.
+ return mApplicationNameField == null ? "" : mApplicationNameField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation() {
+ return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
+ : mUseDefaultLocation.getSelection();
+ }
+
+ /** Returns the the default "src" constant. */
+ public String getSourceFolder() {
+ return SdkConstants.FD_SOURCES;
+ }
+
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget() {
+ return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+ }
+
+ public boolean isTestingSelf() {
+ return mMainInfo == null &&
+ (mTestSelfProjectRadio == null ? false : mTestSelfProjectRadio.getSelection());
+ }
+
+ public boolean isTestingMain() {
+ return mMainInfo != null;
+ }
+
+ public boolean isTestingExisting() {
+ return mMainInfo == null &&
+ (mTestExistingProjectRadio == null ? false
+ : mTestExistingProjectRadio.getSelection());
+ }
+
+ public IProject getExistingTestedProject() {
+ return mExistingTestedProject;
+ }
+ }
+
+ /**
+ * Returns a {@link TestInfo} structure that collects all externally visible information
+ * from this page. This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public TestInfo getTestInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Grabs the {@link MainInfo} structure with visible parameters from the main project page.
+ * This may be null.
+ */
+ public void setMainInfo(IMainInfo mainInfo) {
+ mMainInfo = mainInfo;
+ }
+
+ // --- UI creation ---
+
+ /**
+ * Creates the top level control for this dialog page under the given parent
+ * composite.
+ *
+ * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
+ */
+ public void createControl(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NULL);
+ composite.setFont(parent.getFont());
+
+ initializeDialogUnits(parent);
+
+ composite.setLayout(new GridLayout());
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ createToggleTestProject(composite);
+ createTestProjectGroup(composite);
+ createLocationGroup(composite);
+ createTestTargetGroup(composite);
+ createTargetGroup(composite);
+ createPropertiesGroup(composite);
+
+ // Update state the first time
+ enableLocationWidgets();
+
+ // Show description the first time
+ setErrorMessage(null);
+ setMessage(null);
+ setControl(composite);
+
+ // Validate. This will complain about the first empty field.
+ validatePageComplete();
+ }
+
+ /**
+ * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
+ * the dialog is made visible and to also update the enabled/disabled state of some
+ * controls (doing so in createControl doesn't always change their state somehow.)
+ */
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (visible) {
+ mProjectNameField.setFocus();
+ validatePageComplete();
+ onCreateTestProjectToggle();
+ onExistingProjectChanged();
+ }
+ }
+
+ @Override
+ public void dispose() {
+
+ if (mSdkTargetChangeListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
+ mSdkTargetChangeListener = null;
+ }
+
+ super.dispose();
+ }
+
+ /**
+ * Creates the "create test project" checkbox but only if there's a main page in the wizard.
+ *
+ * @param parent the parent composite
+ */
+ private final void createToggleTestProject(Composite parent) {
+
+ if (mMainInfo != null) {
+ mCreateTestProjectField = new Button(parent, SWT.CHECK);
+ mCreateTestProjectField.setText("Create a Test Project");
+ mCreateTestProjectField.setToolTipText("Select this if you also want to create a Test Project.");
+ mCreateTestProjectField.setSelection(INITIAL_CREATE_TEST_PROJECT);
+ mCreateTestProjectField.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onCreateTestProjectToggle();
+ }
+ });
+ }
+ }
+
+ /**
+ * Creates the group for the project name:
+ * [label: "Project Name"] [text field]
+ *
+ * @param parent the parent composite
+ */
+ private final void createTestProjectGroup(Composite parent) {
+ Composite group = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ mToggleComposites.add(group);
+
+ // --- test project name ---
+
+ // new project label
+ String tooltip = "Name of the Eclipse test project to create. It cannot be empty.";
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Test Project Name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText(tooltip);
+
+ // new project name entry field
+ mProjectNameField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_HORIZONTAL);
+ mProjectNameField.setToolTipText(tooltip);
+ mProjectNameField.setLayoutData(data);
+ mProjectNameField.setFont(parent.getFont());
+ mProjectNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalProjectNameUpdate) {
+ mProjectNameModifiedByUser = true;
+ }
+ updateLocationPathField(null);
+ }
+ });
+
+ }
+
+ private final void createLocationGroup(Composite parent) {
+
+ // --- project location ---
+
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ group.setLayout(new GridLayout(3, /* num columns */
+ false /* columns of not equal size */));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ group.setFont(parent.getFont());
+ group.setText("Content");
+
+ mToggleComposites.add(group);
+
+ mUseDefaultLocation = new Button(group, SWT.CHECK);
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ mUseDefaultLocation.setLayoutData(gd);
+ mUseDefaultLocation.setText("Use default location");
+ mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION);
+
+ mUseDefaultLocation.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ enableLocationWidgets();
+ validatePageComplete();
+ }
+ });
+
+
+ mLocationLabel = new Label(group, SWT.NONE);
+ mLocationLabel.setText("Location:");
+
+ mLocationPathField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL, /* horizontal alignment */
+ GridData.BEGINNING, /* vertical alignment */
+ true, /* grabExcessHorizontalSpace */
+ false, /* grabExcessVerticalSpace */
+ 1, /* horizontalSpan */
+ 1); /* verticalSpan */
+ mLocationPathField.setLayoutData(data);
+ mLocationPathField.setFont(parent.getFont());
+ mLocationPathField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ onLocationPathFieldModified();
+ }
+ });
+
+ mBrowseButton = new Button(group, SWT.PUSH);
+ mBrowseButton.setText("Browse...");
+ setButtonLayoutData(mBrowseButton);
+ mBrowseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onOpenDirectoryBrowser();
+ }
+ });
+ }
+
+ /**
+ * Creates the group for Test Target options.
+ *
+ * There are two different modes here:
+ * <ul>
+ * <li>When mMainInfo exists, this is part of a new Android Project. In which case
+ * the new test is tied to the soon-to-be main project and there is actually no choice.
+ * <li>When mMainInfo does not exist, this is a standalone new test project. In this case
+ * we offer 2 options for the test target: self test or against an existing Android project.
+ * </ul>
+ *
+ * @param parent the parent composite
+ */
+ private final void createTestTargetGroup(Composite parent) {
+
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 3;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+ group.setText("Test Target");
+
+ mToggleComposites.add(group);
+
+ if (mMainInfo == null) {
+ // Standalone mode: choose between self-test and existing-project test
+
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Select the project to test:");
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ label.setLayoutData(gd);
+
+ mTestSelfProjectRadio = new Button(group, SWT.RADIO);
+ mTestSelfProjectRadio.setText("This project");
+ mTestSelfProjectRadio.setSelection(false);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ mTestSelfProjectRadio.setLayoutData(gd);
+
+ mTestExistingProjectRadio = new Button(group, SWT.RADIO);
+ mTestExistingProjectRadio.setText("An existing Android project");
+ mTestExistingProjectRadio.setSelection(mMainInfo == null);
+ mTestExistingProjectRadio.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onExistingProjectChanged();
+ }
+ });
+
+ String tooltip = "The existing Android Project that is being tested.";
+
+ mTestedProjectNameField = new Text(group, SWT.BORDER);
+ mTestedProjectNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mTestedProjectNameField.setToolTipText(tooltip);
+ mTestedProjectNameField.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ onProjectFieldUpdated();
+ }
+ });
+
+ mProjectBrowseButton = new Button(group, SWT.NONE);
+ mProjectBrowseButton.setText("Browse...");
+ mProjectBrowseButton.setToolTipText("Allows you to select the Android project to test.");
+ mProjectBrowseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onProjectBrowse();
+ }
+ });
+
+ mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
+ } else {
+ // Part of NPW mode: no selection.
+
+ }
+
+ // package label line
+
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Test Target Package:");
+ mTestTargetPackageLabel = new Label(group, SWT.NONE);
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 2;
+ mTestTargetPackageLabel.setLayoutData(gd);
+ }
+
+ /**
+ * Creates the target group.
+ * It only contains an SdkTargetSelector.
+ */
+ private void createTargetGroup(Composite parent) {
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ // Layout has 1 column
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+ group.setText("Build Target");
+
+ mToggleComposites.add(group);
+
+ // The selector is created without targets. They are added below in the change listener.
+ mSdkTargetSelector = new SdkTargetSelector(group, null);
+
+ mSdkTargetChangeListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ public void onTargetsLoaded() {
+ // Update the sdk target selector with the new targets
+
+ // get the targets from the sdk
+ IAndroidTarget[] targets = null;
+ if (Sdk.getCurrent() != null) {
+ targets = Sdk.getCurrent().getTargets();
+ }
+ mSdkTargetSelector.setTargets(targets);
+
+ // If there's only one target, select it
+ if (targets != null && targets.length == 1) {
+ mSdkTargetSelector.setSelection(targets[0]);
+ }
+ }
+ };
+
+ AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
+
+ // Invoke it once to initialize the targets
+ mSdkTargetChangeListener.onTargetsLoaded();
+
+ mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onSdkTargetModified();
+ updateLocationPathField(null);
+ validatePageComplete();
+ }
+ });
+ }
+
+ /**
+ * Creates the group for the project properties:
+ * - Package name [text field]
+ * - Activity name [text field]
+ * - Application name [text field]
+ *
+ * @param parent the parent composite
+ */
+ private final void createPropertiesGroup(Composite parent) {
+ // package specification group
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ group.setFont(parent.getFont());
+ group.setText("Properties");
+
+ mToggleComposites.add(group);
+
+ // new application label
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Application name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("Name of the Application. This is a free string. It can be empty.");
+
+ // new application name entry field
+ mApplicationNameField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_HORIZONTAL);
+ mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty.");
+ mApplicationNameField.setLayoutData(data);
+ mApplicationNameField.setFont(parent.getFont());
+ mApplicationNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalApplicationNameUpdate) {
+ mApplicationNameModifiedByUser = true;
+ }
+ }
+ });
+
+ // new package label
+ label = new Label(group, SWT.NONE);
+ label.setText("Package name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
+
+ // new package name entry field
+ mPackageNameField = new Text(group, SWT.BORDER);
+ data = new GridData(GridData.FILL_HORIZONTAL);
+ mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
+ mPackageNameField.setLayoutData(data);
+ mPackageNameField.setFont(parent.getFont());
+ mPackageNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalPackageNameUpdate) {
+ mPackageNameModifiedByUser = true;
+ }
+ onPackageNameFieldModified();
+ }
+ });
+
+ // min sdk version label
+ label = new Label(group, SWT.NONE);
+ label.setText("Min SDK Version:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
+
+ // min sdk version entry field
+ mMinSdkVersionField = new Text(group, SWT.BORDER);
+ data = new GridData(GridData.FILL_HORIZONTAL);
+ label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
+ mMinSdkVersionField.setLayoutData(data);
+ mMinSdkVersionField.setFont(parent.getFont());
+ mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ onMinSdkVersionFieldModified();
+ validatePageComplete();
+ }
+ });
+ }
+
+
+ //--- Internal getters & setters ------------------
+
+ /** Returns the location path field value with spaces trimmed. */
+ private String getLocationPathFieldValue() {
+ return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the current project location, depending on the Use Default Location check box. */
+ private String getProjectLocation() {
+ if (mInfo.useDefaultLocation()) {
+ return Platform.getLocation().toString();
+ } else {
+ return getLocationPathFieldValue();
+ }
+ }
+
+ /**
+ * Creates a project resource handle for the current project name field
+ * value.
+ * <p>
+ * This method does not create the project resource; this is the
+ * responsibility of <code>IProject::create</code> invoked by the new
+ * project resource wizard.
+ * </p>
+ *
+ * @return the new project resource handle
+ */
+ private IProject getProjectHandle() {
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
+ }
+
+ // --- UI Callbacks ----
+
+ /**
+ * Callback invoked when the user toggles the "Test target: Existing Android Project"
+ * checkbox. It enables or disable the UI to select an existing project.
+ */
+ private void onExistingProjectChanged() {
+ if (mInfo.isTestingExisting()) {
+ boolean enabled = mTestExistingProjectRadio.getSelection();
+ mTestedProjectNameField.setEnabled(enabled);
+ mProjectBrowseButton.setEnabled(enabled);
+ setExistingProject(mInfo.getExistingTestedProject());
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * Tries to load the defaults from the main page if possible.
+ */
+ private void useMainProjectInformation() {
+ if (mInfo.isTestingMain() && mMainInfo != null) {
+
+ String projName = String.format("%1$sTest", mMainInfo.getProjectName());
+ String appName = String.format("%1$sTest", mMainInfo.getApplicationName());
+
+ String packageName = mMainInfo.getPackageName();
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+
+ updateTestTargetPackageField(packageName);
+
+ if (!mProjectNameModifiedByUser) {
+ mInternalProjectNameUpdate = true;
+ mProjectNameField.setText(projName); //$NON-NLS-1$
+ mInternalProjectNameUpdate = false;
+ }
+
+ if (!mApplicationNameModifiedByUser) {
+ mInternalApplicationNameUpdate = true;
+ mApplicationNameField.setText(appName);
+ mInternalApplicationNameUpdate = false;
+ }
+
+ if (!mPackageNameModifiedByUser) {
+ mInternalPackageNameUpdate = true;
+ packageName += ".test"; //$NON-NLS-1$
+ mPackageNameField.setText(packageName);
+ mInternalPackageNameUpdate = false;
+ }
+
+ if (!mSdkTargetModifiedByUser) {
+ mInternalSdkTargetUpdate = true;
+ mSdkTargetSelector.setSelection(mMainInfo.getSdkTarget());
+ mInternalSdkTargetUpdate = false;
+ }
+
+ if (!mMinSdkVersionModifiedByUser) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(mMainInfo.getMinSdkVersion());
+ mInternalMinSdkVersionUpdate = false;
+ }
+ }
+ }
+
+ /**
+ * Callback invoked when the user edits the project text field.
+ */
+ private void onProjectFieldUpdated() {
+ String project = mTestedProjectNameField.getText();
+
+ // Is this a valid project?
+ IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/);
+ for (IJavaProject p : projects) {
+ if (p.getProject().getName().equals(project)) {
+ setExistingProject(p.getProject());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Callback called when the user uses the "Browse Projects" button.
+ */
+ private void onProjectBrowse() {
+ IJavaProject p = mProjectChooserHelper.chooseJavaProject(mTestedProjectNameField.getText());
+ if (p != null) {
+ setExistingProject(p.getProject());
+ mTestedProjectNameField.setText(mExistingTestedProject.getName());
+ }
+ }
+
+ private void setExistingProject(IProject project) {
+ mExistingTestedProject = project;
+
+ // Try to update the application, package, sdk target and minSdkVersion accordingly
+ if (project != null &&
+ (!mApplicationNameModifiedByUser ||
+ !mPackageNameModifiedByUser ||
+ !mSdkTargetModifiedByUser ||
+ !mMinSdkVersionModifiedByUser)) {
+
+ IFile file = AndroidManifestParser.getManifest(project);
+ AndroidManifestParser manifestData = null;
+ if (file != null) {
+ try {
+ manifestData = AndroidManifestParser.parseForData(file);
+ } catch (CoreException e) {
+ // pass
+ }
+ }
+
+ if (manifestData != null) {
+ String appName = String.format("%1$sTest", project.getName());
+ String packageName = manifestData.getPackage();
+ int minSdkVersion = manifestData.getApiLevelRequirement();
+ IAndroidTarget sdkTarget = null;
+ if (Sdk.getCurrent() != null) {
+ sdkTarget = Sdk.getCurrent().getTarget(project);
+ }
+
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+ mLastExistingPackageName = packageName;
+
+ if (!mProjectNameModifiedByUser) {
+ mInternalProjectNameUpdate = true;
+ mProjectNameField.setText(appName);
+ mInternalProjectNameUpdate = false;
+ }
+
+ if (!mApplicationNameModifiedByUser) {
+ mInternalApplicationNameUpdate = true;
+ mApplicationNameField.setText(appName);
+ mInternalApplicationNameUpdate = false;
+ }
+
+ if (!mPackageNameModifiedByUser) {
+ mInternalPackageNameUpdate = true;
+ packageName += ".test"; //$NON-NLS-1$
+ mPackageNameField.setText(packageName); //$NON-NLS-1$
+ mInternalPackageNameUpdate = false;
+ }
+
+ if (!mSdkTargetModifiedByUser && sdkTarget != null) {
+ mInternalSdkTargetUpdate = true;
+ mSdkTargetSelector.setSelection(sdkTarget);
+ mInternalSdkTargetUpdate = false;
+ }
+
+ if (!mMinSdkVersionModifiedByUser) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(
+ minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK ?
+ Integer.toString(minSdkVersion) : ""); //$NON-NLS-1$
+ if (sdkTarget == null) {
+ updateSdkSelectorToMatchMinSdkVersion();
+ }
+ mInternalMinSdkVersionUpdate = false;
+ }
+ }
+ }
+
+ updateTestTargetPackageField(mLastExistingPackageName);
+ validatePageComplete();
+ }
+
+ /**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existing_dir = getLocationPathFieldValue();
+
+ // Disable the path if it doesn't exist
+ if (existing_dir.length() == 0) {
+ existing_dir = null;
+ } else {
+ File f = new File(existing_dir);
+ if (!f.exists()) {
+ existing_dir = null;
+ }
+ }
+
+ DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
+ dd.setMessage("Browse for folder");
+ dd.setFilterPath(existing_dir);
+ String abs_dir = dd.open();
+
+ if (abs_dir != null) {
+ updateLocationPathField(abs_dir);
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * Callback when the "create test project" checkbox is changed.
+ * It enables or disables all UI groups accordingly.
+ */
+ private void onCreateTestProjectToggle() {
+ boolean enabled = mInfo.getCreateTestProject();
+ for (Composite c : mToggleComposites) {
+ enableControl(c, enabled);
+ }
+ mSdkTargetSelector.setEnabled(enabled);
+
+ if (enabled) {
+ useMainProjectInformation();
+ }
+ validatePageComplete();
+ }
+
+ /** Enables or disables controls; recursive for composite controls. */
+ private void enableControl(Control c, boolean enabled) {
+ c.setEnabled(enabled);
+ if (c instanceof Composite)
+ for (Control c2 : ((Composite) c).getChildren()) {
+ enableControl(c2, enabled);
+ }
+ }
+
+ /**
+ * Enables or disable the location widgets depending on the user selection:
+ * the location path is enabled when using the "existing source" mode (i.e. not new project)
+ * or in new project mode with the "use default location" turned off.
+ */
+ private void enableLocationWidgets() {
+ boolean use_default = mInfo.useDefaultLocation();
+ boolean location_enabled = !use_default;
+
+ mLocationLabel.setEnabled(location_enabled);
+ mLocationPathField.setEnabled(location_enabled);
+ mBrowseButton.setEnabled(location_enabled);
+
+ updateLocationPathField(null);
+ }
+
+ /**
+ * Updates the location directory path field.
+ * <br/>
+ * When custom user selection is enabled, use the abs_dir argument if not null and also
+ * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the
+ * user selection to be remembered when the user switches from default to custom.
+ * <br/>
+ * When custom user selection is disabled, use the workspace default location with the
+ * current project name. This does not change the internally cached abs_dir.
+ *
+ * @param abs_dir A new absolute directory path or null to use the default.
+ */
+ private void updateLocationPathField(String abs_dir) {
+ boolean use_default = mInfo.useDefaultLocation();
+ boolean custom_location = !use_default;
+
+ if (!mInternalLocationPathUpdate) {
+ mInternalLocationPathUpdate = true;
+ if (custom_location) {
+ if (abs_dir != null) {
+ // We get here if the user selected a directory with the "Browse" button.
+ sCustomLocationOsPath = TextProcessor.process(abs_dir);
+ }
+ if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) {
+ mLocationPathField.setText(sCustomLocationOsPath);
+ }
+ } else {
+ String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
+ value = TextProcessor.process(value);
+ if (!mLocationPathField.getText().equals(value)) {
+ mLocationPathField.setText(value);
+ }
+ }
+ validatePageComplete();
+ mInternalLocationPathUpdate = false;
+ }
+ }
+
+ /**
+ * The location path field is either modified internally (from updateLocationPathField)
+ * or manually by the user when the custom_location mode is not set.
+ *
+ * Ignore the internal modification. When modified by the user, memorize the choice and
+ * validate the page.
+ */
+ private void onLocationPathFieldModified() {
+ if (!mInternalLocationPathUpdate) {
+ // When the updates doesn't come from updateLocationPathField, it must be the user
+ // editing the field manually, in which case we want to save the value internally
+ String newPath = getLocationPathFieldValue();
+ sCustomLocationOsPath = newPath;
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * The package name field is either modified internally (from extractNamesFromAndroidManifest)
+ * or manually by the user when the custom_location mode is not set.
+ *
+ * Ignore the internal modification. When modified by the user, memorize the choice and
+ * validate the page.
+ */
+ private void onPackageNameFieldModified() {
+ updateTestTargetPackageField(null);
+ validatePageComplete();
+ }
+
+ /**
+ * Changes the {@link #mTestTargetPackageLabel} field.
+ *
+ * When using the "self-test" option, the packageName argument is ignored and the
+ * current value from the project package is used.
+ *
+ * Otherwise the packageName is used if it is not null.
+ */
+ private void updateTestTargetPackageField(String packageName) {
+ if (mInfo.isTestingSelf()) {
+ mTestTargetPackageLabel.setText(mInfo.getPackageName());
+
+ } else if (packageName != null) {
+ mTestTargetPackageLabel.setText(packageName);
+ }
+ }
+
+ /**
+ * Called when the min sdk version field has been modified.
+ *
+ * Ignore the internal modifications. When modified by the user, try to match
+ * a target with the same API level.
+ */
+ private void onMinSdkVersionFieldModified() {
+ if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
+ return;
+ }
+
+ updateSdkSelectorToMatchMinSdkVersion();
+
+ mMinSdkVersionModifiedByUser = true;
+ }
+
+ /**
+ * Try to find an SDK Target that matches the current MinSdkVersion.
+ *
+ * There can be multiple targets with the same sdk api version, so don't change
+ * it if it's already at the right version. Otherwise pick the first target
+ * that matches.
+ */
+ private void updateSdkSelectorToMatchMinSdkVersion() {
+ try {
+ int version = Integer.parseInt(mInfo.getMinSdkVersion());
+
+ IAndroidTarget curr_target = mInfo.getSdkTarget();
+ if (curr_target != null && curr_target.getApiVersionNumber() == version) {
+ return;
+ }
+
+ for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
+ if (target.getApiVersionNumber() == version) {
+ mSdkTargetSelector.setSelection(target);
+ return;
+ }
+ }
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * Called when an SDK target is modified.
+ *
+ * Also changes the minSdkVersion field to reflect the sdk api level that has
+ * just been selected.
+ */
+ private void onSdkTargetModified() {
+ if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
+ return;
+ }
+
+ IAndroidTarget target = mInfo.getSdkTarget();
+
+ if (target != null) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber()));
+ mInternalMinSdkVersionUpdate = false;
+ }
+
+ mSdkTargetModifiedByUser = true;
+ }
+
+ /**
+ * Returns whether this page's controls currently all contain valid values.
+ *
+ * @return <code>true</code> if all controls are valid, and
+ * <code>false</code> 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java
new file mode 100755
index 0000000..dd5cf76
--- /dev/null
+++ b/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.
+ * <p/>
+ * 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/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
index b43e75f..5d57413 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
@@ -5,6 +5,8 @@
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="APPLICATION_NAME">
ACTIVITIES
+TEST-USES-LIBRARY
</application>
USES-SDK
+TEST-INSTRUMENTATION
</manifest>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template
new file mode 100755
index 0000000..c282fbc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template
@@ -0,0 +1 @@
+ <instrumentation android:targetPackage="TEST_TARGET_PCKG" android:name="android.test.InstrumentationTestRunner" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template
new file mode 100755
index 0000000..28ae7a4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template
@@ -0,0 +1 @@
+ <uses-library android:name="android.test.runner" />