aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java809
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/FileStoreAdapter.java160
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java512
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java256
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java1520
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java181
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java412
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java32
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java32
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java606
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java271
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java487
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java293
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java109
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java130
16 files changed, 5904 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
new file mode 100644
index 000000000..c8325345a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaConventions;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.net.URI;
+
+/** Page where you choose the application name, activity name, and optional test project info */
+public class ApplicationInfoPage extends WizardPage implements SelectionListener, ModifyListener,
+ ITargetChangeListener {
+ private static final String JDK_15 = "1.5"; //$NON-NLS-1$
+ private final static String DUMMY_PACKAGE = "your.package.namespace";
+
+ /** Suffix added by default to activity names */
+ static final String ACTIVITY_NAME_SUFFIX = "Activity"; //$NON-NLS-1$
+
+ private final NewProjectWizardState mValues;
+
+ private Text mApplicationText;
+ private Text mPackageText;
+ private Text mActivityText;
+ private Button mCreateActivityCheckbox;
+ private Combo mSdkCombo;
+
+ private boolean mIgnore;
+ private Button mCreateTestCheckbox;
+ private Text mTestProjectNameText;
+ private Text mTestApplicationText;
+ private Text mTestPackageText;
+ private Label mTestProjectNameLabel;
+ private Label mTestApplicationLabel;
+ private Label mTestPackageLabel;
+
+ /**
+ * Create the wizard.
+ */
+ ApplicationInfoPage(NewProjectWizardState values) {
+ super("appInfo"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Application Info");
+ setDescription("Configure the new Android Project");
+ AdtPlugin.getDefault().addTargetListener(this);
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ @Override
+ @SuppressWarnings("unused") // Eclipse marks SWT constructors with side effects as unused
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(2, false));
+
+ Label applicationLabel = new Label(container, SWT.NONE);
+ applicationLabel.setText("Application Name:");
+
+ mApplicationText = new Text(container, SWT.BORDER);
+ mApplicationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mApplicationText.addModifyListener(this);
+
+ Label packageLabel = new Label(container, SWT.NONE);
+ packageLabel.setText("Package Name:");
+
+ mPackageText = new Text(container, SWT.BORDER);
+ mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mPackageText.addModifyListener(this);
+
+ if (mValues.mode != Mode.TEST) {
+ mCreateActivityCheckbox = new Button(container, SWT.CHECK);
+ mCreateActivityCheckbox.setText("Create Activity:");
+ mCreateActivityCheckbox.addSelectionListener(this);
+
+ mActivityText = new Text(container, SWT.BORDER);
+ mActivityText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mActivityText.addModifyListener(this);
+ }
+
+ Label minSdkLabel = new Label(container, SWT.NONE);
+ minSdkLabel.setText("Minimum SDK:");
+
+ mSdkCombo = new Combo(container, SWT.NONE);
+ GridData gdSdkCombo = new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1);
+ gdSdkCombo.widthHint = 200;
+ mSdkCombo.setLayoutData(gdSdkCombo);
+ mSdkCombo.addSelectionListener(this);
+ mSdkCombo.addModifyListener(this);
+
+ onSdkLoaded();
+
+ setControl(container);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+
+ mCreateTestCheckbox = new Button(container, SWT.CHECK);
+ mCreateTestCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCreateTestCheckbox.setText("Create a Test Project");
+ mCreateTestCheckbox.addSelectionListener(this);
+
+ mTestProjectNameLabel = new Label(container, SWT.NONE);
+ mTestProjectNameLabel.setText("Test Project Name:");
+
+ mTestProjectNameText = new Text(container, SWT.BORDER);
+ mTestProjectNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestProjectNameText.addModifyListener(this);
+
+ mTestApplicationLabel = new Label(container, SWT.NONE);
+ mTestApplicationLabel.setText("Test Application:");
+
+ mTestApplicationText = new Text(container, SWT.BORDER);
+ mTestApplicationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestApplicationText.addModifyListener(this);
+
+ mTestPackageLabel = new Label(container, SWT.NONE);
+ mTestPackageLabel.setText("Test Package:");
+
+ mTestPackageText = new Text(container, SWT.BORDER);
+ mTestPackageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTestPackageText.addModifyListener(this);
+ }
+
+ /** Controls whether the options for creating a paired test project should be shown */
+ private void showTestOptions(boolean visible) {
+ if (mValues.mode == Mode.SAMPLE) {
+ visible = false;
+ }
+
+ mCreateTestCheckbox.setVisible(visible);
+ mTestProjectNameLabel.setVisible(visible);
+ mTestProjectNameText.setVisible(visible);
+ mTestApplicationLabel.setVisible(visible);
+ mTestApplicationText.setVisible(visible);
+ mTestPackageLabel.setVisible(visible);
+ mTestPackageText.setVisible(visible);
+ }
+
+ /** Controls whether the options for creating a paired test project should be enabled */
+ private void enableTestOptions(boolean enabled) {
+ mTestProjectNameLabel.setEnabled(enabled);
+ mTestProjectNameText.setEnabled(enabled);
+ mTestApplicationLabel.setEnabled(enabled);
+ mTestApplicationText.setEnabled(enabled);
+ mTestPackageLabel.setEnabled(enabled);
+ mTestPackageText.setEnabled(enabled);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ try {
+ mIgnore = true;
+ if (mValues.applicationName != null) {
+ mApplicationText.setText(mValues.applicationName);
+ }
+ if (mValues.packageName != null) {
+ mPackageText.setText(mValues.packageName);
+ } else {
+ mPackageText.setText(DUMMY_PACKAGE);
+ }
+
+ if (mValues.mode != Mode.TEST) {
+ mCreateActivityCheckbox.setSelection(mValues.createActivity);
+ mActivityText.setEnabled(mValues.createActivity);
+ if (mValues.activityName != null) {
+ mActivityText.setText(mValues.activityName);
+ }
+ }
+ if (mValues.minSdk != null && mValues.minSdk.length() > 0) {
+ mSdkCombo.setText(mValues.minSdk);
+ }
+
+ showTestOptions(mValues.mode == Mode.ANY);
+ enableTestOptions(mCreateTestCheckbox.getSelection());
+
+ if (mValues.testProjectName != null) {
+ mTestProjectNameText.setText(mValues.testProjectName);
+ }
+ if (mValues.testApplicationName != null) {
+ mTestApplicationText.setText(mValues.testApplicationName);
+ }
+ if (mValues.testProjectName != null) {
+ mTestPackageText.setText(mValues.testProjectName);
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ // Start focus with the package name, since the other fields are typically assigned
+ // reasonable defaults
+ mPackageText.setFocus();
+ mPackageText.selectAll();
+
+ validatePage();
+ }
+
+ protected void setSdkTargets(IAndroidTarget[] targets, IAndroidTarget target) {
+ if (targets == null) {
+ targets = new IAndroidTarget[0];
+ }
+ int selectionIndex = -1;
+ String[] items = new String[targets.length];
+ for (int i = 0, n = targets.length; i < n; i++) {
+ items[i] = targetLabel(targets[i]);
+ if (targets[i] == target) {
+ selectionIndex = i;
+ }
+ }
+ try {
+ mIgnore = true;
+ mSdkCombo.setItems(items);
+ mSdkCombo.setData(targets);
+ if (selectionIndex != -1) {
+ mSdkCombo.select(selectionIndex);
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ private String targetLabel(IAndroidTarget target) {
+ // In the minimum SDK chooser, show the targets with api number and description,
+ // such as "11 (Android 3.0)"
+ return String.format("%1$s (%2$s)", target.getVersion().getApiString(),
+ target.getFullName());
+ }
+
+ @Override
+ public void dispose() {
+ AdtPlugin.getDefault().removeTargetListener(this);
+ super.dispose();
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // This page is only needed when creating new projects
+ if (mValues.useExisting || mValues.mode != Mode.ANY) {
+ return true;
+ }
+
+ // Ensure that we reach this page
+ if (mValues.packageName == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mSdkCombo) {
+ mValues.minSdk = mSdkCombo.getText().trim();
+ IAndroidTarget[] targets = (IAndroidTarget[]) mSdkCombo.getData();
+ // An editable combo will treat item selection the same way as a user edit,
+ // so we need to see if the string looks like a labeled version
+ int index = mSdkCombo.getSelectionIndex();
+ if (index != -1) {
+ if (index >= 0 && index < targets.length) {
+ IAndroidTarget target = targets[index];
+ if (targetLabel(target).equals(mValues.minSdk)) {
+ mValues.minSdk = target.getVersion().getApiString();
+ }
+ }
+ }
+
+ // Ensure that we never pick up the (Android x.y) suffix shown in combobox
+ // for readability
+ int separator = mValues.minSdk.indexOf(' ');
+ if (separator != -1) {
+ mValues.minSdk = mValues.minSdk.substring(0, separator);
+ }
+ mValues.minSdkModifiedByUser = true;
+ mValues.updateSdkTargetToMatchMinSdkVersion();
+ } else if (source == mApplicationText) {
+ mValues.applicationName = mApplicationText.getText().trim();
+ mValues.applicationNameModifiedByUser = true;
+
+ if (!mValues.testApplicationNameModified) {
+ mValues.testApplicationName = suggestTestApplicationName(mValues.applicationName);
+ try {
+ mIgnore = true;
+ mTestApplicationText.setText(mValues.testApplicationName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ } else if (source == mPackageText) {
+ mValues.packageName = mPackageText.getText().trim();
+ mValues.packageNameModifiedByUser = true;
+
+ if (!mValues.testPackageModified) {
+ mValues.testPackageName = suggestTestPackage(mValues.packageName);
+ try {
+ mIgnore = true;
+ mTestPackageText.setText(mValues.testPackageName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ } else if (source == mActivityText) {
+ mValues.activityName = mActivityText.getText().trim();
+ mValues.activityNameModifiedByUser = true;
+ } else if (source == mTestApplicationText) {
+ mValues.testApplicationName = mTestApplicationText.getText().trim();
+ mValues.testApplicationNameModified = true;
+ } else if (source == mTestPackageText) {
+ mValues.testPackageName = mTestPackageText.getText().trim();
+ mValues.testPackageModified = true;
+ } else if (source == mTestProjectNameText) {
+ mValues.testProjectName = mTestProjectNameText.getText().trim();
+ mValues.testProjectModified = true;
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mCreateActivityCheckbox) {
+ mValues.createActivity = mCreateActivityCheckbox.getSelection();
+ mActivityText.setEnabled(mValues.createActivity);
+ } else if (source == mSdkCombo) {
+ int index = mSdkCombo.getSelectionIndex();
+ IAndroidTarget[] targets = (IAndroidTarget[]) mSdkCombo.getData();
+ if (index != -1) {
+ if (index >= 0 && index < targets.length) {
+ IAndroidTarget target = targets[index];
+ // Even though we are showing the logical version name, we place the
+ // actual api number as the minimum SDK
+ mValues.minSdk = target.getVersion().getApiString();
+ }
+ } else {
+ String text = mSdkCombo.getText();
+ boolean found = false;
+ for (IAndroidTarget target : targets) {
+ if (targetLabel(target).equals(text)) {
+ mValues.minSdk = target.getVersion().getApiString();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ mValues.minSdk = text;
+ }
+ }
+ } else if (source == mCreateTestCheckbox) {
+ mValues.createPairProject = mCreateTestCheckbox.getSelection();
+ enableTestOptions(mValues.createPairProject);
+ if (mValues.createPairProject) {
+ if (mValues.testProjectName == null || mValues.testProjectName.length() == 0) {
+ mValues.testProjectName = suggestTestProjectName(mValues.projectName);
+ }
+ if (mValues.testApplicationName == null ||
+ mValues.testApplicationName.length() == 0) {
+ mValues.testApplicationName =
+ suggestTestApplicationName(mValues.applicationName);
+ }
+ if (mValues.testPackageName == null || mValues.testPackageName.length() == 0) {
+ mValues.testPackageName = suggestTestPackage(mValues.packageName);
+ }
+
+ try {
+ mIgnore = true;
+ mTestProjectNameText.setText(mValues.testProjectName);
+ mTestApplicationText.setText(mValues.testApplicationName);
+ mTestPackageText.setText(mValues.testPackageName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ private void validatePage() {
+ IStatus status = validatePackage(mValues.packageName);
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validActivity = validateActivity();
+ if (validActivity != null) {
+ status = validActivity;
+ }
+ }
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validMinSdk = validateMinSdk();
+ if (validMinSdk != null) {
+ status = validMinSdk;
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validSourceFolder = validateSourceFolder();
+ if (validSourceFolder != null) {
+ status = validSourceFolder;
+ }
+ }
+
+ // If creating a test project to go along with the main project, also validate
+ // the additional test project parameters
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.createPairProject) {
+ IStatus validTestProject = ProjectNamePage.validateProjectName(
+ mValues.testProjectName);
+ if (validTestProject != null) {
+ status = validTestProject;
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validTestLocation = validateTestProjectLocation();
+ if (validTestLocation != null) {
+ status = validTestLocation;
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validTestPackage = validatePackage(mValues.testPackageName);
+ if (validTestPackage != null) {
+ status = new Status(validTestPackage.getSeverity(),
+ AdtPlugin.PLUGIN_ID,
+ validTestPackage.getMessage() + " (in test package)");
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.projectName.equals(mValues.testProjectName)) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "The main project name and the test project name must be different.");
+ }
+ }
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private IStatus validateTestProjectLocation() {
+ assert mValues.createPairProject;
+
+ // Validate location
+ Path path = new Path(mValues.projectLocation.getPath());
+ if (!mValues.useExisting) {
+ if (!mValues.useDefaultLocation) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject handle = workspace.getRoot().getProject(mValues.testProjectName);
+ IStatus locationStatus = workspace.validateProjectLocationURI(handle, uri);
+ if (!locationStatus.isOK()) {
+ return locationStatus;
+ }
+ // 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 new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ } 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 != null && l.length != 0) {
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "The selected output directory is not empty.");
+ }
+ }
+ } else {
+ IPath destPath = path.removeLastSegments(1).append(mValues.testProjectName);
+ File dest = destPath.toFile();
+ if (dest.exists()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "There is already a file or directory named \"%1$s\" in the selected location.",
+ mValues.testProjectName));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private IStatus validateSourceFolder() {
+ // This check does nothing when creating a new project.
+ // This check is also useless when no activity is present or created.
+ mValues.sourceFolder = SdkConstants.FD_SOURCES;
+ if (!mValues.useExisting || !mValues.createActivity) {
+ return null;
+ }
+
+ String osTarget = mValues.activityName;
+ if (osTarget.indexOf('.') == -1) {
+ osTarget = mValues.packageName + File.separator + osTarget;
+ } else if (osTarget.indexOf('.') == 0) {
+ osTarget = mValues.packageName + osTarget;
+ }
+ osTarget = osTarget.replace('.', File.separatorChar) + SdkConstants.DOT_JAVA;
+
+ File projectDir = mValues.projectLocation;
+ File[] allDirs = projectDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+ if (allDirs != null) {
+ boolean found = false;
+ for (File f : allDirs) {
+ Path path = new Path(f.getAbsolutePath());
+ File java_activity = path.append(osTarget).toFile();
+ if (java_activity.isFile()) {
+ mValues.sourceFolder = f.getName();
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ String projectPath = projectDir.getPath();
+ if (allDirs.length > 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("%1$s can not be found under %2$s.", osTarget,
+ projectPath));
+ } else {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No source folders can be found in %1$s.",
+ projectPath));
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private IStatus validateMinSdk() {
+ // Validate min SDK field
+ // If the min sdk version is empty, it is always accepted.
+ if (mValues.minSdk == null || mValues.minSdk.length() == 0) {
+ return null;
+ }
+
+ IAndroidTarget target = mValues.target;
+ if (target == null) {
+ return null;
+ }
+
+ // If the current target is a preview, explicitly indicate minSdkVersion
+ // must be set to this target name.
+ if (target.getVersion().isPreview() && !target.getVersion().equals(mValues.minSdk)) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "The SDK target is a preview. Min SDK Version must be set to '%s'.",
+ target.getVersion().getCodename()));
+ }
+
+ if (!target.getVersion().equals(mValues.minSdk)) {
+ return new Status(target.getVersion().isPreview() ? IStatus.ERROR : IStatus.WARNING,
+ AdtPlugin.PLUGIN_ID,
+ "The API level for the selected SDK target does not match the Min SDK Version."
+ );
+ }
+
+ return null;
+ }
+
+ public static IStatus validatePackage(String packageFieldContents) {
+ // Validate package
+ if (packageFieldContents == null || packageFieldContents.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must be specified.");
+ } else if (packageFieldContents.equals(DUMMY_PACKAGE)) {
+ // The dummy package name is just a placeholder package (which isn't even valid
+ // because it contains the reserved Java keyword "package") but we want to
+ // make the error message say that a proper package should be entered rather than
+ // what's wrong with this specific package. (And the reason we provide a dummy
+ // package rather than a blank line is to make it more clear to beginners what
+ // we're looking for.
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must be specified.");
+ }
+ // Check it's a valid package string
+ IStatus status = JavaConventions.validatePackageName(packageFieldContents, JDK_15,
+ JDK_15);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // 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 (packageFieldContents.indexOf('.') == -1) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must have at least two identifiers.");
+ }
+
+ return null;
+ }
+
+ public static IStatus validateClass(String className) {
+ if (className == null || className.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Class name must be specified.");
+ }
+ if (className.indexOf('.') != -1) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Enter just a class name, not a full package name");
+ }
+ return JavaConventions.validateJavaTypeName(className, JDK_15, JDK_15);
+ }
+
+ private IStatus validateActivity() {
+ // Validate activity (if creating an activity)
+ if (!mValues.createActivity) {
+ return null;
+ }
+
+ return validateActivity(mValues.activityName);
+ }
+
+ /**
+ * Validates the given activity name
+ *
+ * @param activityFieldContents the activity name to validate
+ * @return a status for whether the activity name is valid
+ */
+ public static IStatus validateActivity(String activityFieldContents) {
+ // Validate activity field
+ if (activityFieldContents == null || activityFieldContents.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Activity name must be specified.");
+ } else if (ACTIVITY_NAME_SUFFIX.equals(activityFieldContents)) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a valid activity name");
+ } else if (activityFieldContents.contains("..")) { //$NON-NLS-1$
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package segments in activity name cannot be empty (..)");
+ }
+ // 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 = ""; //$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.length() > 0 && activityFieldContents.charAt(0) == '.') {
+ activityFieldContents = activityFieldContents.substring(1);
+ }
+
+ // Check it's a valid activity string
+ IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents, JDK_15,
+ JDK_15);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // Check it's a valid package string
+ if (packageName.length() > 0) {
+ status = JavaConventions.validatePackageName(packageName, JDK_15, JDK_15);
+ if (!status.isOK()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ status.getMessage() + " (in the activity name)");
+ }
+ }
+
+ return null;
+ }
+
+ // ---- Implement ITargetChangeListener ----
+
+ @Override
+ public void onSdkLoaded() {
+ if (mSdkCombo == null) {
+ return;
+ }
+
+ // 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();
+ }
+ setSdkTargets(targets, mValues.target);
+ }
+
+ @Override
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ @Override
+ public void onTargetLoaded(IAndroidTarget target) {
+ // Ignore
+ }
+
+ public static String suggestTestApplicationName(String applicationName) {
+ if (applicationName == null) {
+ applicationName = ""; //$NON-NLS-1$
+ }
+ if (applicationName.indexOf(' ') != -1) {
+ return applicationName + " Test"; //$NON-NLS-1$
+ } else {
+ return applicationName + "Test"; //$NON-NLS-1$
+ }
+ }
+
+ public static String suggestTestProjectName(String projectName) {
+ if (projectName == null) {
+ projectName = ""; //$NON-NLS-1$
+ }
+ if (projectName.length() > 0 && Character.isUpperCase(projectName.charAt(0))) {
+ return projectName + "Test"; //$NON-NLS-1$
+ } else {
+ return projectName + "-test"; //$NON-NLS-1$
+ }
+ }
+
+
+ public static String suggestTestPackage(String packagePath) {
+ if (packagePath == null) {
+ packagePath = ""; //$NON-NLS-1$
+ }
+ return packagePath + ".test"; //$NON-NLS-1$
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/FileStoreAdapter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/FileStoreAdapter.java
new file mode 100755
index 000000000..0f4e87e9f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/FileStoreAdapter.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2012 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.internal.wizards.newproject;
+
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.filesystem.IFileSystem;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+
+/**
+ * IFileStore implementation that delegates to the give {@link IFileStore}.
+ * This makes it easier to just override a single method from a store.
+ */
+class FileStoreAdapter implements IFileStore {
+
+ private final IFileStore mStore;
+
+ public FileStoreAdapter(IFileStore store) {
+ mStore = store;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object getAdapter(Class adapter) {
+ return mStore.getAdapter(adapter);
+ }
+
+ @Override
+ public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException {
+ return mStore.childInfos(options, monitor);
+ }
+
+ @Override
+ public String[] childNames(int options, IProgressMonitor monitor)
+ throws CoreException {
+ return mStore.childNames(options, monitor);
+ }
+
+ @Override
+ public IFileStore[] childStores(int options, IProgressMonitor monitor) throws CoreException {
+ return mStore.childStores(options, monitor);
+ }
+
+ @Override
+ public void copy(IFileStore destination, int options, IProgressMonitor monitor)
+ throws CoreException {
+ mStore.copy(destination, options, monitor);
+ }
+
+ @Override
+ public void delete(int options, IProgressMonitor monitor) throws CoreException {
+ mStore.delete(options, monitor);
+ }
+
+ @Override
+ public IFileInfo fetchInfo() {
+ return mStore.fetchInfo();
+ }
+
+ @Override
+ public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
+ return mStore.fetchInfo(options, monitor);
+ }
+
+ @Deprecated
+ @Override
+ public IFileStore getChild(IPath path) {
+ return mStore.getChild(path);
+ }
+
+ @Override
+ public IFileStore getFileStore(IPath path) {
+ return mStore.getFileStore(path);
+ }
+
+ @Override
+ public IFileStore getChild(String name) {
+ return mStore.getChild(name);
+ }
+
+ @Override
+ public IFileSystem getFileSystem() {
+ return mStore.getFileSystem();
+ }
+
+ @Override
+ public String getName() {
+ return mStore.getName();
+ }
+
+ @Override
+ public IFileStore getParent() {
+ return mStore.getParent();
+ }
+
+ @Override
+ public boolean isParentOf(IFileStore other) {
+ return mStore.isParentOf(other);
+ }
+
+ @Override
+ public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException {
+ return mStore.mkdir(options, monitor);
+ }
+
+ @Override
+ public void move(IFileStore destination, int options, IProgressMonitor monitor)
+ throws CoreException {
+ mStore.move(destination, options, monitor);
+ }
+
+ @Override
+ public InputStream openInputStream(int options, IProgressMonitor monitor)
+ throws CoreException {
+ return mStore.openInputStream(options, monitor);
+ }
+
+ @Override
+ public OutputStream openOutputStream(int options, IProgressMonitor monitor)
+ throws CoreException {
+ return mStore.openOutputStream(options, monitor);
+ }
+
+ @Override
+ public void putInfo(IFileInfo info, int options, IProgressMonitor monitor)
+ throws CoreException {
+ mStore.putInfo(info, options, monitor);
+ }
+
+ @Override
+ public File toLocalFile(int options, IProgressMonitor monitor) throws CoreException {
+ return mStore.toLocalFile(options, monitor);
+ }
+
+ @Override
+ public URI toURI() {
+ return mStore.toURI();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java
new file mode 100644
index 000000000..1e02fedae
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2012 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.tools.lint.detector.api.LintUtils;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CellLabelProvider;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Rectangle;
+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.DirectoryDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkingSet;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** WizardPage for importing Android projects */
+class ImportPage extends WizardPage implements SelectionListener, IStructuredContentProvider,
+ ICheckStateListener, KeyListener, TraverseListener, ControlListener {
+ private static final int DIR_COLUMN = 0;
+ private static final int NAME_COLUMN = 1;
+
+ private final NewProjectWizardState mValues;
+ private List<ImportedProject> mProjectPaths;
+ private final IProject[] mExistingProjects;
+
+ private Text mDir;
+ private Button mBrowseButton;
+ private Button mCopyCheckBox;
+ private Button mRefreshButton;
+ private Button mDeselectAllButton;
+ private Button mSelectAllButton;
+ private Table mTable;
+ private CheckboxTableViewer mCheckboxTableViewer;
+ private WorkingSetGroup mWorkingSetGroup;
+
+ ImportPage(NewProjectWizardState values) {
+ super("importPage"); //$NON-NLS-1$
+ mValues = values;
+ setTitle("Import Projects");
+ setDescription("Select a directory to search for existing Android projects");
+ mWorkingSetGroup = new WorkingSetGroup();
+ setWorkingSets(new IWorkingSet[0]);
+
+ // Record all projects such that we can ensure that the project names are unique
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ mExistingProjects = workspaceRoot.getProjects();
+ }
+
+ public void init(IStructuredSelection selection, IWorkbenchPart activePart) {
+ setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ container.setLayout(new GridLayout(3, false));
+
+ Label directoryLabel = new Label(container, SWT.NONE);
+ directoryLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ directoryLabel.setText("Root Directory:");
+
+ mDir = new Text(container, SWT.BORDER);
+ mDir.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mDir.addKeyListener(this);
+ mDir.addTraverseListener(this);
+
+ mBrowseButton = new Button(container, SWT.NONE);
+ mBrowseButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mBrowseButton.setText("Browse...");
+ mBrowseButton.addSelectionListener(this);
+
+ Label projectsLabel = new Label(container, SWT.NONE);
+ projectsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ projectsLabel.setText("Projects:");
+
+ mTable = new Table(container, SWT.CHECK);
+ mTable.setHeaderVisible(true);
+ mCheckboxTableViewer = new CheckboxTableViewer(mTable);
+
+ TableViewerColumn dirViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE);
+ TableColumn dirColumn = dirViewerColumn.getColumn();
+ dirColumn.setWidth(200);
+ dirColumn.setText("Project to Import");
+ TableViewerColumn nameViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE);
+ TableColumn nameColumn = nameViewerColumn.getColumn();
+ nameColumn.setWidth(200);
+ nameColumn.setText("New Project Name");
+ nameViewerColumn.setEditingSupport(new ProjectNameEditingSupport(mCheckboxTableViewer));
+
+ mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 4));
+ mTable.setLinesVisible(true);
+ mTable.setHeaderVisible(true);
+ mTable.addSelectionListener(this);
+ mTable.addControlListener(this);
+ mCheckboxTableViewer.setContentProvider(this);
+ mCheckboxTableViewer.setInput(this);
+ mCheckboxTableViewer.addCheckStateListener(this);
+ mCheckboxTableViewer.setLabelProvider(new ProjectCellLabelProvider());
+
+ mSelectAllButton = new Button(container, SWT.NONE);
+ mSelectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mSelectAllButton.setText("Select All");
+ mSelectAllButton.addSelectionListener(this);
+
+ mDeselectAllButton = new Button(container, SWT.NONE);
+ mDeselectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mDeselectAllButton.setText("Deselect All");
+ mDeselectAllButton.addSelectionListener(this);
+
+ mRefreshButton = new Button(container, SWT.NONE);
+ mRefreshButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mRefreshButton.setText("Refresh");
+ mRefreshButton.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+
+ mCopyCheckBox = new Button(container, SWT.CHECK);
+ mCopyCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mCopyCheckBox.setText("Copy projects into workspace");
+ mCopyCheckBox.addSelectionListener(this);
+
+ Composite group = mWorkingSetGroup.createControl(container);
+ group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1));
+
+ updateColumnWidths();
+ }
+
+ private void updateColumnWidths() {
+ Rectangle r = mTable.getClientArea();
+ int availableWidth = r.width;
+ // Add all available size to the first column
+ for (int i = 1; i < mTable.getColumnCount(); i++) {
+ TableColumn column = mTable.getColumn(i);
+ availableWidth -= column.getWidth();
+ }
+ if (availableWidth > 100) {
+ mTable.getColumn(0).setWidth(availableWidth);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ validatePage();
+ }
+
+ private void refresh() {
+ File root = new File(mDir.getText().trim());
+ mProjectPaths = searchForProjects(root);
+ mCheckboxTableViewer.refresh();
+ mCheckboxTableViewer.setAllChecked(true);
+
+ updateValidity();
+ validatePage();
+ }
+
+ private void updateValidity(){
+ List<ImportedProject> selected = new ArrayList<ImportedProject>();
+ List<ImportedProject> disabled = new ArrayList<ImportedProject>();
+ for (ImportedProject project : mProjectPaths) {
+ String projectName = project.getProjectName();
+ boolean invalid = false;
+ for (IProject existingProject : mExistingProjects) {
+ if (projectName.equals(existingProject.getName())) {
+ invalid = true;
+ break;
+ }
+ }
+ if (invalid) {
+ disabled.add(project);
+ } else {
+ selected.add(project);
+ }
+ }
+
+ mValues.importProjects = selected;
+
+ mCheckboxTableViewer.setGrayedElements(disabled.toArray());
+ mCheckboxTableViewer.setCheckedElements(selected.toArray());
+ mCheckboxTableViewer.refresh();
+ mCheckboxTableViewer.getTable().setFocus();
+ }
+
+ private List<ImportedProject> searchForProjects(File dir) {
+ List<ImportedProject> projects = new ArrayList<ImportedProject>();
+ addProjects(dir, projects, dir.getPath().length() + 1);
+ return projects;
+ }
+
+ /** Finds all project directories under the given directory */
+ private void addProjects(File dir, List<ImportedProject> projects, int prefixLength) {
+ if (dir.isDirectory()) {
+ if (LintUtils.isManifestFolder(dir)) {
+ String relative = dir.getPath();
+ if (relative.length() > prefixLength) {
+ relative = relative.substring(prefixLength);
+ }
+ projects.add(new ImportedProject(dir, relative));
+ }
+
+ File[] children = dir.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ addProjects(child, projects, prefixLength);
+ }
+ }
+ }
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+
+ // Validate project name -- unless we're creating a sample, in which case
+ // the user will get a chance to pick the name on the Sample page
+ if (mProjectPaths == null || mProjectPaths.isEmpty()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Select a directory to search for existing Android projects");
+ } else if (mValues.importProjects == null || mValues.importProjects.isEmpty()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Select at least one project");
+ } else {
+ for (ImportedProject project : mValues.importProjects) {
+ if (mCheckboxTableViewer.getGrayed(project)) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("Cannot import %1$s because the project name is in use",
+ project.getProjectName()));
+ break;
+ } else {
+ status = ProjectNamePage.validateProjectName(project.getProjectName());
+ if (status != null && !status.isOK()) {
+ // Need to insert project name to make it clear which project name
+ // is in violation
+ if (mValues.importProjects.size() > 1) {
+ String message = String.format("%1$s: %2$s",
+ project.getProjectName(), status.getMessage());
+ status = new Status(status.getSeverity(), AdtPlugin.PLUGIN_ID,
+ message);
+ }
+ break;
+ } else {
+ status = null; // Don't leave non null status with isOK() == true
+ }
+ }
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ /**
+ * Returns the working sets to which the new project should be added.
+ *
+ * @return the selected working sets to which the new project should be added
+ */
+ private IWorkingSet[] getWorkingSets() {
+ return mWorkingSetGroup.getSelectedWorkingSets();
+ }
+
+ /**
+ * Sets the working sets to which the new project should be added.
+ *
+ * @param workingSets the initial selected working sets
+ */
+ private void setWorkingSets(IWorkingSet[] workingSets) {
+ assert workingSets != null;
+ mWorkingSetGroup.setWorkingSets(workingSets);
+ }
+
+ @Override
+ public IWizardPage getNextPage() {
+ // Sync working set data to the value object, since the WorkingSetGroup
+ // doesn't let us add listeners to do this lazily
+ mValues.workingSets = getWorkingSets();
+
+ return super.getNextPage();
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Object source = e.getSource();
+ if (source == mBrowseButton) {
+ // Choose directory
+ DirectoryDialog dialog = new DirectoryDialog(getShell(), SWT.OPEN);
+ String path = mDir.getText().trim();
+ if (path.length() > 0) {
+ dialog.setFilterPath(path);
+ }
+ String file = dialog.open();
+ if (file != null) {
+ mDir.setText(file);
+ refresh();
+ }
+ } else if (source == mSelectAllButton) {
+ mCheckboxTableViewer.setAllChecked(true);
+ mValues.importProjects = mProjectPaths;
+ } else if (source == mDeselectAllButton) {
+ mCheckboxTableViewer.setAllChecked(false);
+ mValues.importProjects = Collections.emptyList();
+ } else if (source == mRefreshButton || source == mDir) {
+ refresh();
+ } else if (source == mCopyCheckBox) {
+ mValues.copyIntoWorkspace = mCopyCheckBox.getSelection();
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ // ---- KeyListener ----
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getSource() == mDir) {
+ if (e.keyCode == SWT.CR) {
+ refresh();
+ }
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ // ---- TraverseListener ----
+
+ @Override
+ public void keyTraversed(TraverseEvent e) {
+ // Prevent Return from running through the wizard; return is handled by
+ // key listener to refresh project list instead
+ if (SWT.TRAVERSE_RETURN == e.detail) {
+ e.doit = false;
+ }
+ }
+
+ // ---- Implements IStructuredContentProvider ----
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return mProjectPaths != null ? mProjectPaths.toArray() : new Object[0];
+ }
+
+ // ---- Implements ICheckStateListener ----
+
+ @Override
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ // Try to disable other elements that conflict with this
+ Object[] checked = mCheckboxTableViewer.getCheckedElements();
+ List<ImportedProject> selected = new ArrayList<ImportedProject>(checked.length);
+ for (Object o : checked) {
+ if (!mCheckboxTableViewer.getGrayed(o)) {
+ selected.add((ImportedProject) o);
+ }
+ }
+ mValues.importProjects = selected;
+ validatePage();
+
+ mCheckboxTableViewer.update(event.getElement(), null);
+ }
+
+ // ---- Implements ControlListener ----
+
+ @Override
+ public void controlMoved(ControlEvent e) {
+ }
+
+ @Override
+ public void controlResized(ControlEvent e) {
+ updateColumnWidths();
+ }
+
+ private final class ProjectCellLabelProvider extends CellLabelProvider {
+ @Override
+ public void update(ViewerCell cell) {
+ Object element = cell.getElement();
+ int index = cell.getColumnIndex();
+ ImportedProject project = (ImportedProject) element;
+
+ Display display = mTable.getDisplay();
+ Color fg;
+ if (mCheckboxTableViewer.getGrayed(element)) {
+ fg = display.getSystemColor(SWT.COLOR_DARK_GRAY);
+ } else {
+ fg = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ }
+ cell.setForeground(fg);
+ cell.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+
+ switch (index) {
+ case DIR_COLUMN: {
+ // Directory name
+ cell.setText(project.getRelativePath());
+ return;
+ }
+
+ case NAME_COLUMN: {
+ // New name
+ cell.setText(project.getProjectName());
+ return;
+ }
+ default:
+ assert false : index;
+ }
+ cell.setText("");
+ }
+ }
+
+ /** Editing support for the project name column */
+ private class ProjectNameEditingSupport extends EditingSupport {
+ private ProjectNameEditingSupport(ColumnViewer viewer) {
+ super(viewer);
+ }
+
+ @Override
+ protected void setValue(Object element, Object value) {
+ ImportedProject project = (ImportedProject) element;
+ project.setProjectName(value.toString());
+ mCheckboxTableViewer.update(element, null);
+ updateValidity();
+ validatePage();
+ }
+
+ @Override
+ protected Object getValue(Object element) {
+ ImportedProject project = (ImportedProject) element;
+ return project.getProjectName();
+ }
+
+ @Override
+ protected CellEditor getCellEditor(Object element) {
+ return new TextCellEditor(mTable);
+ }
+
+ @Override
+ protected boolean canEdit(Object element) {
+ return true;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java
new file mode 100644
index 000000000..1004fd692
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 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.internal.wizards.newproject;
+
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbench;
+
+import java.io.File;
+
+
+/**
+ * An "Import Android Project" wizard.
+ */
+public class ImportProjectWizard extends Wizard implements INewWizard {
+ private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
+
+ private NewProjectWizardState mValues;
+ private ImportPage mImportPage;
+ private IStructuredSelection mSelection;
+
+ /** Constructs a new wizard default project wizard */
+ public ImportProjectWizard() {
+ }
+
+ @Override
+ public void addPages() {
+ mValues = new NewProjectWizardState(Mode.ANY);
+ mImportPage = new ImportPage(mValues);
+ if (mSelection != null) {
+ mImportPage.init(mSelection, AdtUtils.getActivePart());
+ }
+ addPage(mImportPage);
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ mSelection = selection;
+
+ setHelpAvailable(false); // TODO have help
+ ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
+ setDefaultPageImageDescriptor(desc);
+
+ // Trigger a check to see if the SDK needs to be reloaded (which will
+ // invoke onSdkLoaded asynchronously as needed).
+ AdtPlugin.getDefault().refreshSdk();
+ }
+
+ @Override
+ public boolean performFinish() {
+ File file = new File(AdtPlugin.getOsSdkFolder(), OS_SDK_TOOLS_LIB_FOLDER + File.separator
+ + FN_PROJECT_PROGUARD_FILE);
+ if (!file.exists()) {
+ AdtPlugin.displayError("Tools Out of Date?",
+ String.format("It looks like you do not have the latest version of the "
+ + "SDK Tools installed. Make sure you update via the SDK Manager "
+ + "first. (Could not find %1$s)", file.getPath()));
+ return false;
+ }
+
+ NewProjectCreator creator = new NewProjectCreator(mValues, getContainer());
+ if (!(creator.createAndroidProjects())) {
+ return false;
+ }
+
+ // Open the default Java Perspective
+ OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction();
+ action.run();
+ return true;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java
new file mode 100644
index 000000000..74af651ca
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2012 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.internal.wizards.newproject;
+
+import static com.android.SdkConstants.ATTR_NAME;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.xml.AndroidManifestParser;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.ManifestData.Activity;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.io.FolderWrapper;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** An Android project to be imported */
+class ImportedProject {
+ private final File mLocation;
+ private String mActivityName;
+ private ManifestData mManifest;
+ private String mProjectName;
+ private String mRelativePath;
+
+ ImportedProject(File location, String relativePath) {
+ super();
+ mLocation = location;
+ mRelativePath = relativePath;
+ }
+
+ File getLocation() {
+ return mLocation;
+ }
+
+ String getRelativePath() {
+ return mRelativePath;
+ }
+
+ @Nullable
+ ManifestData getManifest() {
+ if (mManifest == null) {
+ try {
+ mManifest = AndroidManifestParser.parse(new FolderWrapper(mLocation));
+ } catch (SAXException e) {
+ // Some sort of error in the manifest file: report to the user in a better way?
+ AdtPlugin.log(e, null);
+ return null;
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ return null;
+ }
+ }
+
+ return mManifest;
+ }
+
+ @Nullable
+ public String getActivityName() {
+ if (mActivityName == null) {
+ // Compute the project name and the package name from the manifest
+ ManifestData manifest = getManifest();
+ if (manifest != null) {
+ if (manifest.getLauncherActivity() != null) {
+ mActivityName = manifest.getLauncherActivity().getName();
+ }
+ if (mActivityName == null || mActivityName.isEmpty()) {
+ Activity[] activities = manifest.getActivities();
+ for (Activity activity : activities) {
+ mActivityName = activity.getName();
+ if (mActivityName != null && !mActivityName.isEmpty()) {
+ break;
+ }
+ }
+ }
+ if (mActivityName != null) {
+ int index = mActivityName.lastIndexOf('.');
+ mActivityName = mActivityName.substring(index + 1);
+ }
+ }
+ }
+
+ return mActivityName;
+ }
+
+ @NonNull
+ public String getProjectName() {
+ if (mProjectName == null) {
+ // Are we importing an Eclipse project? If so just use the existing project name
+ mProjectName = findEclipseProjectName();
+ if (mProjectName != null) {
+ return mProjectName;
+ }
+
+ String activityName = getActivityName();
+ if (activityName == null || activityName.isEmpty()) {
+ // I could also look at the build files, say build.xml from ant, and
+ // try to glean the project name from there
+ mProjectName = mLocation.getName();
+ } else {
+ // Try to derive it from the activity name:
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IStatus nameStatus = workspace.validateName(activityName, IResource.PROJECT);
+ if (nameStatus.isOK()) {
+ mProjectName = activityName;
+ } else {
+ // Try to derive it by escaping characters
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, n = activityName.length(); i < n; i++) {
+ char c = activityName.charAt(i);
+ if (c != IPath.DEVICE_SEPARATOR && c != IPath.SEPARATOR && c != '\\') {
+ sb.append(c);
+ }
+ }
+ if (sb.length() == 0) {
+ mProjectName = mLocation.getName();
+ } else {
+ mProjectName = sb.toString();
+ }
+ }
+ }
+ }
+
+ return mProjectName;
+ }
+
+ @Nullable
+ private String findEclipseProjectName() {
+ File projectFile = new File(mLocation, ".project"); //$NON-NLS-1$
+ if (projectFile.exists()) {
+ String xml;
+ try {
+ xml = Files.toString(projectFile, Charsets.UTF_8);
+ Document doc = DomUtilities.parseDocument(xml, false);
+ if (doc != null) {
+ NodeList names = doc.getElementsByTagName(ATTR_NAME);
+ if (names.getLength() >= 1) {
+ Node nameElement = names.item(0);
+ String name = nameElement.getTextContent().trim();
+ if (!name.isEmpty()) {
+ return name;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // pass: don't attempt to read project name; must be some sort of unrelated
+ // file with the same name, perhaps from a different editor or IDE
+ }
+ }
+
+ return null;
+ }
+
+ public void setProjectName(@NonNull String newName) {
+ mProjectName = newName;
+ }
+
+ public IAndroidTarget getTarget() {
+ // Pick a target:
+ // First try to find the one requested by project.properties
+ IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
+ ProjectProperties properties = ProjectProperties.load(mLocation.getPath(),
+ PropertyType.PROJECT);
+ if (properties != null) {
+ String targetProperty = properties.getProperty(ProjectProperties.PROPERTY_TARGET);
+ if (targetProperty != null) {
+ Matcher m = Pattern.compile("android-(.+)").matcher( //$NON-NLS-1$
+ targetProperty.trim());
+ if (m.matches()) {
+ String targetName = m.group(1);
+ int targetLevel;
+ try {
+ targetLevel = Integer.parseInt(targetName);
+ } catch (NumberFormatException nufe) {
+ // pass
+ targetLevel = -1;
+ }
+ for (IAndroidTarget t : targets) {
+ AndroidVersion version = t.getVersion();
+ if (version.isPreview() && targetName.equals(version.getCodename())) {
+ return t;
+ } else if (targetLevel == version.getApiLevel()) {
+ return t;
+ }
+ }
+ if (targetLevel > 0) {
+ // If not found, pick the closest one that is higher than the
+ // api level
+ IAndroidTarget target = targets[targets.length - 1];
+ int targetDelta = target.getVersion().getApiLevel() - targetLevel;
+ for (IAndroidTarget t : targets) {
+ int newDelta = t.getVersion().getApiLevel() - targetLevel;
+ if (newDelta >= 0 && newDelta < targetDelta) {
+ targetDelta = newDelta;
+ target = t;
+ }
+ }
+
+ return target;
+ }
+ }
+ }
+ }
+
+ // If not found, pick the closest one to the one requested by the
+ // project (in project.properties) that is still >= the minSdk version
+ IAndroidTarget target = targets[targets.length - 1];
+ ManifestData manifest = getManifest();
+ if (manifest != null) {
+ int minSdkLevel = manifest.getMinSdkVersion();
+ int targetDelta = target.getVersion().getApiLevel() - minSdkLevel;
+ for (IAndroidTarget t : targets) {
+ int newDelta = t.getVersion().getApiLevel() - minSdkLevel;
+ if (newDelta >= 0 && newDelta < targetDelta) {
+ targetDelta = newDelta;
+ target = t;
+ }
+ }
+ }
+
+ return target;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
new file mode 100644
index 000000000..d168c7503
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
@@ -0,0 +1,1520 @@
+/*
+ * 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.
+ */
+
+package com.android.ide.eclipse.adt.internal.wizards.newproject;
+
+import static com.android.SdkConstants.FN_PROJECT_PROPERTIES;
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_LIBRARY;
+
+import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.res2.ValueXmlHelper;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.AndroidNature;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.io.StreamException;
+import com.android.resources.Density;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.filesystem.IFileSystem;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+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;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableContext;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IWorkingSet;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * The actual project creator invoked from the New Project Wizard
+ * <p/>
+ * 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.
+ */
+public class NewProjectCreator {
+
+ 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_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$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_SAMPLE_LOCATION = "SAMPLE_LOCATION"; //$NON-NLS-1$
+ private static final String PARAM_SOURCE = "SOURCE"; //$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_IS_LIBRARY = "IS_LIBRARY"; //$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 + AdtConstants.WS_SEP;
+ private static final String BIN_CLASSES_DIRECTORY =
+ SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP +
+ SdkConstants.FD_CLASSES_OUTPUT + AdtConstants.WS_SEP;
+ private static final String RES_DIRECTORY =
+ SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP;
+ private static final String ASSETS_DIRECTORY =
+ SdkConstants.FD_ASSETS + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP;
+ private static final String DRAWABLE_XHDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.XHIGH.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_HDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.HIGH.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_MDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.MEDIUM.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String DRAWABLE_LDPI_DIRECTORY =
+ SdkConstants.FD_RES_DRAWABLE + '-' + Density.LOW.getResourceValue() +
+ AdtConstants.WS_SEP;
+ private static final String LAYOUT_DIRECTORY =
+ SdkConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP;
+ private static final String VALUES_DIRECTORY =
+ SdkConstants.FD_RES_VALUES + AdtConstants.WS_SEP;
+ private static final String GEN_SRC_DIRECTORY =
+ SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP;
+
+ private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
+ private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
+ + "AndroidManifest.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
+ + "activity.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ + "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$
+ private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
+ + "string.template"; //$NON-NLS-1$
+ private static final String PROJECT_ICON = "ic_launcher.png"; //$NON-NLS-1$
+ private static final String ICON_XHDPI = "ic_launcher_xhdpi.png"; //$NON-NLS-1$
+ private static final String ICON_HDPI = "ic_launcher_hdpi.png"; //$NON-NLS-1$
+ private static final String ICON_MDPI = "ic_launcher_mdpi.png"; //$NON-NLS-1$
+ private static final String ICON_LDPI = "ic_launcher_ldpi.png"; //$NON-NLS-1$
+
+ private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
+
+ private static final String STRING_RSRC_PREFIX = SdkConstants.STRING_PREFIX;
+ 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, BIN_CLASSES_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
+ private static final String[] RES_DIRECTORIES = new String[] {
+ DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+ private static final String[] RES_DENSITY_ENABLED_DIRECTORIES = new String[] {
+ DRAWABLE_XHDPI_DIRECTORY,
+ DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY,
+ LAYOUT_DIRECTORY, VALUES_DIRECTORY };
+
+ 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 final NewProjectWizardState mValues;
+ private final IRunnableContext mRunnableContext;
+
+ /**
+ * Creates a new {@linkplain NewProjectCreator}
+ * @param values the wizard state with initial project parameters
+ * @param runnableContext the context to run project creation in
+ */
+ public NewProjectCreator(NewProjectWizardState values, IRunnableContext runnableContext) {
+ mValues = values;
+ mRunnableContext = runnableContext;
+ }
+
+ /**
+ * 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.
+ */
+ private boolean validateNewProjectLocationIsEmpty(IPath destination) {
+ File f = new File(destination.toOSString());
+ if (f.isDirectory() && f.list().length > 0) {
+ return AdtPlugin.displayPrompt("New Android Project",
+ "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
+ }
+ return true;
+ }
+
+ /**
+ * Structure that describes all the information needed to create a project.
+ * This is collected from the pages by {@link NewProjectCreator#createAndroidProjects()}
+ * and then used by
+ * {@link NewProjectCreator#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.
+ */
+ public boolean createAndroidProjects() {
+ if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
+ return importProjects();
+ }
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ final ProjectInfo testData = collectTestPageInfo();
+
+ // 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, null, true);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Creates the a plain Java project without typical android directories or an Android Nature.
+ * This is intended for use by unit tests and not as a general-purpose Java project creator.
+ * @return True if the project could be created.
+ */
+ @VisibleForTesting
+ public boolean createJavaProjects() {
+ if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) {
+ return importProjects();
+ }
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ final ProjectInfo testData = collectTestPageInfo();
+
+ // 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, null, false);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Imports a list of projects
+ */
+ private boolean importProjects() {
+ assert mValues.importProjects != null && !mValues.importProjects.isEmpty();
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+
+ final List<ProjectInfo> projectData = new ArrayList<ProjectInfo>();
+ for (ImportedProject p : mValues.importProjects) {
+
+ // Compute the project name and the package name from the manifest
+ ManifestData manifest = p.getManifest();
+ if (manifest == null) {
+ continue;
+ }
+ String packageName = manifest.getPackage();
+ String projectName = p.getProjectName();
+ String minSdk = manifest.getMinSdkVersionString();
+
+ final IProject project = workspace.getRoot().getProject(projectName);
+ final IProjectDescription description =
+ workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, projectName);
+ parameters.put(PARAM_PACKAGE, packageName);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, Boolean.FALSE);
+ parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
+
+ parameters.put(PARAM_SDK_TARGET, p.getTarget());
+
+ // TODO: Find out if these end up getting used in the import-path through the code!
+ parameters.put(PARAM_MIN_SDK_VERSION, minSdk);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, mValues.applicationName);
+
+ if (mValues.copyIntoWorkspace) {
+ parameters.put(PARAM_SOURCE, p.getLocation());
+
+ // TODO: Make sure it isn't *already* in the workspace!
+ //IPath defaultLocation = Platform.getLocation();
+ //if ((!mValues.useDefaultLocation || mValues.useExisting)
+ // && !defaultLocation.isPrefixOf(path)) {
+ //IPath workspaceLocation = Platform.getLocation().append(projectName);
+ //description.setLocation(workspaceLocation);
+ // DON'T SET THE LOCATION: It's IMPLIED and in fact it will generate
+ // an error if you set it!
+ } else {
+ // Create in place
+ description.setLocation(new Path(p.getLocation().getPath()));
+ }
+
+ projectData.add(new ProjectInfo(project, description, parameters, dictionary));
+ }
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, null, null, projectData, true);
+ }
+ };
+
+ // 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 (mValues.mode == Mode.TEST) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProject project = workspace.getRoot().getProject(mValues.projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, mValues.projectName);
+ parameters.put(PARAM_PACKAGE, mValues.packageName);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, mValues.mode == Mode.ANY && !mValues.useExisting);
+ parameters.put(PARAM_SAMPLE_LOCATION, mValues.chosenSample);
+ parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ if (mValues.createActivity) {
+ parameters.put(PARAM_ACTIVITY, mValues.activityName);
+ }
+
+ // 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, mValues.applicationName);
+
+ IPath path = new Path(mValues.projectLocation.getPath());
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !defaultLocation.isPrefixOf(path)) {
+ description.setLocation(path);
+ }
+
+ if (mValues.mode == Mode.ANY && !mValues.useExisting && !mValues.useDefaultLocation &&
+ !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * 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 (mValues.mode != Mode.TEST && !mValues.createPairProject) {
+ return null;
+ }
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ String projectName =
+ mValues.mode == Mode.TEST ? mValues.projectName : mValues.testProjectName;
+ final IProject project = workspace.getRoot().getProject(projectName);
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+
+ String pkg =
+ mValues.mode == Mode.TEST ? mValues.packageName : mValues.testPackageName;
+
+ parameters.put(PARAM_PACKAGE, pkg);
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, !mValues.useExisting);
+ parameters.put(PARAM_SRC_FOLDER, mValues.sourceFolder);
+ parameters.put(PARAM_SDK_TARGET, mValues.target);
+ parameters.put(PARAM_MIN_SDK_VERSION, mValues.minSdk);
+
+ // Test-specific parameters
+ String testedPkg = mValues.createPairProject
+ ? mValues.packageName : mValues.testTargetPackageName;
+ if (testedPkg == null) {
+ assert mValues.testingSelf;
+ testedPkg = pkg;
+ }
+
+ parameters.put(PARAM_TEST_TARGET_PACKAGE, testedPkg);
+
+ if (mValues.testingSelf) {
+ parameters.put(PARAM_TARGET_SELF, true);
+ } else {
+ parameters.put(PARAM_TARGET_EXISTING, true);
+ parameters.put(PARAM_REFERENCE_PROJECT, mValues.testedProject);
+ }
+
+ if (mValues.createPairProject) {
+ parameters.put(PARAM_TARGET_MAIN, true);
+ }
+
+ // 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, mValues.testApplicationName);
+
+ // Use the same logic to determine test project location as in
+ // ApplicationInfoPage#validateTestProjectLocation
+ IPath path = new Path(mValues.projectLocation.getPath());
+ path = path.removeLastSegments(1).append(mValues.testProjectName);
+ IPath defaultLocation = Platform.getLocation();
+ if ((!mValues.useDefaultLocation || mValues.useExisting)
+ && !path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (!mValues.useExisting && !mValues.useDefaultLocation &&
+ !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
+
+ /**
+ * Runs the operation in a different thread and display generated
+ * exceptions.
+ *
+ * @param op The asynchronous operation to run.
+ */
+ private void runAsyncOperation(WorkspaceModifyOperation op) {
+ try {
+ mRunnableContext.run(true /* fork */, true /* cancelable */, op);
+ } catch (InvocationTargetException e) {
+
+ AdtPlugin.log(e, "New Project Wizard failed");
+
+ // The runnable threw an exception
+ Throwable t = e.getTargetException();
+ if (t instanceof CoreException) {
+ CoreException core = (CoreException) t;
+ if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
+ // The error indicates the file system is not case sensitive
+ // and there's a resource with a similar name.
+ MessageDialog.openError(AdtPlugin.getShell(),
+ "Error", "Error: Case Variant Exists");
+ } else {
+ ErrorDialog.openError(AdtPlugin.getShell(),
+ "Error", core.getMessage(), core.getStatus());
+ }
+ } else {
+ // Some other kind of exception
+ String msg = t.getMessage();
+ Throwable t1 = t;
+ while (msg == null && t1.getCause() != null) {
+ msg = t1.getMessage();
+ t1 = t1.getCause();
+ }
+ if (msg == null) {
+ msg = t.toString();
+ }
+ MessageDialog.openError(AdtPlugin.getShell(), "Error", msg);
+ }
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates the actual project(s). This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @param mainData Data for main project. Can be null.
+ * @param isAndroidProject true if the project is to be set up as a full Android project; false
+ * for a plain Java project.
+ * @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(IProgressMonitor monitor,
+ ProjectInfo mainData,
+ ProjectInfo testData,
+ List<ProjectInfo> importData,
+ boolean isAndroidProject)
+ throws InvocationTargetException {
+ monitor.beginTask("Create Android Project", 100);
+ try {
+ IProject mainProject = null;
+
+ if (mainData != null) {
+ mainProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ mainData.getProject(),
+ mainData.getDescription(),
+ mainData.getParameters(),
+ mainData.getDictionary(),
+ null,
+ isAndroidProject);
+
+ if (mainProject != null) {
+ final IJavaProject javaProject = JavaCore.create(mainProject);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ }
+ }
+
+ if (testData != null) {
+ Map<String, Object> parameters = testData.getParameters();
+ if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
+ parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
+ }
+
+ IProject testProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ testData.getProject(),
+ testData.getDescription(),
+ parameters,
+ testData.getDictionary(),
+ null,
+ isAndroidProject);
+ if (testProject != null) {
+ final IJavaProject javaProject = JavaCore.create(testProject);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ }
+ }
+
+ if (importData != null) {
+ for (final ProjectInfo data : importData) {
+ ProjectPopulator projectPopulator = null;
+ if (mValues.copyIntoWorkspace) {
+ projectPopulator = new ProjectPopulator() {
+ @Override
+ public void populate(IProject project) {
+ // Copy
+ IFileSystem fileSystem = EFS.getLocalFileSystem();
+ File source = (File) data.getParameters().get(PARAM_SOURCE);
+ IFileStore sourceDir = new ReadWriteFileStore(
+ fileSystem.getStore(source.toURI()));
+ IFileStore destDir = new ReadWriteFileStore(
+ fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
+ try {
+ sourceDir.copy(destDir, EFS.OVERWRITE, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ };
+ }
+ IProject project = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ data.getProject(),
+ data.getDescription(),
+ data.getParameters(),
+ data.getDictionary(),
+ projectPopulator,
+ isAndroidProject);
+ if (project != null) {
+ final IJavaProject javaProject = JavaCore.create(project);
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ mValues.workingSets));
+ ProjectHelper.enforcePreferredCompilerCompliance(javaProject);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ throw new InvocationTargetException(e);
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ } catch (StreamException e) {
+ throw new InvocationTargetException(e);
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /** Handler which can write contents into a project */
+ public interface ProjectPopulator {
+ /**
+ * Add contents into the given project
+ *
+ * @param project the project to write into
+ * @throws InvocationTargetException if anything goes wrong
+ */
+ public void populate(IProject project) throws InvocationTargetException;
+ }
+
+ /**
+ * 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.
+ * @param isAndroidProject true if the project is to be set up as a full Android project; false
+ * for a plain Java project.
+ * @return The project newly created
+ * @throws StreamException
+ */
+ private IProject createEclipseProject(
+ @NonNull IProgressMonitor monitor,
+ @NonNull IProject project,
+ @NonNull IProjectDescription description,
+ @NonNull Map<String, Object> parameters,
+ @Nullable Map<String, String> dictionary,
+ @Nullable ProjectPopulator projectPopulator,
+ boolean isAndroidProject)
+ throws CoreException, IOException, StreamException {
+
+ // get the project target
+ IAndroidTarget target = (IAndroidTarget) parameters.get(PARAM_SDK_TARGET);
+ boolean legacy = isAndroidProject && target.getVersion().getApiLevel() < 4;
+
+ // 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, isAndroidProject);
+
+ // Create folders in the project if they don't already exist
+ addDefaultDirectories(project, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
+ String[] sourceFolders;
+ if (isAndroidProject) {
+ sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ } else {
+ sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER)
+ };
+ }
+ addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor);
+
+ // Create the resource folders in the project if they don't already exist.
+ if (legacy) {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
+ } else {
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
+ }
+
+ if (projectPopulator != null) {
+ try {
+ projectPopulator.populate(project);
+ } catch (InvocationTargetException ite) {
+ AdtPlugin.log(ite, null);
+ }
+ }
+
+ // Setup class path: mark folders as source folders
+ IJavaProject javaProject = JavaCore.create(project);
+ setupSourceFolders(javaProject, sourceFolders, monitor);
+
+ 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, legacy, monitor);
+
+ // Create the default package components
+ addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
+
+ // add the string definition file if needed
+ if (dictionary != null && dictionary.size() > 0) {
+ addStringDictionaryFile(project, dictionary, monitor);
+ }
+
+ // add the default proguard config
+ File libFolder = new File((String) parameters.get(PARAM_SDK_TOOLS_DIR),
+ SdkConstants.FD_LIB);
+ addLocalFile(project,
+ new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
+ // Write ProGuard config files with the extension .pro which
+ // is what is used in the ProGuard documentation and samples
+ SdkConstants.FN_PROJECT_PROGUARD_FILE,
+ monitor);
+
+ // Set output location
+ javaProject.setOutputLocation(project.getFolder(BIN_CLASSES_DIRECTORY).getFullPath(),
+ monitor);
+ }
+
+ File sampleDir = (File) parameters.get(PARAM_SAMPLE_LOCATION);
+ if (sampleDir != null) {
+ // Copy project
+ copySampleCode(project, sampleDir, parameters, dictionary, 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);
+ }
+ }
+
+ if (isAndroidProject) {
+ Sdk.getCurrent().initProject(project, target);
+ }
+
+ // Fix the project to make sure all properties are as expected.
+ // Necessary for existing projects and good for new ones to.
+ ProjectHelper.fixProject(project);
+
+ Boolean isLibraryProject = (Boolean) parameters.get(PARAM_IS_LIBRARY);
+ if (isLibraryProject != null && isLibraryProject.booleanValue()
+ && Sdk.getCurrent() != null && project.isOpen()) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null) {
+ // make a working copy of the properties
+ ProjectPropertiesWorkingCopy properties =
+ state.getProperties().makeWorkingCopy();
+
+ properties.setProperty(PROPERTY_LIBRARY, Boolean.TRUE.toString());
+ try {
+ properties.save();
+ IResource projectProp = project.findMember(FN_PROJECT_PROPERTIES);
+ if (projectProp != null) {
+ projectProp.refreshLocal(DEPTH_ZERO, new NullProgressMonitor());
+ }
+ } catch (Exception e) {
+ String msg = String.format(
+ "Failed to save %1$s for project %2$s",
+ SdkConstants.FN_PROJECT_PROPERTIES, project.getName());
+ AdtPlugin.log(e, msg);
+ }
+ }
+ }
+
+ return project;
+ }
+
+ /**
+ * Creates a new project
+ *
+ * @param monitor An existing monitor.
+ * @param project The project to create.
+ * @param target the build target to associate with the project
+ * @param projectPopulator a handler for writing the template contents
+ * @param isLibrary whether this project should be marked as a library project
+ * @param projectLocation the location to write the project into
+ * @param workingSets Eclipse working sets, if any, to add the project to
+ * @throws CoreException if anything goes wrong
+ */
+ public static void create(
+ @NonNull IProgressMonitor monitor,
+ @NonNull final IProject project,
+ @NonNull IAndroidTarget target,
+ @Nullable final ProjectPopulator projectPopulator,
+ boolean isLibrary,
+ @NonNull String projectLocation,
+ @NonNull final IWorkingSet[] workingSets)
+ throws CoreException {
+ final NewProjectCreator creator = new NewProjectCreator(null, null);
+
+ final Map<String, String> dictionary = null;
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_SDK_TARGET, target);
+ parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
+ parameters.put(PARAM_IS_NEW_PROJECT, false);
+ parameters.put(PARAM_SAMPLE_LOCATION, null);
+ parameters.put(PARAM_IS_LIBRARY, isLibrary);
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ if (projectLocation != null) {
+ IPath path = new Path(projectLocation);
+ IPath parent = new Path(path.toFile().getParent());
+ IPath workspaceLocation = Platform.getLocation();
+ if (!workspaceLocation.equals(parent)) {
+ description.setLocation(path);
+ }
+ }
+
+ IWorkspaceRunnable workspaceRunnable = new IWorkspaceRunnable() {
+ @Override
+ public void run(IProgressMonitor submonitor) throws CoreException {
+ try {
+ creator.createEclipseProject(submonitor, project, description, parameters,
+ dictionary, projectPopulator, true);
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Unexpected error while creating project", e));
+ } catch (StreamException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Unexpected error while creating project", e));
+ }
+ if (workingSets != null && workingSets.length > 0) {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject != null) {
+ Display.getDefault().syncExec(new WorksetAdder(javaProject,
+ workingSets));
+ }
+ }
+ }
+ };
+
+ ResourcesPlugin.getWorkspace().run(workspaceRunnable, monitor);
+ }
+
+ /**
+ * Adds default directories to the project.
+ *
+ * @param project The Java Project to update.
+ * @param parentFolder The path of the parent folder. Must end with a
+ * separator.
+ * @param folders Folders to be added.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to create the directories in
+ * the project.
+ */
+ private void addDefaultDirectories(IProject project, String parentFolder,
+ String[] folders, IProgressMonitor monitor) throws CoreException {
+ for (String name : folders) {
+ if (name.length() > 0) {
+ IFolder folder = project.getFolder(parentFolder + name);
+ if (!folder.exists()) {
+ folder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds the manifest to the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @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.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addManifest(IProject project, Map<String, Object> parameters,
+ Map<String, String> dictionary, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // get IFile to the manifest and check if it's not already there.
+ IFile file = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (!file.exists()) {
+
+ // Read manifest template
+ String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
+
+ // Replace all keyword parameters
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ if (manifestTemplate == null) {
+ // Inform the user there will be not manifest.
+ AdtPlugin.logAndPrintError(null, "Create Project" /*TAG*/,
+ "Failed to generate the Android manifest. Missing template %s",
+ TEMPLATE_MANIFEST);
+ // Abort now, there's no need to continue
+ return;
+ }
+
+ if (parameters.containsKey(PARAM_ACTIVITY)) {
+ // now get the activity template
+ String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
+
+ // If the activity name doesn't contain any dot, it's in the form
+ // "ClassName" and we need to expand it to ".ClassName" in the XML.
+ String name = (String) parameters.get(PARAM_ACTIVITY);
+ if (name.indexOf('.') == -1) {
+ // Duplicate the parameters map to avoid changing the caller
+ parameters = new HashMap<String, Object>(parameters);
+ parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$
+ }
+
+ // Replace all keyword parameters to make main activity.
+ String activities = replaceParameters(activityTemplate, parameters);
+
+ // set the intent.
+ String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
+
+ if (activities != null) {
+ if (intent != null) {
+ // 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, ""); //$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);
+ if (usesLibrary != null) {
+ manifestTemplate = manifestTemplate.replaceAll(
+ PH_TEST_USES_LIBRARY, usesLibrary);
+ }
+
+ // Set the instrumentation element needed by the test project
+ String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
+ if (instru != null) {
+ 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);
+ if (usesSdkTemplate != null) {
+ String usesSdk = replaceParameters(usesSdkTemplate, parameters);
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
+ }
+ } else {
+ manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
+ }
+
+ // Reformat the file according to the user's formatting settings
+ manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the string resource file.
+ *
+ * @param project The Java Project to update.
+ * @param strings The list of strings to be added to the string file.
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addStringDictionaryFile(IProject project,
+ Map<String, String> strings, IProgressMonitor monitor)
+ throws CoreException, IOException {
+
+ // create the IFile object and check if the file doesn't already exist.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE);
+ if (!file.exists()) {
+ // get the Strings.xml template
+ String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
+
+ // get the template for one string
+ String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
+
+ // get all the string names
+ Set<String> stringNames = strings.keySet();
+
+ // loop on it and create the string definitions
+ StringBuilder stringNodes = new StringBuilder();
+ for (String key : stringNames) {
+ // get the value from the key
+ String value = strings.get(key);
+
+ // Escape values if necessary
+ value = ValueXmlHelper.escapeResourceString(value);
+
+ // place them in the template
+ String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
+ stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
+
+ // append to the other string
+ if (stringNodes.length() > 0) {
+ stringNodes.append('\n');
+ }
+ stringNodes.append(stringDef);
+ }
+
+ // put the string nodes in the Strings.xml template
+ stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
+ stringNodes.toString());
+
+ // reformat the file according to the user's formatting settings
+ stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
+
+ // write the file as UTF-8
+ InputStream stream = new ByteArrayInputStream(
+ stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
+ file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /** Reformats the given contents with the current formatting settings */
+ private String reformat(XmlFormatStyle style, String contents) {
+ if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
+ return EclipseXmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
+ null /*lineSeparator*/);
+ } else {
+ return contents;
+ }
+ }
+
+ /**
+ * Adds default application icon to the project.
+ *
+ * @param project The Java Project to update.
+ * @param legacy whether we're running in legacy mode (no density support)
+ * @param monitor An existing monitor.
+ * @throws CoreException if the method fails to update the project.
+ */
+ private void addIcon(IProject project, boolean legacy, IProgressMonitor monitor)
+ throws CoreException {
+ if (legacy) { // density support
+ // do medium density icon only, in the default drawable folder.
+ IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+ } else {
+ // do all 4 icons.
+ IFile file;
+
+ // extra high density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_XHDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_XHDPI), monitor);
+ }
+
+ // high density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor);
+ }
+
+ // medium density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor);
+ }
+
+ // low density
+ file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP
+ + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON);
+ if (!file.exists()) {
+ addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor);
+ }
+ }
+ }
+
+ /**
+ * Creates a file from a data source.
+ * @param dest the file to write
+ * @param source the content of the file.
+ * @param monitor the progress monitor
+ * @throws CoreException
+ */
+ private void addFile(IFile dest, byte[] source, IProgressMonitor monitor) throws CoreException {
+ if (source != null) {
+ // Save in the project
+ InputStream stream = new ByteArrayInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Creates the package folder and copies the sample code in the project.
+ *
+ * @param project The Java Project to update.
+ * @param parameters Template Parameters.
+ * @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.
+ * @throws IOException if the method fails to create the files in the
+ * project.
+ */
+ private void addSampleCode(IProject project, String sourceFolder,
+ 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);
+
+ Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters);
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, ""); //$NON-NLS-1$
+
+ if (activityName != null) {
+
+ String resourcePackageClass = null;
+
+ // An activity name can be of the form ".package.Class", ".Class" or FQDN.
+ // The initial dot is ignored, as it is always added later in the templates.
+ int lastDotIndex = activityName.lastIndexOf('.');
+
+ if (lastDotIndex != -1) {
+
+ // Resource class
+ if (lastDotIndex > 0) {
+ resourcePackageClass = packageName + '.' + SdkConstants.FN_RESOURCE_BASE;
+ }
+
+ // Package name
+ if (activityName.startsWith(".")) { //$NON-NLS-1$
+ packageName += activityName.substring(0, lastDotIndex);
+ } else {
+ packageName = activityName.substring(0, lastDotIndex);
+ }
+
+ // Activity Class name
+ activityName = activityName.substring(lastDotIndex + 1);
+ }
+
+ java_activity_parameters.put(PARAM_ACTIVITY, activityName);
+ java_activity_parameters.put(PARAM_PACKAGE, packageName);
+ if (resourcePackageClass != null) {
+ String importResourceClass = "\nimport " + resourcePackageClass + ";"; //$NON-NLS-1$ // $NON-NLS-2$
+ java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass);
+ }
+ }
+
+ String[] components = packageName.split(AdtConstants.RE_DOT);
+ for (String component : components) {
+ pkgFolder = pkgFolder.getFolder(component);
+ if (!pkgFolder.exists()) {
+ pkgFolder.create(true /* force */, true /* local */,
+ new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ if (activityName != null) {
+ // create the main activity Java file
+ String activityJava = activityName + SdkConstants.DOT_JAVA;
+ IFile file = pkgFolder.getFile(activityJava);
+ if (!file.exists()) {
+ copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
+ }
+
+ // create the layout file (if we're creating an
+ IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
+ file = layoutfolder.getFile(MAIN_LAYOUT_XML);
+ if (!file.exists()) {
+ copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
+ dictionary.put(STRING_HELLO_WORLD, String.format("Hello World, %1$s!",
+ activityName));
+ }
+ }
+ }
+
+ private void copySampleCode(IProject project, File sampleDir,
+ Map<String, Object> parameters, Map<String, String> dictionary,
+ IProgressMonitor monitor) throws CoreException {
+ // Copy the sampleDir into the project directory recursively
+ IFileSystem fileSystem = EFS.getLocalFileSystem();
+ IFileStore sourceDir = new ReadWriteFileStore(
+ fileSystem.getStore(sampleDir.toURI()));
+ IFileStore destDir = new ReadWriteFileStore(
+ fileSystem.getStore(AdtUtils.getAbsolutePath(project)));
+ sourceDir.copy(destDir, EFS.OVERWRITE, null);
+ }
+
+ /**
+ * In a sample we never duplicate source files as read-only.
+ * This creates a store that read files attributes and doesn't set the r-o flag.
+ */
+ private static class ReadWriteFileStore extends FileStoreAdapter {
+
+ public ReadWriteFileStore(IFileStore store) {
+ super(store);
+ }
+
+ // Override when reading attributes
+ @Override
+ public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
+ IFileInfo info = super.fetchInfo(options, monitor);
+ info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
+ return info;
+ }
+
+ // Override when writing attributes
+ @Override
+ public void putInfo(IFileInfo info, int options, IProgressMonitor storeMonitor)
+ throws CoreException {
+ info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, false);
+ super.putInfo(info, options, storeMonitor);
+ }
+
+ @Deprecated
+ @Override
+ public IFileStore getChild(IPath path) {
+ IFileStore child = super.getChild(path);
+ if (!(child instanceof ReadWriteFileStore)) {
+ child = new ReadWriteFileStore(child);
+ }
+ return child;
+ }
+
+ @Override
+ public IFileStore getChild(String name) {
+ return new ReadWriteFileStore(super.getChild(name));
+ }
+ }
+
+ /**
+ * Adds a file to the root of the project
+ * @param project the project to add the file to.
+ * @param destName the name to write the file as
+ * @param source the file to add. It'll keep the same filename once copied into the project.
+ * @param monitor the monitor to report progress to
+ * @throws FileNotFoundException if the file to be added does not exist
+ * @throws CoreException if writing the file does not work
+ */
+ public static void addLocalFile(IProject project, File source, String destName,
+ IProgressMonitor monitor) throws FileNotFoundException, CoreException {
+ IFile dest = project.getFile(destName);
+ if (dest.exists() == false) {
+ FileInputStream stream = new FileInputStream(source);
+ dest.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the given folder to the project's class path.
+ *
+ * @param javaProject The Java Project to update.
+ * @param sourceFolders Template Parameters.
+ * @param monitor An existing monitor.
+ * @throws JavaModelException if the classpath could not be set.
+ */
+ private void setupSourceFolders(IJavaProject javaProject, String[] sourceFolders,
+ IProgressMonitor monitor) throws JavaModelException {
+ IProject project = javaProject.getProject();
+
+ // get the list of entries.
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+
+ // remove the project as a source folder (This is the default)
+ entries = removeSourceClasspath(entries, project);
+
+ // add the source folders.
+ for (String sourceFolder : sourceFolders) {
+ IFolder srcFolder = project.getFolder(sourceFolder);
+
+ // remove it first in case.
+ entries = removeSourceClasspath(entries, srcFolder);
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newSourceEntry(srcFolder.getFullPath()));
+ }
+
+ javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
+ }
+
+
+ /**
+ * Removes the corresponding source folder from the class path entries if
+ * found.
+ *
+ * @param entries The class path entries to read. A copy will be returned.
+ * @param folder The parent source folder to remove.
+ * @return A new class path entries array.
+ */
+ private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
+ if (folder == null) {
+ return entries;
+ }
+ IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
+ int n = entries.length;
+ for (int i = n - 1; i >= 0; i--) {
+ if (entries[i].equals(source)) {
+ IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
+ if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
+ if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
+ n--;
+ entries = newEntries;
+ }
+ }
+ return entries;
+ }
+
+
+ /**
+ * Copies the given file from our resource folder to the new project.
+ * Expects the file to the US-ASCII or UTF-8 encoded.
+ *
+ * @throws CoreException from IFile if failing to create the new file.
+ * @throws MalformedURLException from URL if failing to interpret the URL.
+ * @throws FileNotFoundException from RandomAccessFile.
+ * @throws IOException from RandomAccessFile.length() if can't determine the
+ * length.
+ */
+ private void copyFile(String resourceFilename, IFile destFile,
+ Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
+ throws CoreException, IOException {
+
+ // Read existing file.
+ String template = AdtPlugin.readEmbeddedTextFile(
+ TEMPLATES_DIRECTORY + resourceFilename);
+
+ // Replace all keyword parameters
+ template = replaceParameters(template, parameters);
+
+ if (reformat) {
+ // Guess the formatting style based on the file location
+ XmlFormatStyle style = EclipseXmlPrettyPrinter
+ .getForFile(destFile.getProjectRelativePath());
+ if (style != null) {
+ template = reformat(style, template);
+ }
+ }
+
+ // Save in the project as UTF-8
+ InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
+ destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
+ }
+
+ /**
+ * Replaces placeholders found in a string with values.
+ *
+ * @param str the string to search for placeholders.
+ * @param parameters a map of <placeholder, Value> to search for in the string
+ * @return A new String object with the placeholder replaced by the values.
+ */
+ private String replaceParameters(String str, Map<String, Object> parameters) {
+
+ if (parameters == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null parameter map. String: '%s'", str); //$NON-NLS-1$
+ return str;
+ } else if (str == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null template string"); //$NON-NLS-1$
+ return str;
+ }
+
+ for (Entry<String, Object> entry : parameters.entrySet()) {
+ if (entry != null && entry.getValue() instanceof String) {
+ Object value = entry.getValue();
+ if (value == null) {
+ AdtPlugin.log(IStatus.ERROR,
+ "NPW replace parameters: null value for key '%s' in template '%s'", //$NON-NLS-1$
+ entry.getKey(),
+ str);
+ } else {
+ str = str.replaceAll(entry.getKey(), (String) value);
+ }
+ }
+ }
+
+ return str;
+ }
+
+ private static class WorksetAdder implements Runnable {
+ private final IJavaProject mProject;
+ private final IWorkingSet[] mWorkingSets;
+
+ private WorksetAdder(IJavaProject project, IWorkingSet[] workingSets) {
+ mProject = project;
+ mWorkingSets = workingSets;
+ }
+
+ @Override
+ public void run() {
+ if (mWorkingSets.length > 0 && mProject != null
+ && mProject.exists()) {
+ PlatformUI.getWorkbench().getWorkingSetManager()
+ .addToWorkingSets(mProject, mWorkingSets);
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
new file mode 100644
index 000000000..ff03b338f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbench;
+
+import java.io.File;
+
+
+/**
+ * A "New Android Project" Wizard.
+ * <p/>
+ * 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.
+ * <p/>
+ * Do not derive from this class.
+ */
+public class NewProjectWizard extends Wizard implements INewWizard {
+ private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
+
+ private NewProjectWizardState mValues;
+ private ProjectNamePage mNamePage;
+ private SdkSelectionPage mSdkPage;
+ private SampleSelectionPage mSamplePage;
+ private ApplicationInfoPage mPropertiesPage;
+ private final Mode mMode;
+ private IStructuredSelection mSelection;
+
+ /** Constructs a new wizard default project wizard */
+ public NewProjectWizard() {
+ this(Mode.ANY);
+ }
+
+ protected NewProjectWizard(Mode mode) {
+ mMode = mode;
+ switch (mMode) {
+ case SAMPLE:
+ setWindowTitle("New Android Sample Project");
+ break;
+ case TEST:
+ setWindowTitle("New Android Test Project");
+ break;
+ default:
+ setWindowTitle("New Android Project");
+ break;
+ }
+ }
+
+ @Override
+ public void addPages() {
+ mValues = new NewProjectWizardState(mMode);
+
+ if (mMode != Mode.SAMPLE) {
+ mNamePage = new ProjectNamePage(mValues);
+
+ if (mSelection != null) {
+ mNamePage.init(mSelection, AdtUtils.getActivePart());
+ }
+
+ addPage(mNamePage);
+ }
+
+ if (mMode == Mode.TEST) {
+ addPage(new TestTargetPage(mValues));
+ }
+
+ mSdkPage = new SdkSelectionPage(mValues);
+ addPage(mSdkPage);
+
+ if (mMode != Mode.TEST) {
+ // Sample projects can be created when entering the new/existing wizard, or
+ // the sample wizard
+ mSamplePage = new SampleSelectionPage(mValues);
+ addPage(mSamplePage);
+ }
+
+ if (mMode != Mode.SAMPLE) {
+ // Project properties are entered in all project types except sample projects
+ mPropertiesPage = new ApplicationInfoPage(mValues);
+ addPage(mPropertiesPage);
+ }
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ mSelection = selection;
+
+ setHelpAvailable(false); // TODO have help
+ ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
+ setDefaultPageImageDescriptor(desc);
+
+ // Trigger a check to see if the SDK needs to be reloaded (which will
+ // invoke onSdkLoaded asynchronously as needed).
+ AdtPlugin.getDefault().refreshSdk();
+ }
+
+ @Override
+ public boolean performFinish() {
+ File file = new File(AdtPlugin.getOsSdkFolder(), OS_SDK_TOOLS_LIB_FOLDER + File.separator
+ + FN_PROJECT_PROGUARD_FILE);
+ if (!file.exists()) {
+ AdtPlugin.displayError("Tools Out of Date?",
+ String.format("It looks like you do not have the latest version of the "
+ + "SDK Tools installed. Make sure you update via the SDK Manager "
+ + "first. (Could not find %1$s)", file.getPath()));
+ return false;
+ }
+
+ NewProjectCreator creator = new NewProjectCreator(mValues, getContainer());
+ if (!(creator.createAndroidProjects())) {
+ return false;
+ }
+
+ // Open the default Java Perspective
+ OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction();
+ action.run();
+ return true;
+ }
+
+ @Override
+ public IWizardPage getNextPage(IWizardPage page) {
+ if (page == mNamePage) {
+ // Skip the test target selection page unless creating a test project
+ if (mValues.mode != Mode.TEST) {
+ return mSdkPage;
+ }
+ } else if (page == mSdkPage) {
+ if (mValues.mode == Mode.SAMPLE) {
+ return mSamplePage;
+ } else if (mValues.mode != Mode.TEST) {
+ return mPropertiesPage;
+ } else {
+ // Done with wizard when creating from existing or creating test projects
+ return null;
+ }
+ } else if (page == mSamplePage) {
+ // Nothing more to be entered for samples
+ return null;
+ }
+
+ return super.getNextPage(page);
+ }
+
+ /**
+ * Returns the package name currently set by the wizard
+ *
+ * @return the current package name, or null
+ */
+ public String getPackageName() {
+ return mValues.packageName;
+ }
+
+ // TBD: Call setDialogSettings etc to store persistent state between wizard invocations.
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java
new file mode 100644
index 000000000..06c0300b7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.ManifestData.Activity;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.android.utils.Pair;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.ui.IWorkingSet;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link NewProjectWizardState} holds the state used by the various pages
+ * in the {@link NewProjectWizard} and its variations, and it can also be used
+ * to pass project information to the {@link NewProjectCreator}.
+ */
+public class NewProjectWizardState {
+ /** The mode to run the wizard in: creating test, or sample, or plain project */
+ public Mode mode;
+
+ /**
+ * If true, the project should be created from an existing codebase (pointed
+ * to by the {@link #projectLocation} or in the case of sample projects, the
+ * {@link #chosenSample}. Otherwise, create a brand new project from scratch.
+ */
+ public boolean useExisting;
+
+ /**
+ * Whether new projects should be created into the default project location
+ * (e.g. in the Eclipse workspace) or not
+ */
+ public boolean useDefaultLocation = true;
+
+ /** The build target SDK */
+ public IAndroidTarget target;
+ /** True if the user has manually modified the target */
+ public boolean targetModifiedByUser;
+
+ /** The location to store projects into */
+ public File projectLocation = new File(Platform.getLocation().toOSString());
+ /** True if the project location name has been manually edited by the user */
+ public boolean projectLocationModifiedByUser;
+
+ /** The name of the project */
+ public String projectName = ""; //$NON-NLS-1$
+ /** True if the project name has been manually edited by the user */
+ public boolean projectNameModifiedByUser;
+
+ /** The application name */
+ public String applicationName;
+ /** True if the application name has been manually edited by the user */
+ public boolean applicationNameModifiedByUser;
+
+ /** The package path */
+ public String packageName;
+ /** True if the package name has been manually edited by the user */
+ public boolean packageNameModifiedByUser;
+
+ /** True if a new activity should be created */
+ public boolean createActivity;
+
+ /** The name of the new activity to be created */
+ public String activityName;
+ /** True if the activity name has been manually edited by the user */
+ public boolean activityNameModifiedByUser;
+
+ /** The minimum SDK version to use with the project (may be null or blank) */
+ public String minSdk;
+ /** True if the minimum SDK version has been manually edited by the user */
+ public boolean minSdkModifiedByUser;
+ /**
+ * A list of paths to each of the available samples for the current SDK.
+ * The pair is (String: sample display name => File: sample directory).
+ * Note we want a list, not a map since we might have duplicates.
+ * */
+ public List<Pair<String, File>> samples = new ArrayList<Pair<String, File>>();
+ /** Path to the currently chosen sample */
+ public File chosenSample;
+
+ /** The name of the source folder, relative to the project root */
+ public String sourceFolder = SdkConstants.FD_SOURCES;
+ /** The set of chosen working sets to use when creating the project */
+ public IWorkingSet[] workingSets = new IWorkingSet[0];
+
+ /**
+ * A reference to a different project that the current test project will be
+ * testing.
+ */
+ public IProject testedProject;
+ /**
+ * If true, this test project should be testing itself, otherwise it will be
+ * testing the project pointed to by {@link #testedProject}.
+ */
+ public boolean testingSelf;
+
+ // NOTE: These apply only to creating paired projects; when isTest is true
+ // we're using
+ // the normal fields above
+ /**
+ * If true, create a test project along with this plain project which will
+ * be testing the plain project. (This flag only applies when creating
+ * normal projects.)
+ */
+ public boolean createPairProject;
+ /**
+ * The application name of the test application (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testApplicationName;
+ /**
+ * True if the testing application name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testApplicationNameModified;
+ /**
+ * The package name of the test application (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testPackageName;
+ /**
+ * True if the testing package name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testPackageModified;
+ /**
+ * The project name of the test project (only applies when
+ * {@link #createPairProject} is true)
+ */
+ public String testProjectName;
+ /**
+ * True if the testing project name has been modified by the user (only
+ * applies when {@link #createPairProject} is true)
+ */
+ public boolean testProjectModified;
+ /** Package name of the tested app */
+ public String testTargetPackageName;
+
+ /**
+ * Copy project into workspace? This flag only applies when importing
+ * projects (creating projects from existing source)
+ */
+ public boolean copyIntoWorkspace;
+
+ /**
+ * List of projects to be imported. Null if not importing projects.
+ */
+ @Nullable
+ public List<ImportedProject> importProjects;
+
+ /**
+ * Creates a new {@link NewProjectWizardState}
+ *
+ * @param mode the mode to run the wizard in
+ */
+ public NewProjectWizardState(Mode mode) {
+ this.mode = mode;
+ if (mode == Mode.SAMPLE) {
+ useExisting = true;
+ } else if (mode == Mode.TEST) {
+ createActivity = false;
+ }
+ }
+
+ /**
+ * Extract information (package name, application name, minimum SDK etc) from
+ * the given Android project.
+ *
+ * @param path the path to the project to extract information from
+ */
+ public void extractFromAndroidManifest(Path path) {
+ String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
+ if (!(new File(osPath).exists())) {
+ return;
+ }
+
+ ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
+ if (manifestData == null) {
+ return;
+ }
+
+ String newPackageName = null;
+ Activity activity = null;
+ String newActivityName = null;
+ String minSdkVersion = null;
+ try {
+ newPackageName = manifestData.getPackage();
+ minSdkVersion = manifestData.getMinSdkVersionString();
+
+ // try to get the first launcher activity. If none, just take the first activity.
+ activity = manifestData.getLauncherActivity();
+ if (activity == null) {
+ Activity[] activities = manifestData.getActivities();
+ if (activities != null && activities.length > 0) {
+ activity = activities[0];
+ }
+ }
+ } catch (Exception e) {
+ // ignore exceptions
+ }
+
+ if (newPackageName != null && newPackageName.length() > 0) {
+ packageName = newPackageName;
+ }
+
+ if (activity != null) {
+ newActivityName = AndroidManifest.extractActivityName(activity.getName(),
+ newPackageName);
+ }
+
+ if (newActivityName != null && newActivityName.length() > 0) {
+ activityName = newActivityName;
+ // we are "importing" an existing activity, not creating a new one
+ createActivity = false;
+
+ // If project name and application names are empty, use the activity
+ // name as a default. If the activity name has dots, it's a part of a
+ // package specification and only the last identifier must be used.
+ if (newActivityName.indexOf('.') != -1) {
+ String[] ids = newActivityName.split(AdtConstants.RE_DOT);
+ newActivityName = ids[ids.length - 1];
+ }
+ if (projectName == null || projectName.length() == 0 ||
+ !projectNameModifiedByUser) {
+ projectName = newActivityName;
+ projectNameModifiedByUser = false;
+ }
+ if (applicationName == null || applicationName.length() == 0 ||
+ !applicationNameModifiedByUser) {
+ applicationNameModifiedByUser = false;
+ applicationName = newActivityName;
+ }
+ } else {
+ activityName = ""; //$NON-NLS-1$
+
+ // 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 (newPackageName != null && newPackageName.length() > 0) {
+ // Package name is a java identifier, so it's most suitable for
+ // an application name.
+
+ if (applicationName == null || applicationName.length() == 0 ||
+ !applicationNameModifiedByUser) {
+ applicationName = newPackageName;
+ }
+
+ // For the project name, remove any dots
+ newPackageName = newPackageName.replace('.', '_');
+ if (projectName == null || projectName.length() == 0 ||
+ !projectNameModifiedByUser) {
+ projectName = newPackageName;
+ }
+
+ }
+ }
+
+ if (mode == Mode.ANY && useExisting) {
+ updateSdkTargetToMatchProject(path.toFile());
+ }
+
+ minSdk = minSdkVersion;
+ minSdkModifiedByUser = false;
+ }
+
+ /**
+ * 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.
+ */
+ public void updateSdkTargetToMatchMinSdkVersion() {
+ IAndroidTarget currentTarget = target;
+ if (currentTarget != null && currentTarget.getVersion().equals(minSdk)) {
+ return;
+ }
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ IAndroidTarget[] targets = sdk.getTargets();
+ for (IAndroidTarget t : targets) {
+ if (t.getVersion().equals(minSdk)) {
+ target = t;
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the SDK to reflect the SDK required by the project at the given
+ * location
+ *
+ * @param location the location of the project
+ */
+ public void updateSdkTargetToMatchProject(File location) {
+ // Select the target matching the manifest's sdk or build properties, if any
+ IAndroidTarget foundTarget = null;
+ // This is the target currently in the UI
+ IAndroidTarget currentTarget = target;
+ String projectPath = location.getPath();
+
+ // If there's a current target defined, we do not allow to change it when
+ // operating in the create-from-sample mode -- since the available sample list
+ // is tied to the current target, so changing it would invalidate the project we're
+ // trying to load in the first place.
+ if (!targetModifiedByUser) {
+ ProjectProperties p = ProjectProperties.load(projectPath,
+ PropertyType.PROJECT);
+ if (p != null) {
+ String v = p.getProperty(ProjectProperties.PROPERTY_TARGET);
+ IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v);
+ // We can change the current target if:
+ // - we found a new desired target
+ // - there is no current target
+ // - or the current target can't run the desired target
+ if (desiredTarget != null &&
+ (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) {
+ foundTarget = desiredTarget;
+ }
+ }
+
+ Sdk sdk = Sdk.getCurrent();
+ IAndroidTarget[] targets = null;
+ if (sdk != null) {
+ targets = sdk.getTargets();
+ }
+ if (targets == null) {
+ targets = new IAndroidTarget[0];
+ }
+
+ if (foundTarget == null && minSdk != null) {
+ // Otherwise try to match the requested min-sdk-version if we find an
+ // exact match, regardless of the currently selected target.
+ for (IAndroidTarget existingTarget : targets) {
+ if (existingTarget != null &&
+ existingTarget.getVersion().equals(minSdk)) {
+ foundTarget = existingTarget;
+ break;
+ }
+ }
+ }
+
+ if (foundTarget == null) {
+ // Or last attempt, try to match a sample project location and use it
+ // if we find an exact match, regardless of the currently selected target.
+ for (IAndroidTarget existingTarget : targets) {
+ if (existingTarget != null &&
+ projectPath.startsWith(existingTarget.getLocation())) {
+ foundTarget = existingTarget;
+ break;
+ }
+ }
+ }
+ }
+
+ if (foundTarget != null) {
+ target = foundTarget;
+ }
+ }
+
+ /**
+ * Type of project being offered/created by the wizard
+ */
+ public enum Mode {
+ /** Create a sample project. Testing options are not presented. */
+ SAMPLE,
+
+ /**
+ * Create a test project, either testing itself or some other project.
+ * Note that even if in the {@link #ANY} mode, a test project can be
+ * created as a *paired* project with the main project, so this flag
+ * only means that we are creating *just* a test project
+ */
+ TEST,
+
+ /**
+ * Create an Android project, which can be a plain project, optionally
+ * with a paired test project, or a sample project (the first page
+ * contains toggles for choosing which
+ */
+ ANY;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java
new file mode 100644
index 000000000..6b6a4c29e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewSampleProjectWizard.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+/**
+ * A "New Sample Android Project" Wizard.
+ * <p/>
+ * This displays the new project wizard pre-configured for samples only.
+ */
+public class NewSampleProjectWizard extends NewProjectWizard {
+ /**
+ * Creates a new wizard for creating a sample Android project
+ */
+ public NewSampleProjectWizard() {
+ super(Mode.SAMPLE);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java
new file mode 100644
index 000000000..e0959f4db
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectWizard.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+/**
+ * A "New Test Android Project" Wizard.
+ * <p/>
+ * This is really the {@link NewProjectWizard} that only displays the "test project" pages.
+ */
+public class NewTestProjectWizard extends NewProjectWizard {
+ /**
+ * Creates a new wizard for creating an Android Test Project
+ */
+ public NewTestProjectWizard() {
+ super(Mode.TEST);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
new file mode 100644
index 000000000..d04ea897f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
+import static com.android.ide.eclipse.adt.AdtUtils.capitalize;
+import static com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage.ACTIVITY_NAME_SUFFIX;
+import static com.android.utils.SdkUtils.stripWhitespace;
+
+import com.android.SdkConstants;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.ManifestData.Activity;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.VersionCheck;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+
+import org.eclipse.core.filesystem.URIUtil;
+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.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+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.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkingSet;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Locale;
+
+/**
+ * Initial page shown when creating projects which asks for the project name,
+ * the the location of the project, working sets, etc.
+ */
+public class ProjectNamePage extends WizardPage implements SelectionListener, ModifyListener {
+ private final NewProjectWizardState mValues;
+ /** Flag used when setting button/text state manually to ignore listener updates */
+ private boolean mIgnore;
+ /** Last user-browsed location, static so that it be remembered for the whole session */
+ private static String sCustomLocationOsPath = ""; //$NON-NLS-1$
+ private static boolean sAutoComputeCustomLocation = true;
+
+ private Text mProjectNameText;
+ private Text mLocationText;
+ private Button mCreateSampleRadioButton;
+ private Button mCreateNewButton;
+ private Button mUseDefaultCheckBox;
+ private Button mBrowseButton;
+ private Label mLocationLabel;
+ private WorkingSetGroup mWorkingSetGroup;
+ /**
+ * Whether we've made sure the Tools are up to date (enough that all the
+ * resources required by the New Project wizard are present -- we don't
+ * necessarily check for newer versions than that here; that's done by
+ * {@link VersionCheck}, though that check doesn't <b>enforce</b> an update
+ * since it needs to allow the user to proceed to access the SDK manager
+ * etc.)
+ */
+ private boolean mCheckedSdkUptodate;
+
+ /**
+ * Create the wizard.
+ * @param values current wizard state
+ */
+ ProjectNamePage(NewProjectWizardState values) {
+ super("projectNamePage"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Create Android Project");
+ setDescription("Select project name and type of project");
+ mWorkingSetGroup = new WorkingSetGroup();
+ setWorkingSets(new IWorkingSet[0]);
+ }
+
+ void init(IStructuredSelection selection, IWorkbenchPart activePart) {
+ setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
+ }
+
+ /**
+ * Create contents of the wizard.
+ * @param parent the parent to add the page to
+ */
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(3, false));
+
+ Label nameLabel = new Label(container, SWT.NONE);
+ nameLabel.setText("Project Name:");
+
+ mProjectNameText = new Text(container, SWT.BORDER);
+ mProjectNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mProjectNameText.addModifyListener(this);
+
+ if (mValues.mode != Mode.TEST) {
+ mCreateNewButton = new Button(container, SWT.RADIO);
+ mCreateNewButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mCreateNewButton.setText("Create new project in workspace");
+ mCreateNewButton.addSelectionListener(this);
+
+ // TBD: Should we hide this completely, and make samples something you only invoke
+ // from the "New Sample Project" wizard?
+ mCreateSampleRadioButton = new Button(container, SWT.RADIO);
+ mCreateSampleRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
+ 3, 1));
+ mCreateSampleRadioButton.setText("Create project from existing sample");
+ mCreateSampleRadioButton.addSelectionListener(this);
+ }
+
+ Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ separator.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1));
+
+ mUseDefaultCheckBox = new Button(container, SWT.CHECK);
+ mUseDefaultCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mUseDefaultCheckBox.setText("Use default location");
+ mUseDefaultCheckBox.addSelectionListener(this);
+
+ mLocationLabel = new Label(container, SWT.NONE);
+ mLocationLabel.setText("Location:");
+
+ mLocationText = new Text(container, SWT.BORDER);
+ mLocationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mLocationText.addModifyListener(this);
+
+ mBrowseButton = new Button(container, SWT.NONE);
+ mBrowseButton.setText("Browse...");
+ mBrowseButton.addSelectionListener(this);
+
+ Composite group = mWorkingSetGroup.createControl(container);
+ group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1));
+
+ setControl(container);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ try {
+ mIgnore = true;
+ if (mValues.projectName != null) {
+ mProjectNameText.setText(mValues.projectName);
+ mProjectNameText.setFocus();
+ }
+ if (mValues.mode == Mode.ANY || mValues.mode == Mode.TEST) {
+ if (mValues.useExisting) {
+ assert false; // This is now handled by the separate import wizard
+ } else if (mCreateNewButton != null) {
+ mCreateNewButton.setSelection(true);
+ }
+ } else if (mValues.mode == Mode.SAMPLE) {
+ mCreateSampleRadioButton.setSelection(true);
+ }
+ if (mValues.projectLocation != null) {
+ mLocationText.setText(mValues.projectLocation.getPath());
+ }
+ mUseDefaultCheckBox.setSelection(mValues.useDefaultLocation);
+ updateLocationState();
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mProjectNameText) {
+ onProjectFieldModified();
+ if (!mValues.useDefaultLocation && !mValues.projectLocationModifiedByUser) {
+ updateLocationPathField(null);
+ }
+ } else if (source == mLocationText) {
+ mValues.projectLocationModifiedByUser = true;
+ if (!mValues.useDefaultLocation) {
+ File f = new File(mLocationText.getText().trim());
+ mValues.projectLocation = f;
+ if (f.exists() && f.isDirectory() && !f.equals(mValues.projectLocation)) {
+ updateLocationPathField(mValues.projectLocation.getPath());
+ }
+ }
+ }
+
+ validatePage();
+ }
+
+ private void onProjectFieldModified() {
+ mValues.projectName = mProjectNameText.getText().trim();
+ mValues.projectNameModifiedByUser = true;
+
+ if (!mValues.applicationNameModifiedByUser) {
+ mValues.applicationName = capitalize(mValues.projectName);
+ if (!mValues.testApplicationNameModified) {
+ mValues.testApplicationName =
+ ApplicationInfoPage.suggestTestApplicationName(mValues.applicationName);
+ }
+ }
+ if (!mValues.activityNameModifiedByUser) {
+ String name = capitalize(mValues.projectName);
+ mValues.activityName = stripWhitespace(name) + ACTIVITY_NAME_SUFFIX;
+ }
+ if (!mValues.testProjectModified) {
+ mValues.testProjectName =
+ ApplicationInfoPage.suggestTestProjectName(mValues.projectName);
+ }
+ if (!mValues.projectLocationModifiedByUser) {
+ updateLocationPathField(null);
+ }
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+
+ if (source == mCreateNewButton && mCreateNewButton != null
+ && mCreateNewButton.getSelection()) {
+ mValues.useExisting = false;
+ if (mValues.mode == Mode.SAMPLE) {
+ // Only reset the mode if we're toggling from sample back to create new
+ // or create existing. We can only come to the sample state when we're in
+ // ANY mode. (In particular, we don't want to switch to ANY if you're
+ // in test mode.
+ mValues.mode = Mode.ANY;
+ }
+ updateLocationState();
+ } else if (source == mCreateSampleRadioButton && mCreateSampleRadioButton.getSelection()) {
+ mValues.useExisting = true;
+ mValues.useDefaultLocation = true;
+ if (!mUseDefaultCheckBox.getSelection()) {
+ try {
+ mIgnore = true;
+ mUseDefaultCheckBox.setSelection(true);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ mValues.mode = Mode.SAMPLE;
+ updateLocationState();
+ } else if (source == mUseDefaultCheckBox) {
+ mValues.useDefaultLocation = mUseDefaultCheckBox.getSelection();
+ updateLocationState();
+ } else if (source == mBrowseButton) {
+ onOpenDirectoryBrowser();
+ }
+
+ validatePage();
+ }
+
+ /**
+ * 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 updateLocationState() {
+ boolean isNewProject = !mValues.useExisting;
+ boolean isCreateFromSample = mValues.mode == Mode.SAMPLE;
+ boolean useDefault = mValues.useDefaultLocation && !isCreateFromSample;
+ boolean locationEnabled = (!isNewProject || !useDefault) && !isCreateFromSample;
+
+ mUseDefaultCheckBox.setEnabled(isNewProject);
+ mLocationLabel.setEnabled(locationEnabled);
+ mLocationText.setEnabled(locationEnabled);
+ mBrowseButton.setEnabled(locationEnabled);
+
+ updateLocationPathField(null);
+ }
+
+ /**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existingDir = mLocationText.getText().trim();
+
+ // Disable the path if it doesn't exist
+ if (existingDir.length() == 0) {
+ existingDir = null;
+ } else {
+ File f = new File(existingDir);
+ if (!f.exists()) {
+ existingDir = null;
+ }
+ }
+
+ DirectoryDialog directoryDialog = new DirectoryDialog(mLocationText.getShell());
+ directoryDialog.setMessage("Browse for folder");
+ directoryDialog.setFilterPath(existingDir);
+ String dir = directoryDialog.open();
+
+ if (dir != null) {
+ updateLocationPathField(dir);
+ validatePage();
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ /**
+ * Returns the working sets to which the new project should be added.
+ *
+ * @return the selected working sets to which the new project should be added
+ */
+ private IWorkingSet[] getWorkingSets() {
+ return mWorkingSetGroup.getSelectedWorkingSets();
+ }
+
+ /**
+ * Sets the working sets to which the new project should be added.
+ *
+ * @param workingSets the initial selected working sets
+ */
+ private void setWorkingSets(IWorkingSet[] workingSets) {
+ assert workingSets != null;
+ mWorkingSetGroup.setWorkingSets(workingSets);
+ }
+
+ /**
+ * Updates the location directory path field.
+ * <br/>
+ * When custom user selection is enabled, use the absDir argument if not null and also
+ * save it internally. If absDir is null, restore the last saved absDir. 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 absDir.
+ *
+ * @param absDir A new absolute directory path or null to use the default.
+ */
+ private void updateLocationPathField(String absDir) {
+ boolean isNewProject = !mValues.useExisting || mValues.mode == Mode.SAMPLE;
+ boolean useDefault = mValues.useDefaultLocation;
+ boolean customLocation = !isNewProject || !useDefault;
+
+ if (!mIgnore) {
+ try {
+ mIgnore = true;
+ if (customLocation) {
+ if (absDir != null) {
+ // We get here if the user selected a directory with the "Browse" button.
+ // Disable auto-compute of the custom location unless the user selected
+ // the exact same path.
+ sAutoComputeCustomLocation = sAutoComputeCustomLocation &&
+ absDir.equals(sCustomLocationOsPath);
+ sCustomLocationOsPath = TextProcessor.process(absDir);
+ } else if (sAutoComputeCustomLocation ||
+ (!isNewProject && !new File(sCustomLocationOsPath).isDirectory())) {
+ // As a default import location, just suggest the home directory; the user
+ // needs to point to a project to import.
+ // TODO: Open file chooser automatically?
+ sCustomLocationOsPath = System.getProperty("user.home"); //$NON-NLS-1$
+ }
+ if (!mLocationText.getText().equals(sCustomLocationOsPath)) {
+ mLocationText.setText(sCustomLocationOsPath);
+ mValues.projectLocation = new File(sCustomLocationOsPath);
+ }
+ } else {
+ String value = Platform.getLocation().append(mValues.projectName).toString();
+ value = TextProcessor.process(value);
+ if (!mLocationText.getText().equals(value)) {
+ mLocationText.setText(value);
+ mValues.projectLocation = new File(value);
+ }
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ if (mValues.useExisting && mValues.projectLocation != null
+ && mValues.projectLocation.exists() && mValues.mode != Mode.SAMPLE) {
+ mValues.extractFromAndroidManifest(new Path(mValues.projectLocation.getPath()));
+ if (!mValues.projectNameModifiedByUser && mValues.projectName != null) {
+ try {
+ mIgnore = true;
+ mProjectNameText.setText(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+
+ // Validate project name -- unless we're creating a sample, in which case
+ // the user will get a chance to pick the name on the Sample page
+ if (mValues.mode != Mode.SAMPLE) {
+ status = validateProjectName(mValues.projectName);
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ IStatus validLocation = validateLocation();
+ if (validLocation != null) {
+ status = validLocation;
+ }
+ }
+
+ if (!mCheckedSdkUptodate) {
+ // Ensure that we have a recent enough version of the Tools that the right templates
+ // are available
+ File file = new File(AdtPlugin.getOsSdkFolder(), OS_SDK_TOOLS_LIB_FOLDER
+ + File.separator + FN_PROJECT_PROGUARD_FILE);
+ if (!file.exists()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("You do not have the latest version of the "
+ + "SDK Tools installed: Please update. (Missing %1$s)", file.getPath()));
+ } else {
+ mCheckedSdkUptodate = true;
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private IStatus validateLocation() {
+ if (mValues.mode == Mode.SAMPLE) {
+ // Samples are always created in the default directory
+ return null;
+ }
+
+ // Validate location
+ Path path = new Path(mValues.projectLocation.getPath());
+ if (!mValues.useExisting) {
+ if (!mValues.useDefaultLocation) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject handle = workspace.getRoot().getProject(mValues.projectName);
+ IStatus locationStatus = workspace.validateProjectLocationURI(handle, uri);
+ if (!locationStatus.isOK()) {
+ return locationStatus;
+ }
+ // 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 new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ } 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 != null && l.length != 0) {
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "The selected output directory is not empty.");
+ }
+ }
+ } else {
+ // Otherwise validate the path string is not empty
+ if (mValues.projectLocation.getPath().length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A directory name must be specified.");
+ }
+ File dest = path.toFile();
+ if (dest.exists()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "There is already a file or directory named \"%1$s\" in the selected location.",
+ mValues.projectName));
+ }
+ }
+ } else {
+ // Must be an existing directory
+ File f = path.toFile();
+ if (!f.isDirectory()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "An existing directory name must be specified.");
+ }
+
+ // Check there's an android manifest in the directory
+ String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString();
+ File manifestFile = new File(osPath);
+ if (!manifestFile.isFile()) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format(
+ "Choose a valid Android code directory\n" +
+ "(%1$s not found in %2$s.)",
+ SdkConstants.FN_ANDROID_MANIFEST_XML, f.getName()));
+ }
+
+ // Parse it and check the important fields.
+ ManifestData manifestData = AndroidManifestHelper.parseForData(osPath);
+ if (manifestData == null) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("File %1$s could not be parsed.", osPath));
+ }
+ String packageName = manifestData.getPackage();
+ if (packageName == null || packageName.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No package name defined in %1$s.", osPath));
+ }
+ Activity[] activities = manifestData.getActivities();
+ if (activities == null || activities.length == 0) {
+ // This is acceptable now as long as no activity needs to be
+ // created
+ if (mValues.createActivity) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("No activity name defined in %1$s.", osPath));
+ }
+ }
+
+ // If there's already a .project, tell the user to use import instead.
+ if (path.append(".project").toFile().exists()) { //$NON-NLS-1$
+ return new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "An Eclipse project already exists in this directory.\n" +
+ "Consider using File > Import > Existing Project instead.");
+ }
+ }
+
+ return null;
+ }
+
+ public static IStatus validateProjectName(String projectName) {
+ if (projectName == null || projectName.length() == 0) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Project name must be specified");
+ } else {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
+ if (!nameStatus.isOK()) {
+ return nameStatus;
+ } else {
+ // Note: the case-sensitiveness of the project name matters and can cause a
+ // conflict *later* when creating the project resource, so let's check it now.
+ for (IProject existingProj : workspace.getRoot().getProjects()) {
+ if (projectName.equalsIgnoreCase(existingProj.getName())) {
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "A project with that name already exists in the workspace");
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public IWizardPage getNextPage() {
+ // Sync working set data to the value object, since the WorkingSetGroup
+ // doesn't let us add listeners to do this lazily
+ mValues.workingSets = getWorkingSets();
+
+ return super.getNextPage();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java
new file mode 100644
index 000000000..197247083
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SampleSelectionPage.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.Pair;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+
+/** Page where the user can select a sample to "instantiate" */
+class SampleSelectionPage extends WizardPage implements SelectionListener, ModifyListener {
+ private final NewProjectWizardState mValues;
+ private boolean mIgnore;
+
+ private Table mTable;
+ private TableViewer mTableViewer;
+ private IAndroidTarget mCurrentSamplesTarget;
+ private Text mSampleProjectName;
+
+ /**
+ * Create the wizard.
+ */
+ SampleSelectionPage(NewProjectWizardState values) {
+ super("samplePage"); //$NON-NLS-1$
+ setTitle("Select Sample");
+ setDescription("Select which sample to create");
+ mValues = values;
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayout(new GridLayout(2, false));
+
+ mTableViewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
+ mTable = mTableViewer.getTable();
+ GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
+ gridData.heightHint = 300;
+ mTable.setLayoutData(gridData);
+ mTable.addSelectionListener(this);
+
+ setControl(container);
+
+ Label projectLabel = new Label(container, SWT.NONE);
+ projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ projectLabel.setText("Project Name:");
+
+ mSampleProjectName = new Text(container, SWT.BORDER);
+ mSampleProjectName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mSampleProjectName.addModifyListener(this);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ if (mValues.projectName != null) {
+ try {
+ mIgnore = true;
+ mSampleProjectName.setText(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ // Update samples list if the SDK target has changed (or if it hasn't yet
+ // been populated)
+ if (mCurrentSamplesTarget != mValues.target) {
+ mCurrentSamplesTarget = mValues.target;
+ updateSamples();
+ }
+
+ validatePage();
+ }
+ }
+
+ private void updateSamples() {
+ IBaseLabelProvider labelProvider = new ColumnLabelProvider() {
+ @Override
+ public Image getImage(Object element) {
+ return AdtPlugin.getAndroidLogo();
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Pair<?, ?>) {
+ Object name = ((Pair<?, ?>) element).getFirst();
+ return name.toString();
+ }
+ return element.toString(); // Fallback. Should not happen.
+ }
+ };
+
+ mTableViewer.setContentProvider(new ArrayContentProvider());
+ mTableViewer.setLabelProvider(labelProvider);
+
+ if (mValues.samples != null && mValues.samples.size() > 0) {
+ Object[] samples = mValues.samples.toArray();
+ mTableViewer.setInput(samples);
+
+ mTable.select(0);
+ selectSample(mValues.samples.get(0).getSecond());
+ extractNamesFromAndroidManifest();
+ }
+ }
+
+ private void selectSample(File sample) {
+ mValues.chosenSample = sample;
+ if (sample != null && !mValues.projectNameModifiedByUser) {
+ mValues.projectName = sample.getName();
+ if (SdkConstants.FD_SAMPLE.equals(mValues.projectName) &&
+ sample.getParentFile() != null) {
+ mValues.projectName = sample.getParentFile().getName() + '_' + mValues.projectName;
+ }
+ try {
+ mIgnore = true;
+ mSampleProjectName.setText(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ updatedProjectName();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ if (e.getSource() == mTable) {
+ extractNamesFromAndroidManifest();
+ int index = mTable.getSelectionIndex();
+ if (index >= 0) {
+ Object[] roots = (Object[]) mTableViewer.getInput();
+ selectSample(((Pair<String, File>) roots[index]).getSecond());
+ } else {
+ selectSample(null);
+ }
+ } else {
+ assert false : e.getSource();
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ if (e.getSource() == mSampleProjectName) {
+ mValues.projectName = mSampleProjectName.getText().trim();
+ mValues.projectNameModifiedByUser = true;
+ updatedProjectName();
+ }
+
+ validatePage();
+ }
+
+ private void updatedProjectName() {
+ if (mValues.useDefaultLocation) {
+ mValues.projectLocation = Platform.getLocation().toFile();
+ }
+ }
+
+ /**
+ * A sample was selected. Update the location field, manifest and validate.
+ * Extract names from an android manifest.
+ * This is done only if the user selected the "use existing source" and a manifest xml file
+ * can actually be found in the custom user directory.
+ */
+ private void extractNamesFromAndroidManifest() {
+ if (mValues.chosenSample == null || !mValues.chosenSample.isDirectory()) {
+ return;
+ }
+
+ Path path = new Path(mValues.chosenSample.getPath());
+ mValues.extractFromAndroidManifest(path);
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ if (mValues.mode != Mode.SAMPLE) {
+ return true;
+ }
+
+ // Ensure that when creating samples, the Finish button isn't enabled until
+ // the user has reached and completed this page
+ if (mValues.chosenSample == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+ if (mValues.samples == null || mValues.samples.size() == 0) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "The chosen SDK does not contain any samples");
+ } else if (mValues.chosenSample == null) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Choose a sample");
+ } else if (!mValues.chosenSample.exists()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("Sample does not exist: %1$s", mValues.chosenSample.getPath()));
+ } else {
+ status = ProjectNamePage.validateProjectName(mValues.projectName);
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java
new file mode 100644
index 000000000..6cafcf057
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/SdkSelectionPage.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.ide.common.xml.AndroidManifestParser;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
+import com.android.io.FileWrapper;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
+import com.android.utils.NullLogger;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+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.Composite;
+import org.eclipse.swt.widgets.Group;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+/** A page in the New Project wizard where you select the target SDK */
+class SdkSelectionPage extends WizardPage implements ITargetChangeListener {
+ private final NewProjectWizardState mValues;
+ private boolean mIgnore;
+ private SdkTargetSelector mSdkTargetSelector;
+
+ /**
+ * Create the wizard.
+ */
+ SdkSelectionPage(NewProjectWizardState values) {
+ super("sdkSelection"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Select Build Target");
+ AdtPlugin.getDefault().addTargetListener(this);
+ }
+
+ @Override
+ public void dispose() {
+ AdtPlugin.getDefault().removeTargetListener(this);
+ super.dispose();
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ @Override
+ public void createControl(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");
+
+ // The selector is created without targets. They are added below in the change listener.
+ mSdkTargetSelector = new SdkTargetSelector(group, null);
+
+ mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ mValues.target = mSdkTargetSelector.getSelected();
+ mValues.targetModifiedByUser = true;
+ onSdkTargetModified();
+ validatePage();
+ }
+ });
+
+ onSdkLoaded();
+
+ setControl(group);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (mValues.mode == Mode.SAMPLE) {
+ setDescription("Choose an SDK to select a sample from");
+ } else {
+ setDescription("Choose an SDK to target");
+ }
+ try {
+ mIgnore = true;
+ if (mValues.target != null) {
+ mSdkTargetSelector.setSelection(mValues.target);
+ }
+ } finally {
+ mIgnore = false;
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // Ensure that the Finish button isn't enabled until
+ // the user has reached and completed this page
+ if (mValues.target == null) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ /**
+ * 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 (mIgnore) {
+ return;
+ }
+
+ IAndroidTarget target = mValues.target;
+
+ // Update the minimum SDK text field?
+ // We do if one of two conditions are met:
+ if (target != null) {
+ boolean setMinSdk = false;
+ AndroidVersion version = target.getVersion();
+ int apiLevel = version.getApiLevel();
+ // 1. Has the user not manually edited the SDK field yet? If so, keep
+ // updating it to the selected value.
+ if (!mValues.minSdkModifiedByUser) {
+ setMinSdk = true;
+ } else {
+ // 2. Is the API level set to a higher level than the newly selected
+ // target SDK? If so, change it down to the new lower value.
+ String s = mValues.minSdk;
+ if (s.length() > 0) {
+ try {
+ int currentApi = Integer.parseInt(s);
+ if (currentApi > apiLevel) {
+ setMinSdk = true;
+ }
+ } catch (NumberFormatException nfe) {
+ // User may have typed something invalid -- ignore
+ }
+ }
+ }
+ if (setMinSdk) {
+ String minSdk;
+ if (version.isPreview()) {
+ minSdk = version.getCodename();
+ } else {
+ minSdk = Integer.toString(apiLevel);
+ }
+ mValues.minSdk = minSdk;
+ }
+ }
+
+ loadSamplesForTarget(target);
+ }
+
+ /**
+ * Updates the list of all samples for the given target SDK.
+ * The list is stored in mSamplesPaths as absolute directory paths.
+ * The combo is recreated to match this.
+ */
+ private void loadSamplesForTarget(IAndroidTarget target) {
+ // Keep the name of the old selection (if there were any samples)
+ File previouslyChosenSample = mValues.chosenSample;
+
+ mValues.samples.clear();
+ mValues.chosenSample = null;
+
+ if (target != null) {
+ // Get the sample root path and recompute the list of samples
+ String samplesRootPath = target.getPath(IAndroidTarget.SAMPLES);
+
+ File root = new File(samplesRootPath);
+ findSamplesManifests(root, root, null, null, mValues.samples);
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ // Parse the extras to see if we can find samples that are
+ // compatible with the selected target API.
+ // First we need an SdkManager that suppresses all output.
+ SdkManager sdkman = sdk.getNewSdkManager(NullLogger.getLogger());
+
+ Map<File, String> extras = sdkman.getExtraSamples();
+ for (Entry<File, String> entry : extras.entrySet()) {
+ File path = entry.getKey();
+ String name = entry.getValue();
+
+ // Case where the sample is at the root of the directory and not
+ // in a per-sample sub-directory.
+ if (path.getName().equals(SdkConstants.FD_SAMPLE)) {
+ findSampleManifestInDir(
+ path, path, name, target.getVersion(), mValues.samples);
+ }
+
+ // Scan sub-directories
+ findSamplesManifests(
+ path, path, name, target.getVersion(), mValues.samples);
+ }
+ }
+
+ if (mValues.samples.isEmpty()) {
+ return;
+ } else {
+ Collections.sort(mValues.samples, new Comparator<Pair<String, File>>() {
+ @Override
+ public int compare(Pair<String, File> o1, Pair<String, File> o2) {
+ // Compare the display name of the sample
+ return o1.getFirst().compareTo(o2.getFirst());
+ }
+ });
+ }
+
+ // Try to find the old selection.
+ if (previouslyChosenSample != null) {
+ String previouslyChosenName = previouslyChosenSample.getName();
+ for (int i = 0, n = mValues.samples.size(); i < n; i++) {
+ File file = mValues.samples.get(i).getSecond();
+ if (file.getName().equals(previouslyChosenName)) {
+ mValues.chosenSample = file;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Recursively find potential sample directories under the given directory.
+ * Actually lists any directory that contains an android manifest.
+ * Paths found are added the samplesPaths list.
+ *
+ * @param rootDir The "samples" root directory. Doesn't change during recursion.
+ * @param currDir The directory being scanned. Caller must initially set it to {@code rootDir}.
+ * @param extraName Optional name appended to the samples display name. Typically used to
+ * indicate a sample comes from a given extra package.
+ * @param targetVersion Optional target version filter. If non null, only samples that are
+ * compatible with the given target will be listed.
+ * @param samplesPaths A non-null list filled by this method with all samples found. The
+ * pair is (String: sample display name => File: sample directory).
+ */
+ private void findSamplesManifests(
+ File rootDir,
+ File currDir,
+ @Nullable String extraName,
+ @Nullable AndroidVersion targetVersion,
+ List<Pair<String, File>> samplesPaths) {
+ if (!currDir.isDirectory()) {
+ return;
+ }
+
+ for (File f : currDir.listFiles()) {
+ if (f.isDirectory()) {
+ findSampleManifestInDir(f, rootDir, extraName, targetVersion, samplesPaths);
+
+ // Recurse in the project, to find embedded tests sub-projects
+ // We can however skip this recursion for known android sub-dirs that
+ // can't have projects, namely for sources, assets and resources.
+ String leaf = f.getName();
+ if (!SdkConstants.FD_SOURCES.equals(leaf) &&
+ !SdkConstants.FD_ASSETS.equals(leaf) &&
+ !SdkConstants.FD_RES.equals(leaf)) {
+ findSamplesManifests(rootDir, f, extraName, targetVersion, samplesPaths);
+ }
+ }
+ }
+ }
+
+ private void findSampleManifestInDir(
+ File sampleDir,
+ File rootDir,
+ String extraName,
+ AndroidVersion targetVersion,
+ List<Pair<String, File>> samplesPaths) {
+ // Assume this is a sample if it contains an android manifest.
+ File manifestFile = new File(sampleDir, SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (manifestFile.isFile()) {
+ try {
+ ManifestData data =
+ AndroidManifestParser.parse(new FileWrapper(manifestFile));
+ if (data != null) {
+ boolean accept = false;
+ if (targetVersion == null) {
+ accept = true;
+ } else if (targetVersion != null) {
+ int i = data.getMinSdkVersion();
+ if (i != ManifestData.MIN_SDK_CODENAME) {
+ accept = i <= targetVersion.getApiLevel();
+ } else {
+ String s = data.getMinSdkVersionString();
+ if (s != null) {
+ accept = s.equals(targetVersion.getCodename());
+ }
+ }
+ }
+
+ if (accept) {
+ String name = getSampleDisplayName(extraName, rootDir, sampleDir);
+ samplesPaths.add(Pair.of(name, sampleDir));
+ }
+ }
+ } catch (Exception e) {
+ // Ignore. Don't use a sample which manifest doesn't parse correctly.
+ AdtPlugin.log(IStatus.INFO,
+ "NPW ignoring malformed manifest %s", //$NON-NLS-1$
+ manifestFile.getAbsolutePath());
+ }
+ }
+ }
+
+ /**
+ * Compute the sample name compared to its root directory.
+ */
+ private String getSampleDisplayName(String extraName, File rootDir, File sampleDir) {
+ String name = null;
+ if (!rootDir.equals(sampleDir)) {
+ String path = sampleDir.getPath();
+ int n = rootDir.getPath().length();
+ if (path.length() > n) {
+ path = path.substring(n);
+ if (path.charAt(0) == File.separatorChar) {
+ path = path.substring(1);
+ }
+ if (path.endsWith(File.separator)) {
+ path = path.substring(0, path.length() - 1);
+ }
+ name = path.replaceAll(Pattern.quote(File.separator), " > "); //$NON-NLS-1$
+ }
+ }
+ if (name == null &&
+ rootDir.equals(sampleDir) &&
+ sampleDir.getName().equals(SdkConstants.FD_SAMPLE) &&
+ extraName != null) {
+ // This is an old-style extra with one single sample directory. Just use the
+ // extra's name as the same name.
+ return extraName;
+ }
+ if (name == null) {
+ // Otherwise try to use the sample's directory name as the sample name.
+ while (sampleDir != null &&
+ (name == null ||
+ SdkConstants.FD_SAMPLE.equals(name) ||
+ SdkConstants.FD_SAMPLES.equals(name))) {
+ name = sampleDir.getName();
+ sampleDir = sampleDir.getParentFile();
+ }
+ }
+ if (name == null) {
+ if (extraName != null) {
+ // In the unlikely case nothing worked and we have an extra name, use that.
+ return extraName;
+ } else {
+ name = "Sample"; // fallback name... should not happen. //$NON-NLS-1$
+ }
+ }
+ if (extraName != null) {
+ name = name + " [" + extraName + ']'; //$NON-NLS-1$
+ }
+
+ return name;
+ }
+
+ private void validatePage() {
+ String error = null;
+
+ if (AdtPlugin.getDefault().getSdkLoadStatus() == LoadStatus.LOADING) {
+ error = "The SDK is still loading; please wait.";
+ }
+
+ if (error == null && mValues.target == null) {
+ error = "An SDK Target must be specified.";
+ }
+
+ if (error == null && mValues.mode == Mode.SAMPLE) {
+ // Make sure this SDK target contains samples
+ if (mValues.samples == null || mValues.samples.size() == 0) {
+ error = "This target has no samples. Please select another target.";
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ // ---- Implements ITargetChangeListener ----
+ @Override
+ public void onSdkLoaded() {
+ if (mSdkTargetSelector == null) {
+ return;
+ }
+
+ // 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.
+ // This will invoke the selection listener on the selector defined above.
+ if (targets != null && targets.length == 1) {
+ mValues.target = targets[0];
+ mSdkTargetSelector.setSelection(mValues.target);
+ onSdkTargetModified();
+ } else if (targets != null) {
+ // Pick the highest available platform by default (see issue #17505
+ // for related discussion.)
+ IAndroidTarget initialTarget = null;
+ for (IAndroidTarget target : targets) {
+ if (target.isPlatform()
+ && !target.getVersion().isPreview()
+ && (initialTarget == null ||
+ target.getVersion().getApiLevel() >
+ initialTarget.getVersion().getApiLevel())) {
+ initialTarget = target;
+ }
+ }
+ if (initialTarget != null) {
+ mValues.target = initialTarget;
+ try {
+ mIgnore = true;
+ mSdkTargetSelector.setSelection(mValues.target);
+ } finally {
+ mIgnore = false;
+ }
+ onSdkTargetModified();
+ }
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ @Override
+ public void onTargetLoaded(IAndroidTarget target) {
+ // Ignore
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java
new file mode 100644
index 000000000..f1c188ae9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/TestTargetPage.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2011 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.internal.wizards.newproject;
+
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.JavaElementLabelProvider;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.ui.dialogs.FilteredList;
+
+/**
+ * Page shown when creating a test project which lets you choose between testing
+ * yourself and testing a different project
+ */
+class TestTargetPage extends WizardPage implements SelectionListener {
+ private final NewProjectWizardState mValues;
+ /** Flag used when setting button/text state manually to ignore listener updates */
+ private boolean mIgnore;
+ private String mLastExistingPackageName;
+
+ private Button mCurrentRadioButton;
+ private Button mExistingRadioButton;
+ private FilteredList mProjectList;
+ private boolean mPageShown;
+
+ /**
+ * Create the wizard.
+ */
+ TestTargetPage(NewProjectWizardState values) {
+ super("testTargetPage"); //$NON-NLS-1$
+ setTitle("Select Test Target");
+ setDescription("Choose a project to test");
+ mValues = values;
+ }
+
+ /**
+ * Create contents of the wizard.
+ */
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+
+ setControl(container);
+ container.setLayout(new GridLayout(2, false));
+
+ mCurrentRadioButton = new Button(container, SWT.RADIO);
+ mCurrentRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCurrentRadioButton.setText("This project");
+ mCurrentRadioButton.addSelectionListener(this);
+
+ mExistingRadioButton = new Button(container, SWT.RADIO);
+ mExistingRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mExistingRadioButton.setText("An existing Android project:");
+ mExistingRadioButton.addSelectionListener(this);
+
+ ILabelProvider labelProvider = new JavaElementLabelProvider(
+ JavaElementLabelProvider.SHOW_DEFAULT);
+ mProjectList = new FilteredList(container,
+ SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE, labelProvider,
+ true /*ignoreCase*/, false /*allowDuplicates*/, true /* matchEmptyString*/);
+ mProjectList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ mProjectList.addSelectionListener(this);
+ }
+
+ private void initializeList() {
+ ProjectChooserHelper helper = new ProjectChooserHelper(getShell(), null /*filter*/);
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IJavaModel javaModel = JavaCore.create(workspaceRoot);
+ IJavaProject[] androidProjects = helper.getAndroidProjects(javaModel);
+ mProjectList.setElements(androidProjects);
+ if (mValues.testedProject != null) {
+ for (IJavaProject project : androidProjects) {
+ if (project.getProject() == mValues.testedProject) {
+ mProjectList.setSelection(new Object[] { project });
+ break;
+ }
+ }
+ } else {
+ // No initial selection: force the user to choose
+ mProjectList.setSelection(new int[0]);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ mPageShown = true;
+
+ if (visible) {
+ try {
+ mIgnore = true;
+ mCurrentRadioButton.setSelection(mValues.testingSelf);
+ mExistingRadioButton.setSelection(!mValues.testingSelf);
+ mProjectList.setEnabled(!mValues.testingSelf);
+
+ if (mProjectList.isEmpty()) {
+ initializeList();
+ }
+ if (!mValues.testingSelf) {
+ mProjectList.setFocus();
+ IProject project = getSelectedProject();
+ if (project != null) {
+ // The FilteredList seems to -insist- on selecting the first item
+ // in the list, even when the selection is explicitly set to an empty
+ // array. This means the user is looking at a selection, so we need
+ // to also go ahead and select this item in the model such that the
+ // two agree, even if we would have preferred to have no initial
+ // selection.
+ mValues.testedProject = project;
+ }
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mExistingRadioButton) {
+ mProjectList.setEnabled(true);
+ mValues.testingSelf = false;
+ setExistingProject(getSelectedProject());
+ mProjectList.setFocus();
+ } else if (source == mCurrentRadioButton) {
+ mProjectList.setEnabled(false);
+ mValues.testingSelf = true;
+ mValues.testedProject = null;
+ } else {
+ // The event must be from the project list, which unfortunately doesn't
+ // pass itself as the selection event, it passes a reference to some internal
+ // table widget that it uses, so we check for this case last
+ IProject project = getSelectedProject();
+ if (project != mValues.testedProject) {
+ setExistingProject(project);
+ }
+ }
+
+ validatePage();
+ }
+
+ private IProject getSelectedProject() {
+ Object[] selection = mProjectList.getSelection();
+ IProject project = selection != null && selection.length == 1
+ ? ((IJavaProject) selection[0]).getProject() : null;
+ return project;
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ private void setExistingProject(IProject project) {
+ mValues.testedProject = project;
+
+ // Try to update the application, package, sdk target and minSdkVersion accordingly
+ if (project != null &&
+ (!mValues.applicationNameModifiedByUser ||
+ !mValues.packageNameModifiedByUser ||
+ !mValues.targetModifiedByUser ||
+ !mValues.minSdkModifiedByUser)) {
+ ManifestData manifestData = AndroidManifestHelper.parseForData(project);
+ if (manifestData != null) {
+ String appName = String.format("%1$sTest", project.getName());
+ String packageName = manifestData.getPackage();
+ String minSdkVersion = manifestData.getMinSdkVersionString();
+ IAndroidTarget sdkTarget = null;
+ if (Sdk.getCurrent() != null) {
+ sdkTarget = Sdk.getCurrent().getTarget(project);
+ }
+
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+ mLastExistingPackageName = packageName;
+
+ if (!mValues.projectNameModifiedByUser) {
+ mValues.projectName = appName;
+ }
+
+ if (!mValues.applicationNameModifiedByUser) {
+ mValues.applicationName = appName;
+ }
+
+ if (!mValues.packageNameModifiedByUser) {
+ packageName += ".test"; //$NON-NLS-1$
+ mValues.packageName = packageName;
+ }
+
+ if (!mValues.targetModifiedByUser && sdkTarget != null) {
+ mValues.target = sdkTarget;
+ }
+
+ if (!mValues.minSdkModifiedByUser) {
+ if (minSdkVersion != null || sdkTarget != null) {
+ mValues.minSdk = minSdkVersion;
+ }
+ if (sdkTarget == null) {
+ mValues.updateSdkTargetToMatchMinSdkVersion();
+ }
+ }
+ }
+ }
+
+ updateTestTargetPackageField(mLastExistingPackageName);
+ }
+
+ /**
+ * Updates the test target package name
+ *
+ * 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 (mValues.testingSelf) {
+ mValues.testTargetPackageName = mValues.packageName;
+ } else if (packageName != null) {
+ mValues.testTargetPackageName = packageName;
+ }
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // Ensure that the user sees the page and makes a selection
+ if (!mPageShown) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ private void validatePage() {
+ String error = null;
+
+ if (!mValues.testingSelf) {
+ if (mValues.testedProject == null) {
+ error = "Please select an existing Android project as a test target.";
+ } else if (mValues.projectName.equals(mValues.testedProject.getName())) {
+ error = "The main project name and the test project name must be different.";
+ }
+ }
+
+ // -- update UI & enable finish if there's no error
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java
new file mode 100644
index 000000000..fb33a08b4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package com.android.ide.eclipse.adt.internal.wizards.newproject;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
+import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs;
+import org.eclipse.swt.SWT;
+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.Group;
+import org.eclipse.ui.IWorkingSet;
+import org.eclipse.ui.dialogs.WorkingSetConfigurationBlock;
+
+/**
+ * Copied from
+ * org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne$WorkingSetGroup
+ *
+ * Creates the working set group with controls that allow
+ * the selection of working sets
+ */
+@SuppressWarnings("restriction")
+public class WorkingSetGroup {
+
+ private WorkingSetConfigurationBlock fWorkingSetBlock;
+ private Button mEnableButton;
+
+ public WorkingSetGroup() {
+ String[] workingSetIds = new String[] {
+ IWorkingSetIDs.JAVA, IWorkingSetIDs.RESOURCE
+ };
+ fWorkingSetBlock = new WorkingSetConfigurationBlock(workingSetIds, JavaPlugin.getDefault()
+ .getDialogSettings());
+ }
+
+ public Composite createControl(Composite composite) {
+ Group workingSetGroup = new Group(composite, SWT.NONE);
+ workingSetGroup.setFont(composite.getFont());
+ workingSetGroup.setText(NewWizardMessages.NewJavaProjectWizardPageOne_WorkingSets_group);
+ workingSetGroup.setLayout(new GridLayout(1, false));
+
+ fWorkingSetBlock.createContent(workingSetGroup);
+
+ // WorkingSetGroup is implemented in such a way that the checkbox it contains
+ // can only be programmatically set if there's an existing working set associated
+ // *before* we construct the control. However the control is created when the
+ // wizard is opened, not when the page is first shown.
+ //
+ // One choice is to duplicate the class in our project.
+ // Or find the checkbox we want and trigger it manually.
+ mEnableButton = findCheckbox(workingSetGroup);
+
+ return workingSetGroup;
+ }
+
+ public void setWorkingSets(IWorkingSet[] workingSets) {
+ fWorkingSetBlock.setWorkingSets(workingSets);
+ }
+
+ public IWorkingSet[] getSelectedWorkingSets() {
+ try {
+ return fWorkingSetBlock.getSelectedWorkingSets();
+ } catch (Throwable t) {
+ // Test scenarios; no UI is created, which the fWorkingSetBlock assumes
+ // (it dereferences the enabledButton)
+ return new IWorkingSet[0];
+ }
+ }
+
+ public boolean isChecked() {
+ return mEnableButton == null ? false : mEnableButton.getSelection();
+ }
+
+ public void setChecked(boolean state) {
+ if (mEnableButton != null) {
+ mEnableButton.setSelection(state);
+ }
+ }
+
+ /**
+ * Finds the first button of style Checkbox in the given parent composite.
+ * Returns null if not found.
+ */
+ private Button findCheckbox(Composite parent) {
+ for (Control control : parent.getChildren()) {
+ if (control instanceof Button && (control.getStyle() & SWT.CHECK) == SWT.CHECK) {
+ return (Button) control;
+ } else if (control instanceof Composite) {
+ Button found = findCheckbox((Composite) control);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java
new file mode 100755
index 000000000..428bfd331
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package com.android.ide.eclipse.adt.internal.wizards.newproject;
+
+import org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart;
+import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeSelection;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkingSet;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class contains a helper method to deal with working sets.
+ * <p/>
+ * Copied from org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne
+ */
+@SuppressWarnings("restriction")
+public final class WorkingSetHelper {
+
+ private static final IWorkingSet[] EMPTY_WORKING_SET_ARRAY = new IWorkingSet[0];
+
+ /** This class is never instantiated. */
+ private WorkingSetHelper() {
+ }
+
+ public static IWorkingSet[] getSelectedWorkingSet(IStructuredSelection selection,
+ IWorkbenchPart activePart) {
+ IWorkingSet[] selected= getSelectedWorkingSet(selection);
+ if (selected != null && selected.length > 0) {
+ for (int i= 0; i < selected.length; i++) {
+ if (!isValidWorkingSet(selected[i]))
+ return EMPTY_WORKING_SET_ARRAY;
+ }
+ return selected;
+ }
+
+ if (!(activePart instanceof PackageExplorerPart))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ PackageExplorerPart explorerPart= (PackageExplorerPart) activePart;
+ if (explorerPart.getRootMode() == PackageExplorerPart.PROJECTS_AS_ROOTS) {
+ //Get active filter
+ IWorkingSet filterWorkingSet= explorerPart.getFilterWorkingSet();
+ if (filterWorkingSet == null)
+ return EMPTY_WORKING_SET_ARRAY;
+
+ if (!isValidWorkingSet(filterWorkingSet))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ return new IWorkingSet[] {filterWorkingSet};
+ } else {
+ //If we have been gone into a working set return the working set
+ Object input= explorerPart.getViewPartInput();
+ if (!(input instanceof IWorkingSet))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ IWorkingSet workingSet= (IWorkingSet)input;
+ if (!isValidWorkingSet(workingSet))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ return new IWorkingSet[] {workingSet};
+ }
+ }
+
+ private static IWorkingSet[] getSelectedWorkingSet(IStructuredSelection selection) {
+ if (!(selection instanceof ITreeSelection))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ ITreeSelection treeSelection= (ITreeSelection) selection;
+ if (treeSelection.isEmpty())
+ return EMPTY_WORKING_SET_ARRAY;
+
+ List<?> elements = treeSelection.toList();
+ if (elements.size() == 1) {
+ Object element= elements.get(0);
+ TreePath[] paths= treeSelection.getPathsFor(element);
+ if (paths.length != 1)
+ return EMPTY_WORKING_SET_ARRAY;
+
+ TreePath path= paths[0];
+ if (path.getSegmentCount() == 0)
+ return EMPTY_WORKING_SET_ARRAY;
+
+ Object candidate= path.getSegment(0);
+ if (!(candidate instanceof IWorkingSet))
+ return EMPTY_WORKING_SET_ARRAY;
+
+ IWorkingSet workingSetCandidate= (IWorkingSet) candidate;
+ if (isValidWorkingSet(workingSetCandidate))
+ return new IWorkingSet[] { workingSetCandidate };
+
+ return EMPTY_WORKING_SET_ARRAY;
+ }
+
+ ArrayList<Object> result = new ArrayList<Object>();
+ for (Iterator<?> iterator = elements.iterator(); iterator.hasNext();) {
+ Object element= iterator.next();
+ if (element instanceof IWorkingSet && isValidWorkingSet((IWorkingSet) element)) {
+ result.add(element);
+ }
+ }
+ return result.toArray(new IWorkingSet[result.size()]);
+ }
+
+
+ private static boolean isValidWorkingSet(IWorkingSet workingSet) {
+ String id= workingSet.getId();
+ if (!IWorkingSetIDs.JAVA.equals(id) && !IWorkingSetIDs.RESOURCE.equals(id))
+ return false;
+
+ if (workingSet.isAggregateWorkingSet())
+ return false;
+
+ return true;
+ }
+}