aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--androidprefs/.gitignore2
-rw-r--r--anttasks/.gitignore2
-rw-r--r--apkbuilder/.gitignore2
-rw-r--r--ddms/.gitignore4
-rw-r--r--ddms/app/src/com/android/ddms/Main.java4
-rw-r--r--ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java3
-rw-r--r--eclipse/changes.txt13
-rw-r--r--eclipse/features/com.android.ide.eclipse.adt/feature.xml2
-rw-r--r--eclipse/features/com.android.ide.eclipse.ddms/feature.xml2
-rw-r--r--eclipse/features/com.android.ide.eclipse.tests/feature.xml2
-rw-r--r--eclipse/plugins/.gitignore52
-rw-r--r--eclipse/plugins/README.txt114
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/.classpath2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.pngbin0 -> 135 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml60
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java26
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java666
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java76
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java82
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java55
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java42
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java1
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/IUpdateWizardDialog.java30
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/WizardDialogEx.java46
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java34
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java6
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java34
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java57
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java73
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java631
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java114
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java472
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java567
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java1349
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java30
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestPkgAttrNode.java338
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template1
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java79
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java23
-rw-r--r--eclipse/sites/external/site.xml4
-rw-r--r--eclipse/sites/internal/site.xml6
-rw-r--r--eclipse/source_package_readme.txt49
-rw-r--r--jarutils/.gitignore2
-rw-r--r--layoutlib_utils/.gitignore2
-rw-r--r--sdkmanager/.gitignore4
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/Main.java49
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java9
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java9
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java16
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java3
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java94
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java232
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java18
-rw-r--r--sdkstats/.gitignore2
-rw-r--r--sdkstats/src/com/android/sdkstats/SdkStatsService.java145
-rw-r--r--traceview/.gitignore1
-rw-r--r--traceview/src/com/android/traceview/MainWindow.java2
77 files changed, 4656 insertions, 1245 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f4c6af0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*~
+*.pyc
+Thumbs.db
+
diff --git a/androidprefs/.gitignore b/androidprefs/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/androidprefs/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/anttasks/.gitignore b/anttasks/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/anttasks/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/apkbuilder/.gitignore b/apkbuilder/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/apkbuilder/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/ddms/.gitignore b/ddms/.gitignore
new file mode 100644
index 0000000..6d833a0
--- /dev/null
+++ b/ddms/.gitignore
@@ -0,0 +1,4 @@
+app/bin
+libs/ddmlib/bin
+libs/ddmuilib/bin
+
diff --git a/ddms/app/src/com/android/ddms/Main.java b/ddms/app/src/com/android/ddms/Main.java
index d63b884..d545ed9 100644
--- a/ddms/app/src/com/android/ddms/Main.java
+++ b/ddms/app/src/com/android/ddms/Main.java
@@ -78,7 +78,7 @@ public class Main {
// the "ping" argument means to check in with the server and exit
// the application name and version number must also be supplied
if (args.length >= 3 && args[0].equals("ping")) {
- SdkStatsService.ping(args[1], args[2]);
+ SdkStatsService.ping(args[1], args[2], null);
return;
} else if (args.length > 0) {
Log.e("ddms", "Unknown argument: " + args[0]);
@@ -86,7 +86,7 @@ public class Main {
}
// ddms itself is wanted: send a ping for ourselves
- SdkStatsService.ping("ddms", VERSION); //$NON-NLS-1$
+ SdkStatsService.ping("ddms", VERSION, null); //$NON-NLS-1$
DebugPortManager.setProvider(DebugPortProvider.getInstance());
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
index f9d0fa0..87e023a 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
@@ -748,12 +748,13 @@ final class DeviceMonitor {
if (AndroidDebugBridge.getClientSupport()) {
client.listenForDebugger(debuggerPort);
}
- client.requestAllocationStatus();
} catch (IOException ioe) {
client.getClientData().setDebuggerConnectionStatus(ClientData.DEBUGGER_ERROR);
Log.e("ddms", "Can't bind to local " + debuggerPort + " for debugger");
// oh well
}
+
+ client.requestAllocationStatus();
} else {
Log.e("ddms", "Handshake with " + client + " failed!");
/*
diff --git a/eclipse/changes.txt b/eclipse/changes.txt
index 02d9075..ecb1f15 100644
--- a/eclipse/changes.txt
+++ b/eclipse/changes.txt
@@ -1,4 +1,15 @@
-0.9.0 (work in progress)
+0.9.2:
+- New wizard to create Android JUnit Test Projects.
+
+
+0.9.1:
+- Added an AVD creation wizard to ADT. It is automatically displayed during a launch if no compatible AVDs are found.
+- Fixed issue with libs/ folder where files with no extension would prevent the build from finishing.
+- Improved error handling during the final steps of the build to mark the project if an unexpected error prevent the build from finishing.
+- Fixed issue when launching ADT on a clean install would trigger org.eclipse.swt.SWTError: Not implemented [multiple displays].
+
+
+0.9.0:
- Projects now store generated Java files (R.java/Manifest.java and output from aidl) in a 'gen' source folder.
- Support for the new Android SDK with support for multiple versions of the Android platform and for vendor supplied add-ons.
* New Project Wizard lets you choose which platform/add-on to target.
diff --git a/eclipse/features/com.android.ide.eclipse.adt/feature.xml b/eclipse/features/com.android.ide.eclipse.adt/feature.xml
index e7cffea..1c4a043 100644
--- a/eclipse/features/com.android.ide.eclipse.adt/feature.xml
+++ b/eclipse/features/com.android.ide.eclipse.adt/feature.xml
@@ -2,7 +2,7 @@
<feature
id="com.android.ide.eclipse.adt"
label="Android Development Tools"
- version="0.9.0.qualifier"
+ version="0.9.1.qualifier"
provider-name="The Android Open Source Project"
plugin="com.android.ide.eclipse.adt">
diff --git a/eclipse/features/com.android.ide.eclipse.ddms/feature.xml b/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
index 00805e4..ae3944b 100644
--- a/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
+++ b/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
@@ -2,7 +2,7 @@
<feature
id="com.android.ide.eclipse.ddms"
label="Android DDMS"
- version="0.9.0.qualifier"
+ version="0.9.1.qualifier"
provider-name="The Android Open Source Project">
<description>
diff --git a/eclipse/features/com.android.ide.eclipse.tests/feature.xml b/eclipse/features/com.android.ide.eclipse.tests/feature.xml
index 2a3a74f..b88f071 100644
--- a/eclipse/features/com.android.ide.eclipse.tests/feature.xml
+++ b/eclipse/features/com.android.ide.eclipse.tests/feature.xml
@@ -2,7 +2,7 @@
<feature
id="com.android.ide.eclipse.tests"
label="ADT Tests"
- version="0.9.0.qualifier"
+ version="0.9.1.qualifier"
provider-name="The Android Open Source Project">
<copyright>
diff --git a/eclipse/plugins/.gitignore b/eclipse/plugins/.gitignore
new file mode 100644
index 0000000..2842bb1
--- /dev/null
+++ b/eclipse/plugins/.gitignore
@@ -0,0 +1,52 @@
+com.android.ide.eclipse.adt/bin
+com.android.ide.eclipse.ddms/bin
+com.android.ide.eclipse.tests/bin
+
+com.android.ide.eclipse.adt/androidprefs.jar
+com.android.ide.eclipse.adt/jarutils.jar
+com.android.ide.eclipse.adt/kxml2-2.3.0.jar
+com.android.ide.eclipse.adt/layoutlib_api.jar
+com.android.ide.eclipse.adt/layoutlib_utils.jar
+com.android.ide.eclipse.adt/ninepatch.jar
+com.android.ide.eclipse.adt/sdklib.jar
+com.android.ide.eclipse.adt/sdkstats.jar
+com.android.ide.eclipse.adt/sdkuilib.jar
+com.android.ide.eclipse.ddms/icons/add.png
+com.android.ide.eclipse.ddms/icons/backward.png
+com.android.ide.eclipse.ddms/icons/clear.png
+com.android.ide.eclipse.ddms/icons/d.png
+com.android.ide.eclipse.ddms/icons/debug-attach.png
+com.android.ide.eclipse.ddms/icons/debug-error.png
+com.android.ide.eclipse.ddms/icons/debug-wait.png
+com.android.ide.eclipse.ddms/icons/delete.png
+com.android.ide.eclipse.ddms/icons/device.png
+com.android.ide.eclipse.ddms/icons/down.png
+com.android.ide.eclipse.ddms/icons/e.png
+com.android.ide.eclipse.ddms/icons/edit.png
+com.android.ide.eclipse.ddms/icons/empty.png
+com.android.ide.eclipse.ddms/icons/emulator.png
+com.android.ide.eclipse.ddms/icons/forward.png
+com.android.ide.eclipse.ddms/icons/gc.png
+com.android.ide.eclipse.ddms/icons/halt.png
+com.android.ide.eclipse.ddms/icons/heap.png
+com.android.ide.eclipse.ddms/icons/i.png
+com.android.ide.eclipse.ddms/icons/importBug.png
+com.android.ide.eclipse.ddms/icons/load.png
+com.android.ide.eclipse.ddms/icons/pause.png
+com.android.ide.eclipse.ddms/icons/play.png
+com.android.ide.eclipse.ddms/icons/pull.png
+com.android.ide.eclipse.ddms/icons/push.png
+com.android.ide.eclipse.ddms/icons/save.png
+com.android.ide.eclipse.ddms/icons/thread.png
+com.android.ide.eclipse.ddms/icons/up.png
+com.android.ide.eclipse.ddms/icons/v.png
+com.android.ide.eclipse.ddms/icons/w.png
+com.android.ide.eclipse.ddms/icons/warning.png
+com.android.ide.eclipse.ddms/libs/jcommon-1.0.12.jar
+com.android.ide.eclipse.ddms/libs/jfreechart-1.0.9-swt.jar
+com.android.ide.eclipse.ddms/libs/jfreechart-1.0.9.jar
+com.android.ide.eclipse.ddms/src/com/android/ddmlib
+com.android.ide.eclipse.ddms/src/com/android/ddmuilib
+com.android.ide.eclipse.tests/kxml2-2.3.0.jar
+com.android.ide.eclipse.tests/unittests/com/android/ddmlib
+
diff --git a/eclipse/plugins/README.txt b/eclipse/plugins/README.txt
deleted file mode 100644
index 184d731..0000000
--- a/eclipse/plugins/README.txt
+++ /dev/null
@@ -1,114 +0,0 @@
-Compiling and deploying the Android Development Toolkit (ADT) feature.
-
-The ADT feature is composed of four plugins:
-- com.android.ide.eclipse.adt:
- The ADT plugin, which provides support for compiling and debugging android
- applications.
-- com.android.ide.eclipse.common:
- A common plugin providing utility services to the other plugins.
-- com.android.ide.eclipse.editors:
- A plugin providing optional XML editors.
-- com.android.ide.eclipse.ddms:
- A plugin version of the tool DDMS
-
-Because the DDMS plugin source code is not yet released, compiling the
-ADT/Common/Editors plugins requires to install the DDMS plugin in eclipse.
-
-Basic requirements:
-- Eclipse 3.3 or 3.4 with JDT and PDE.
-- DDMS plugin installed and running.
-
-
---------------------------
-1- Install the DDMS plugin
---------------------------
-
-The easiest way to setup the DDMS plugin in your Eclipse environment is to
-install the ADT features (see SDK documentation for details) and then remove
-the following features and plugins:
-
-- <eclipse-directory>/features/com.android.ide.eclipse.adt_x.x.x.jar
-- <eclipse-directory>/plugins/com.android.ide.eclipse.adt_x.x.x.jar
-- <eclipse-directory>/plugins/com.android.ide.eclipse.common_x.x.x.jar
-- <eclipse-directory>/plugins/com.android.ide.eclipse.editors_x.x.x.jar
-
-This will leave you with only the DDMS plugin installed in your Eclipse
-distribution.
-
-
--------------------------------------
-2- Setting up the ADT/Common project
--------------------------------------
-
-- Download the ADT/Common/Editors source.
-
-- From the SDK, copy the following jars:
- * androidprefs.jar => com.android.ide.eclipse.adt folder.
- * jarutils.jar => com.android.ide.eclipse.adt folder.
- * ping.jar => com.android.ide.eclipse.common folder.
- * androidprefs.jar => com.android.ide.eclipse.common folder.
-
-- Create a java project from existing source for both the ADT plugin and the
- common plugin.
-
-- In the Package Explorer, right click the projects and choose
- PDE Tools > Convert Projects to Plug-in Project...
-
-- Select your projects in the dialog box and click OK.
-
-- In the Package Explorer, for ADT and common, right click the jar files mentioned above
- and choose Build Path > Add to Build Path
-
-At this point the projects will compile.
-
-To launch the projects, open the Run/Debug Dialog and create an "Eclipse
-Application" launch configuration.
-
-Additionnaly, another feature containing the Android Editors Plugin
-(com.android.ide.eclipse.editors) is available.
-
-- Make sure the common project is present in your workspace as the Editors
- plugin depends on this plugin. Alternatively, you can have the offical ADT
- feature installed in your Eclipse distribution.
-- Create a java project from existing source for the Editors project.
-- In the Package Explorer, right click the project and choose
- PDE Tools > Convert Projects to Plug-in Project...
-- Select your project in the dialog box and click OK.
-
-Create an "Eclipse Application" launch configuration to test the plugin.
-
--------------------------------------
-3- Setting up the Editors project
--------------------------------------
-
-The "editors" plugin is optional. You can use ADT to develop Android
-applications without the XML editor support. When this plugin is present, it
-offers several customized form-based XML editors and one graphical layout
-editor.
-
-At the time of this release (Android 0.9 SDK), some of the supporting libraries
-still need some cleanup and are currently only provided as JAR files.
-
-- Download the ADT/Common/Editors source.
-
-- From the source archives, copy the following jars:
- * ninepatch.jar => com.android.ide.eclipse.editors folder.
- * layoutlib_utils.jar => com.android.ide.eclipse.editors folder.
- * layoutlib_api.jar => com.android.ide.eclipse.editors folder.
-
-- From http://kxml.sourceforge.net/ download:
- * kXML2-2.3.0.jar => com.android.ide.eclipse.editors folder.
-
-- Create a java project from existing source for both the editors plugin.
-
-- In the Package Explorer, right click the project and choose
- PDE Tools > Convert Projects to Plug-in Project...
-
-- Select your project in the dialog box and click OK.
-
-- In the Package Explorer for editors, right click the jar files mentioned
- above and choose Build Path > Add to Build Path
-
-To launch the projects, reuse the "Eclipse Application" launch configuration
-created for ADT.
-
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index a24fc87..9898b97 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -5,7 +5,7 @@
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="lib" path="jarutils.jar"/>
<classpathentry kind="lib" path="androidprefs.jar"/>
- <classpathentry kind="lib" path="sdkstats.jar"/>
+ <classpathentry kind="lib" path="sdkstats.jar" sourcepath="/SdkStatsService"/>
<classpathentry kind="lib" path="kxml2-2.3.0.jar"/>
<classpathentry kind="lib" path="layoutlib_api.jar"/>
<classpathentry kind="lib" path="layoutlib_utils.jar"/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 8092f3a..7d52bb6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Development Toolkit
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
-Bundle-Version: 0.9.0.qualifier
+Bundle-Version: 0.9.1.qualifier
Bundle-ClassPath: .,
jarutils.jar,
androidprefs.jar,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png
new file mode 100755
index 0000000..7dbbbb6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 6022a20..1d0a18d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -90,7 +90,7 @@
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
- icon="icons/android.png"
+ icon="icons/new_adt_project.png"
id="com.android.ide.eclipse.adt.project.NewProjectWizard"
name="Android Project"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
@@ -98,10 +98,22 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
- class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
+ class="com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
- icon="icons/android.png"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.project.NewTestProjectWizard"
+ name="Android Test Project"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="true">
+ </wizard>
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_xml.png"
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
name="Android XML File"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
@@ -220,11 +232,22 @@
value="com.android.ide.eclipse.adt.AndroidNature">
</filter>
<action
- class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
+ class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
enablesFor="1"
- id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
+ icon="icons/new_xml.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
label="New Resource File..."
- menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1">
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"
+ tooltip="Opens a wizard to help create a new Android XML Resource file">
+ </action>
+ <action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ enablesFor="1"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ label="New Test Project..."
+ menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"
+ tooltip="Opens a wizard to help create a new Android Test Project">
</action>
<action
class="com.android.ide.eclipse.adt.project.ExportAction"
@@ -484,6 +507,15 @@
tooltip="Opens a wizard to help create a new Android XML file">
</action>
<action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewTestProjectAction"
+ label="New Android Test Project"
+ style="push"
+ toolbarPath="android_project"
+ tooltip="Opens a wizard to help create a new Android Test Project">
+ </action>
+ <action
class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
icon="icons/new_adt_project.png"
id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
@@ -538,6 +570,22 @@
label="Refactor">
</menu>
</actionSet>
+ <actionSet
+ description="Android AVD Manager"
+ id="adt.actionSet.avdManager"
+ label="Android AVD Manager"
+ visible="true">
+ <action
+ class="com.android.ide.eclipse.adt.wizards.actions.AvdManagerAction"
+ icon="icons/avd_manager.png"
+ id="com.android.ide.eclipse.adt.ui.avdmanager"
+ label="Android AVD Manager"
+ menubarPath="Window/additions"
+ style="push"
+ toolbarPath="android_project"
+ tooltip="Opens the Android Virtual Device (AVD) Manager">
+ </action>
+ </actionSet>
</extension>
<extension
point="org.eclipse.debug.core.launchDelegates">
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index b5cee81..936f47c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -31,7 +31,6 @@ import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.ui.EclipseUiHelper;
import com.android.ide.eclipse.common.AndroidConstants;
-import com.android.ide.eclipse.common.SdkStatsHelper;
import com.android.ide.eclipse.common.StreamHelper;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ExportHelper;
@@ -51,6 +50,7 @@ import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileLi
import com.android.ide.eclipse.editors.xml.XmlEditor;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
+import com.android.sdkstats.SdkStatsService;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -983,13 +983,7 @@ public class AdtPlugin extends AbstractUIPlugin {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
-
- // get the version of the plugin
- String versionString = (String) getBundle().getHeaders().get(
- Constants.BUNDLE_VERSION);
- Version version = new Version(versionString);
-
- SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
+ pingUsageServer(); //$NON-NLS-1$
return Status.OK_STATUS;
} catch (Throwable t) {
@@ -1389,4 +1383,20 @@ public class AdtPlugin extends AbstractUIPlugin {
public static synchronized OutputStream getErrorStream() {
return sPlugin.mAndroidConsoleErrorStream;
}
+
+ /**
+ * Pings the usage start server.
+ */
+ private void pingUsageServer() {
+ // get the version of the plugin
+ String versionString = (String) getBundle().getHeaders().get(
+ Constants.BUNDLE_VERSION);
+ Version version = new Version(versionString);
+
+ versionString = String.format("%1$d.%2$d.%3$d", version.getMajor(), //$NON-NLS-1$
+ version.getMinor(), version.getMicro());
+
+ SdkStatsService.ping("adt", versionString, getDisplay()); //$NON-NLS-1$
+ }
+
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
index 47ea3e7..f20843a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
@@ -204,364 +204,390 @@ public class ApkBuilder extends BaseBuilder {
// get a project object
IProject project = getProject();
- // Top level check to make sure the build can move forward.
- abortOnBadSetup(project);
-
- // get the list of referenced projects.
- IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project);
- IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
-
- // get the output folder, this method returns the path with a trailing
- // separator
- IJavaProject javaProject = JavaCore.create(project);
- IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
-
- // now we need to get the classpath list
- ArrayList<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
-
- // First thing we do is go through the resource delta to not
- // lose it if we have to abort the build for any reason.
- ApkDeltaVisitor dv = null;
- if (kind == FULL_BUILD) {
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
- Messages.Start_Full_Apk_Build);
-
- mPackageResources = true;
- mConvertToDex = true;
- mBuildFinalPackage = true;
- } else {
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
- Messages.Start_Inc_Apk_Build);
-
- // go through the resources and see if something changed.
- IResourceDelta delta = getDelta(project);
- if (delta == null) {
+ // list of referenced projects.
+ IProject[] referencedProjects = null;
+
+ try {
+ // Top level check to make sure the build can move forward.
+ abortOnBadSetup(project);
+
+ // get the list of referenced projects.
+ referencedProjects = ProjectHelper.getReferencedProjects(project);
+ IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
+
+ // get the output folder, this method returns the path with a trailing
+ // separator
+ IJavaProject javaProject = JavaCore.create(project);
+ IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
+
+ // now we need to get the classpath list
+ ArrayList<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
+
+ // First thing we do is go through the resource delta to not
+ // lose it if we have to abort the build for any reason.
+ ApkDeltaVisitor dv = null;
+ if (kind == FULL_BUILD) {
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
+ Messages.Start_Full_Apk_Build);
+
mPackageResources = true;
mConvertToDex = true;
mBuildFinalPackage = true;
} else {
- dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
- delta.accept(dv);
-
- // save the state
- mPackageResources |= dv.getPackageResources();
- mConvertToDex |= dv.getConvertToDex();
- mBuildFinalPackage |= dv.getMakeFinalPackage();
- }
-
- // also go through the delta for all the referenced projects, until we are forced to
- // compile anyway
- for (int i = 0 ; i < referencedJavaProjects.length &&
- (mBuildFinalPackage == false || mConvertToDex == false); i++) {
- IJavaProject referencedJavaProject = referencedJavaProjects[i];
- delta = getDelta(referencedJavaProject.getProject());
- if (delta != null) {
- ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
- referencedJavaProject);
- delta.accept(refProjectDv);
-
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
+ Messages.Start_Inc_Apk_Build);
+
+ // go through the resources and see if something changed.
+ IResourceDelta delta = getDelta(project);
+ if (delta == null) {
+ mPackageResources = true;
+ mConvertToDex = true;
+ mBuildFinalPackage = true;
+ } else {
+ dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
+ delta.accept(dv);
+
// save the state
- mConvertToDex |= refProjectDv.needDexConvertion();
- mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
+ mPackageResources |= dv.getPackageResources();
+ mConvertToDex |= dv.getConvertToDex();
+ mBuildFinalPackage |= dv.getMakeFinalPackage();
+ }
+
+ // also go through the delta for all the referenced projects, until we are forced to
+ // compile anyway
+ for (int i = 0 ; i < referencedJavaProjects.length &&
+ (mBuildFinalPackage == false || mConvertToDex == false); i++) {
+ IJavaProject referencedJavaProject = referencedJavaProjects[i];
+ delta = getDelta(referencedJavaProject.getProject());
+ if (delta != null) {
+ ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
+ referencedJavaProject);
+ delta.accept(refProjectDv);
+
+ // save the state
+ mConvertToDex |= refProjectDv.needDexConvertion();
+ mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
+ }
}
}
- }
+
+ // store the build status in the persistent storage
+ saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
+ saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
+ saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
+
+ if (dv != null && dv.mXmlError) {
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
+ Messages.Xml_Error);
+
+ // if there was some XML errors, we just return w/o doing
+ // anything since we've put some markers in the files anyway
+ return referencedProjects;
+ }
+
+ if (outputFolder == null) {
+ // mark project and exit
+ markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output,
+ IMarker.SEVERITY_ERROR);
+ return referencedProjects;
+ }
+
+ // first thing we do is check that the SDK directory has been setup.
+ String osSdkFolder = AdtPlugin.getOsSdkFolder();
+
+ if (osSdkFolder.length() == 0) {
+ // this has already been checked in the precompiler. Therefore,
+ // while we do have to cancel the build, we don't have to return
+ // any error or throw anything.
+ return referencedProjects;
+ }
+
+ // get the extra configs for the project.
+ // The map contains (name, filter) where 'name' is a name to be used in the apk filename,
+ // and filter is the resource filter to be used in the aapt -c parameters to restrict
+ // which resource configurations to package in the apk.
+ Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project);
+
+ // do some extra check, in case the output files are not present. This
+ // will force to recreate them.
+ IResource tmp = null;
+
+ if (mPackageResources == false) {
+ // check the full resource package
+ tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
+ if (tmp == null || tmp.exists() == false) {
+ mPackageResources = true;
+ mBuildFinalPackage = true;
+ } else {
+ // if the full package is present, we check the filtered resource packages as well
+ if (configs != null) {
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+
+ for (Entry<String, String> entry : entrySet) {
+ String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
+ entry.getKey());
- // store the build status in the persistent storage
- saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
- saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
- saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
-
- if (dv != null && dv.mXmlError) {
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
- Messages.Xml_Error);
-
- // if there was some XML errors, we just return w/o doing
- // anything since we've put some markers in the files anyway
- return referencedProjects;
- }
-
- if (outputFolder == null) {
- // mark project and exit
- markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output,
- IMarker.SEVERITY_ERROR);
- return referencedProjects;
- }
-
- // first thing we do is check that the SDK directory has been setup.
- String osSdkFolder = AdtPlugin.getOsSdkFolder();
-
- if (osSdkFolder.length() == 0) {
- // this has already been checked in the precompiler. Therefore,
- // while we do have to cancel the build, we don't have to return
- // any error or throw anything.
- return referencedProjects;
- }
-
- // get the extra configs for the project.
- // The map contains (name, filter) where 'name' is a name to be used in the apk filename,
- // and filter is the resource filter to be used in the aapt -c parameters to restrict
- // which resource configurations to package in the apk.
- Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project);
-
- // do some extra check, in case the output files are not present. This
- // will force to recreate them.
- IResource tmp = null;
-
- if (mPackageResources == false) {
- // check the full resource package
- tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
- if (tmp == null || tmp.exists() == false) {
- mPackageResources = true;
- mBuildFinalPackage = true;
- } else {
- // if the full package is present, we check the filtered resource packages as well
- if (configs != null) {
+ tmp = outputFolder.findMember(filename);
+ if (tmp == null || (tmp instanceof IFile &&
+ tmp.exists() == false)) {
+ String msg = String.format(Messages.s_Missing_Repackaging, filename);
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ mPackageResources = true;
+ mBuildFinalPackage = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // check classes.dex is present. If not we force to recreate it.
+ if (mConvertToDex == false) {
+ tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
+ if (tmp == null || tmp.exists() == false) {
+ mConvertToDex = true;
+ mBuildFinalPackage = true;
+ }
+ }
+
+ // also check the final file(s)!
+ String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
+ if (mBuildFinalPackage == false) {
+ tmp = outputFolder.findMember(finalPackageName);
+ if (tmp == null || (tmp instanceof IFile &&
+ tmp.exists() == false)) {
+ String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ mBuildFinalPackage = true;
+ } else if (configs != null) {
+ // if the full apk is present, we check the filtered apk as well
Set<Entry<String, String>> entrySet = configs.entrySet();
for (Entry<String, String> entry : entrySet) {
- String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
- entry.getKey());
+ String filename = ProjectHelper.getApkFilename(project, entry.getKey());
tmp = outputFolder.findMember(filename);
if (tmp == null || (tmp instanceof IFile &&
tmp.exists() == false)) {
String msg = String.format(Messages.s_Missing_Repackaging, filename);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
- mPackageResources = true;
mBuildFinalPackage = true;
break;
}
}
}
}
- }
-
- // check classes.dex is present. If not we force to recreate it.
- if (mConvertToDex == false) {
- tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
- if (tmp == null || tmp.exists() == false) {
- mConvertToDex = true;
- mBuildFinalPackage = true;
- }
- }
-
- // also check the final file(s)!
- String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
- if (mBuildFinalPackage == false) {
- tmp = outputFolder.findMember(finalPackageName);
- if (tmp == null || (tmp instanceof IFile &&
- tmp.exists() == false)) {
- String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
- mBuildFinalPackage = true;
- } else if (configs != null) {
- // if the full apk is present, we check the filtered apk as well
- Set<Entry<String, String>> entrySet = configs.entrySet();
-
- for (Entry<String, String> entry : entrySet) {
- String filename = ProjectHelper.getApkFilename(project, entry.getKey());
-
- tmp = outputFolder.findMember(filename);
- if (tmp == null || (tmp instanceof IFile &&
- tmp.exists() == false)) {
- String msg = String.format(Messages.s_Missing_Repackaging, filename);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
- mBuildFinalPackage = true;
- break;
- }
- }
- }
- }
-
- // at this point we know if we need to recreate the temporary apk
- // or the dex file, but we don't know if we simply need to recreate them
- // because they are missing
-
- // refresh the output directory first
- IContainer ic = outputFolder.getParent();
- if (ic != null) {
- ic.refreshLocal(IResource.DEPTH_ONE, monitor);
- }
-
- // we need to test all three, as we may need to make the final package
- // but not the intermediary ones.
- if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
- IPath binLocation = outputFolder.getLocation();
- if (binLocation == null) {
- markProject(AdtConstants.MARKER_ADT, Messages.Output_Missing,
- IMarker.SEVERITY_ERROR);
- return referencedProjects;
- }
- String osBinPath = binLocation.toOSString();
-
- // Remove the old .apk.
- // This make sure that if the apk is corrupted, then dx (which would attempt
- // to open it), will not fail.
- String osFinalPackagePath = osBinPath + File.separator + finalPackageName;
- File finalPackage = new File(osFinalPackagePath);
-
- // if delete failed, this is not really a problem, as the final package generation
- // handle already present .apk, and if that one failed as well, the user will be
- // notified.
- finalPackage.delete();
-
- if (configs != null) {
- Set<Entry<String, String>> entrySet = configs.entrySet();
- for (Entry<String, String> entry : entrySet) {
- String packageFilepath = osBinPath + File.separator +
- ProjectHelper.getApkFilename(project, entry.getKey());
-
- finalPackage = new File(packageFilepath);
- finalPackage.delete();
- }
+
+ // at this point we know if we need to recreate the temporary apk
+ // or the dex file, but we don't know if we simply need to recreate them
+ // because they are missing
+
+ // refresh the output directory first
+ IContainer ic = outputFolder.getParent();
+ if (ic != null) {
+ ic.refreshLocal(IResource.DEPTH_ONE, monitor);
}
-
- // first we check if we need to package the resources.
- if (mPackageResources) {
- // remove some aapt_package only markers.
- removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
-
- // need to figure out some path before we can execute aapt;
-
- // resource to the AndroidManifest.xml file
- IResource manifestResource = project .findMember(
- AndroidConstants.WS_SEP + AndroidConstants.FN_ANDROID_MANIFEST);
-
- if (manifestResource == null
- || manifestResource.exists() == false) {
- // mark project and exit
- String msg = String.format(Messages.s_File_Missing,
- AndroidConstants.FN_ANDROID_MANIFEST);
- markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
+
+ // we need to test all three, as we may need to make the final package
+ // but not the intermediary ones.
+ if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
+ IPath binLocation = outputFolder.getLocation();
+ if (binLocation == null) {
+ markProject(AdtConstants.MARKER_ADT, Messages.Output_Missing,
+ IMarker.SEVERITY_ERROR);
return referencedProjects;
}
-
- // get the resource folder
- IFolder resFolder = project.getFolder(
- AndroidConstants.WS_RESOURCES);
-
- // and the assets folder
- IFolder assetsFolder = project.getFolder(
- AndroidConstants.WS_ASSETS);
-
- // we need to make sure this one exists.
- if (assetsFolder.exists() == false) {
- assetsFolder = null;
- }
-
- IPath resLocation = resFolder.getLocation();
- IPath manifestLocation = manifestResource.getLocation();
-
- if (resLocation != null && manifestLocation != null) {
- String osResPath = resLocation.toOSString();
- String osManifestPath = manifestLocation.toOSString();
-
- String osAssetsPath = null;
- if (assetsFolder != null) {
- osAssetsPath = assetsFolder.getLocation().toOSString();
+ String osBinPath = binLocation.toOSString();
+
+ // Remove the old .apk.
+ // This make sure that if the apk is corrupted, then dx (which would attempt
+ // to open it), will not fail.
+ String osFinalPackagePath = osBinPath + File.separator + finalPackageName;
+ File finalPackage = new File(osFinalPackagePath);
+
+ // if delete failed, this is not really a problem, as the final package generation
+ // handle already present .apk, and if that one failed as well, the user will be
+ // notified.
+ finalPackage.delete();
+
+ if (configs != null) {
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+ for (Entry<String, String> entry : entrySet) {
+ String packageFilepath = osBinPath + File.separator +
+ ProjectHelper.getApkFilename(project, entry.getKey());
+
+ finalPackage = new File(packageFilepath);
+ finalPackage.delete();
}
-
- // build the default resource package
- if (executeAapt(project, osManifestPath, osResPath,
- osAssetsPath, osBinPath + File.separator +
- AndroidConstants.FN_RESOURCES_AP_, null /*configFilter*/) == false) {
- // aapt failed. Whatever files that needed to be marked
- // have already been marked. We just return.
+ }
+
+ // first we check if we need to package the resources.
+ if (mPackageResources) {
+ // remove some aapt_package only markers.
+ removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
+
+ // need to figure out some path before we can execute aapt;
+
+ // resource to the AndroidManifest.xml file
+ IResource manifestResource = project .findMember(
+ AndroidConstants.WS_SEP + AndroidConstants.FN_ANDROID_MANIFEST);
+
+ if (manifestResource == null
+ || manifestResource.exists() == false) {
+ // mark project and exit
+ String msg = String.format(Messages.s_File_Missing,
+ AndroidConstants.FN_ANDROID_MANIFEST);
+ markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return referencedProjects;
}
-
- // now do the same thing for all the configured resource packages.
- if (configs != null) {
- Set<Entry<String, String>> entrySet = configs.entrySet();
- for (Entry<String, String> entry : entrySet) {
- String outPathFormat = osBinPath + File.separator +
- AndroidConstants.FN_RESOURCES_S_AP_;
- String outPath = String.format(outPathFormat, entry.getKey());
- if (executeAapt(project, osManifestPath, osResPath,
- osAssetsPath, outPath, entry.getValue()) == false) {
- // aapt failed. Whatever files that needed to be marked
- // have already been marked. We just return.
- return referencedProjects;
+
+ // get the resource folder
+ IFolder resFolder = project.getFolder(
+ AndroidConstants.WS_RESOURCES);
+
+ // and the assets folder
+ IFolder assetsFolder = project.getFolder(
+ AndroidConstants.WS_ASSETS);
+
+ // we need to make sure this one exists.
+ if (assetsFolder.exists() == false) {
+ assetsFolder = null;
+ }
+
+ IPath resLocation = resFolder.getLocation();
+ IPath manifestLocation = manifestResource.getLocation();
+
+ if (resLocation != null && manifestLocation != null) {
+ String osResPath = resLocation.toOSString();
+ String osManifestPath = manifestLocation.toOSString();
+
+ String osAssetsPath = null;
+ if (assetsFolder != null) {
+ osAssetsPath = assetsFolder.getLocation().toOSString();
+ }
+
+ // build the default resource package
+ if (executeAapt(project, osManifestPath, osResPath,
+ osAssetsPath, osBinPath + File.separator +
+ AndroidConstants.FN_RESOURCES_AP_, null /*configFilter*/) == false) {
+ // aapt failed. Whatever files that needed to be marked
+ // have already been marked. We just return.
+ return referencedProjects;
+ }
+
+ // now do the same thing for all the configured resource packages.
+ if (configs != null) {
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+ for (Entry<String, String> entry : entrySet) {
+ String outPathFormat = osBinPath + File.separator +
+ AndroidConstants.FN_RESOURCES_S_AP_;
+ String outPath = String.format(outPathFormat, entry.getKey());
+ if (executeAapt(project, osManifestPath, osResPath,
+ osAssetsPath, outPath, entry.getValue()) == false) {
+ // aapt failed. Whatever files that needed to be marked
+ // have already been marked. We just return.
+ return referencedProjects;
+ }
}
}
+
+ // build has been done. reset the state of the builder
+ mPackageResources = false;
+
+ // and store it
+ saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
}
-
+ }
+
+ // then we check if we need to package the .class into classes.dex
+ if (mConvertToDex) {
+ if (executeDx(javaProject, osBinPath, osBinPath + File.separator +
+ AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) {
+ // dx failed, we return
+ return referencedProjects;
+ }
+
// build has been done. reset the state of the builder
- mPackageResources = false;
-
+ mConvertToDex = false;
+
// and store it
- saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
+ saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
}
- }
-
- // then we check if we need to package the .class into classes.dex
- if (mConvertToDex) {
- if (executeDx(javaProject, osBinPath, osBinPath + File.separator +
- AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) {
- // dx failed, we return
+
+ // now we need to make the final package from the intermediary apk
+ // and classes.dex.
+ // This is the default package with all the resources.
+
+ String classesDexPath = osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX;
+ if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
+ classesDexPath,osFinalPackagePath, javaProject,
+ referencedJavaProjects) == false) {
return referencedProjects;
}
-
+
+ // now do the same thing for all the configured resource packages.
+ if (configs != null) {
+ String resPathFormat = osBinPath + File.separator +
+ AndroidConstants.FN_RESOURCES_S_AP_;
+
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+ for (Entry<String, String> entry : entrySet) {
+ // make the filename for the resource package.
+ String resPath = String.format(resPathFormat, entry.getKey());
+
+ // make the filename for the apk to generate
+ String apkOsFilePath = osBinPath + File.separator +
+ ProjectHelper.getApkFilename(project, entry.getKey());
+ if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
+ referencedJavaProjects) == false) {
+ return referencedProjects;
+ }
+ }
+ }
+
+ // we are done.
+
+ // get the resource to bin
+ outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
+
// build has been done. reset the state of the builder
- mConvertToDex = false;
-
+ mBuildFinalPackage = false;
+
// and store it
- saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
- }
-
- // now we need to make the final package from the intermediary apk
- // and classes.dex.
- // This is the default package with all the resources.
-
- String classesDexPath = osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX;
- if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
- classesDexPath,osFinalPackagePath, javaProject,
- referencedJavaProjects) == false) {
- return referencedProjects;
+ saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
+
+ // reset the installation manager to force new installs of this project
+ ApkInstallManager.getInstance().resetInstallationFor(project);
+
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
+ "Build Success!");
}
-
- // now do the same thing for all the configured resource packages.
- if (configs != null) {
- String resPathFormat = osBinPath + File.separator +
- AndroidConstants.FN_RESOURCES_S_AP_;
-
- Set<Entry<String, String>> entrySet = configs.entrySet();
- for (Entry<String, String> entry : entrySet) {
- // make the filename for the resource package.
- String resPath = String.format(resPathFormat, entry.getKey());
-
- // make the filename for the apk to generate
- String apkOsFilePath = osBinPath + File.separator +
- ProjectHelper.getApkFilename(project, entry.getKey());
- if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
- referencedJavaProjects) == false) {
- return referencedProjects;
- }
+ } catch (Exception exception) {
+ // try to catch other exception to actually display an error. This will be useful
+ // if we get an NPE or something so that we can at least notify the user that something
+ // went wrong.
+
+ // first check if this is a CoreException we threw to cancel the build.
+ if (exception instanceof CoreException) {
+ if (((CoreException)exception).getStatus().getCode() == IStatus.CANCEL) {
+ // Project is already marked with an error. Nothing to do
+ return referencedProjects;
}
}
- // we are done.
-
- // get the resource to bin
- outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
-
- // build has been done. reset the state of the builder
- mBuildFinalPackage = false;
-
- // and store it
- saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
-
- // reset the installation manager to force new installs of this project
- ApkInstallManager.getInstance().resetInstallationFor(project);
+ String msg = exception.getMessage();
+ if (msg == null) {
+ msg = exception.getClass().getCanonicalName();
+ }
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
- "Build Success!");
+ msg = String.format("Unknown error: %1$s", msg);
+ AdtPlugin.printErrorToConsole(project, msg);
+ markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
}
+
return referencedProjects;
}
-
@Override
protected void startupOnInitialize() {
super.startupOnInitialize();
@@ -907,6 +933,20 @@ public class ApkBuilder extends BaseBuilder {
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
+ } catch (Exception e) {
+ // try to catch other exception to actually display an error. This will be useful
+ // if we get an NPE or something so that we can at least notify the user that something
+ // went wrong (otherwise the build appears to succeed but the zip archive is not closed
+ // and therefore invalid.
+ String msg = e.getMessage();
+ if (msg == null) {
+ msg = e.getClass().getCanonicalName();
+ }
+
+ msg = String.format("Unknown error: %1$s", msg);
+ AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
+ markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
+ return false;
} finally {
if (fos != null) {
try {
@@ -943,7 +983,8 @@ public class ApkBuilder extends BaseBuilder {
IPath path = resource.getFullPath();
// check the extension.
- if (path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
+ String ext = path.getFileExtension();
+ if (ext != null && ext.equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
// remove the first segment to build the path inside the archive.
path = path.removeFirstSegments(rootSegmentCount);
@@ -954,7 +995,8 @@ public class ApkBuilder extends BaseBuilder {
// writes the file in the apk.
jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString());
}
- } else if (resource.getType() == IResource.FOLDER) {
+ } else if (resource.getType() == IResource.FOLDER &&
+ checkFolderForPackaging((IFolder)resource)) {
IResource[] members = ((IFolder)resource).members();
for (IResource member : members) {
writeNativeLibraries(rootSegmentCount, jarBuilder, member);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
index df023b8..e56f27e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
@@ -271,8 +271,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway.
if (dv != null && dv.mXmlError) {
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
- Messages.Xml_Error);
+ AdtPlugin.printErrorToConsole(project, Messages.Xml_Error);
// This interrupts the build. The next builders will not run.
stopBuild(Messages.Xml_Error);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
index 04393c9..e69c9f0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
@@ -35,6 +35,7 @@ import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserRespo
import com.android.ide.eclipse.adt.project.ApkInstallManager;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.wizards.actions.AvdManagerAction;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.prefs.AndroidLocation.AndroidLocationException;
@@ -64,6 +65,8 @@ import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
import java.io.BufferedReader;
import java.io.IOException;
@@ -465,17 +468,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// we are going to take the closest AVD. ie a compatible AVD that has the API level
// closest to the project target.
- AvdInfo[] avds = avdManager.getValidAvds();
- AvdInfo defaultAvd = null;
- for (AvdInfo avd : avds) {
- if (projectTarget.isCompatibleBaseFor(avd.getTarget())) {
- if (defaultAvd == null ||
- avd.getTarget().getApiVersionNumber() <
- defaultAvd.getTarget().getApiVersionNumber()) {
- defaultAvd = avd;
- }
- }
- }
+ AvdInfo defaultAvd = findMatchingAvd(avdManager, projectTarget);
if (defaultAvd != null) {
response.setAvdToLaunch(defaultAvd);
@@ -487,13 +480,44 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
continueLaunch(response, project, launch, launchInfo, config);
return;
} else {
- // FIXME: ask the user if he wants to create a AVD.
- // we found no compatible AVD.
- AdtPlugin.printErrorToConsole(project, String.format(
- "Failed to find an AVD compatible with target '%1$s'. Launch aborted.",
+ AdtPlugin.printToConsole(project, String.format(
+ "Failed to find an AVD compatible with target '%1$s'.",
projectTarget.getName()));
- stopLaunch(launchInfo);
- return;
+
+ final Display display = AdtPlugin.getDisplay();
+ final boolean[] searchAgain = new boolean[] { false };
+ // ask the user to create a new one.
+ display.syncExec(new Runnable() {
+ public void run() {
+ Shell shell = display.getActiveShell();
+ if (MessageDialog.openQuestion(shell, "Android AVD Error",
+ "No compatible targets were found. Do you wish to a add new Android Virtual Device?")) {
+ AvdManagerAction action = new AvdManagerAction();
+ action.run(null /*action*/);
+ searchAgain[0] = true;
+ }
+ }
+ });
+ if (searchAgain[0]) {
+ // attempt to reload the AVDs and find one compatible.
+ defaultAvd = findMatchingAvd(avdManager, projectTarget);
+
+ if (defaultAvd == null) {
+ AdtPlugin.printErrorToConsole(project, String.format(
+ "Still no compatible AVDs with target '%1$s': Aborting launch.",
+ projectTarget.getName()));
+ stopLaunch(launchInfo);
+ } else {
+ response.setAvdToLaunch(defaultAvd);
+
+ AdtPlugin.printToConsole(project, String.format(
+ "Launching new emulator with compatible AVD '%1$s'",
+ defaultAvd.getName()));
+
+ continueLaunch(response, project, launch, launchInfo, config);
+ return;
+ }
+ }
}
} else if (hasDevice == false && compatibleRunningAvds.size() == 1) {
Entry<IDevice, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next();
@@ -557,6 +581,24 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
});
}
+
+ /**
+ * Find a matching AVD.
+ */
+ private AvdInfo findMatchingAvd(AvdManager avdManager, final IAndroidTarget projectTarget) {
+ AvdInfo[] avds = avdManager.getValidAvds();
+ AvdInfo defaultAvd = null;
+ for (AvdInfo avd : avds) {
+ if (projectTarget.isCompatibleBaseFor(avd.getTarget())) {
+ if (defaultAvd == null ||
+ avd.getTarget().getApiVersionNumber() <
+ defaultAvd.getTarget().getApiVersionNumber()) {
+ defaultAvd = avd;
+ }
+ }
+ }
+ return defaultAvd;
+ }
/**
* Continues the launch based on the DeviceChooser response.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
index f3bd28a..893e095 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DelayedLaunchInfo.java
@@ -136,7 +136,7 @@ public final class DelayedLaunchInfo {
/**
* Returns the Android app process name that the debugger should connect to. Typically this is
- * the same value as {@link getPackageName}
+ * the same value as {@link #getPackageName()}.
*/
public String getDebugPackageName() {
if (mDebugPackageName == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java
index 1bc07fe..52ba42f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java
@@ -27,11 +27,13 @@ import com.android.ddmuilib.ImageHelper;
import com.android.ddmuilib.TableHelper;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.wizards.actions.AvdManagerAction;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.AvdSelector;
+import com.android.sdkuilib.AvdSelector.SelectionMode;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
@@ -53,6 +55,7 @@ import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
@@ -89,7 +92,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
private final IAndroidTarget mProjectTarget;
private final Sdk mSdk;
- private final AvdInfo[] mFullAvdList;
+ private AvdInfo[] mFullAvdList;
private Button mDeviceRadioButton;
@@ -262,14 +265,6 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
mProjectTarget = projectTarget;
mSdk = Sdk.getCurrent();
- // get the full list of Android Virtual Devices
- AvdManager avdManager = mSdk.getAvdManager();
- if (avdManager != null) {
- mFullAvdList = avdManager.getValidAvds();
- } else {
- mFullAvdList = null;
- }
-
loadImages();
}
@@ -310,9 +305,16 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
@Override
protected Control createDialogArea(Composite parent) {
+ // set dialog title
+ getShell().setText("Android Device Chooser");
+
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(1, true));
+ Label label = new Label(top, SWT.NONE);
+ label.setText(String.format("Select a device compatible with target %s.",
+ mProjectTarget.getFullName()));
+
mDeviceRadioButton = new Button(top, SWT.RADIO);
mDeviceRadioButton.setText("Choose a running Android device");
mDeviceRadioButton.addSelectionListener(new SelectionAdapter() {
@@ -326,7 +328,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
if (deviceMode) {
handleDeviceSelection();
} else {
- mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
+ mResponse.setAvdToLaunch(mPreferredAvdSelector.getSelected());
}
enableOkButton();
@@ -344,7 +346,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
offsetComp.setLayout(layout);
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
- mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION);
+ mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER);
GridData gd;
mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.heightHint = 100;
@@ -413,7 +415,25 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
layout.marginLeft = 30;
offsetComp.setLayout(layout);
- mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget);
+ mPreferredAvdSelector = new AvdSelector(offsetComp,
+ getNonRunningAvds(false /*reloadAvds*/),
+ mProjectTarget,
+ new AvdSelector.IExtraAction() {
+ public void run() {
+ AvdManagerAction action = new AvdManagerAction();
+ action.run(null);
+ refillAvdList(true /*reloadAvds*/);
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String label() {
+ return "AVD Manager...";
+ }
+ },
+ SelectionMode.CHECK);
mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setEnabled(false);
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
@@ -424,7 +444,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
@Override
public void widgetSelected(SelectionEvent e) {
if (mDisableAvdSelectionChange == false) {
- mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
+ mResponse.setAvdToLaunch(mPreferredAvdSelector.getSelected());
enableOkButton();
}
}
@@ -446,7 +466,6 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
}
});
- AndroidDebugBridge.addDeviceChangeListener(this);
return top;
}
@@ -529,7 +548,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
// update the display of AvdInfo (since it's filtered to only display
// non running AVD.)
- refillAvdList();
+ refillAvdList(false /*reloadAvds*/);
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
@@ -576,7 +595,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
// update the display of AvdInfo (since it's filtered to only display
// non running AVD). This is done on deviceChanged because the avd name
// of a (emulator) device may be updated as the emulator boots.
- refillAvdList();
+ refillAvdList(false /*reloadAvds*/);
// if the changed device is the current selection,
// we update the OK button based on its state.
@@ -692,19 +711,28 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
/**
* Returns the list of {@link AvdInfo} that are not already running in an emulator.
*/
- private AvdInfo[] getNonRunningAvds() {
+ private AvdInfo[] getNonRunningAvds(boolean reloadAvds) {
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
- Device[] devices = AndroidDebugBridge.getBridge().getDevices();
-
+ // get the full list of Android Virtual Devices
+ if (reloadAvds || mFullAvdList == null) {
+ AvdManager avdManager = mSdk.getAvdManager();
+ if (avdManager != null) {
+ mFullAvdList = avdManager.getValidAvds();
+ }
+ }
+
// loop through all the Avd and put the one that are not running in the list.
- avdLoop: for (AvdInfo info : mFullAvdList) {
- for (Device d : devices) {
- if (info.getName().equals(d.getAvdName())) {
- continue avdLoop;
+ if (mFullAvdList != null) {
+ Device[] devices = AndroidDebugBridge.getBridge().getDevices();
+ avdLoop: for (AvdInfo info : mFullAvdList) {
+ for (Device d : devices) {
+ if (info.getName().equals(d.getAvdName())) {
+ continue avdLoop;
+ }
}
+ list.add(info);
}
- list.add(info);
}
return list.toArray(new AvdInfo[list.size()]);
@@ -713,11 +741,11 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
/**
* Refills the AVD list keeping the current selection.
*/
- private void refillAvdList() {
- AvdInfo[] array = getNonRunningAvds();
+ private void refillAvdList(boolean reloadAvds) {
+ AvdInfo[] array = getNonRunningAvds(reloadAvds);
// save the current selection
- AvdInfo selected = mPreferredAvdSelector.getFirstSelected();
+ AvdInfo selected = mPreferredAvdSelector.getSelected();
// disable selection change.
mDisableAvdSelectionChange = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
index bba7126..fd9a1c2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.launch;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration.TargetMode;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.wizards.actions.AvdManagerAction;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.prefs.AndroidLocation.AndroidLocationException;
@@ -26,6 +27,7 @@ import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.AvdSelector;
+import com.android.sdkuilib.AvdSelector.SelectionMode;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
@@ -92,6 +94,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
private Label mPreferredAvdLabel;
+ private IAndroidTarget mProjectTarget;
+
/**
* Returns the emulator ready speed option value.
* @param value The index of the combo selection.
@@ -187,8 +191,24 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
mPreferredAvdLabel = new Label(offsetComp, SWT.NONE);
mPreferredAvdLabel.setText("Select a preferred Android Virtual Device for deployment:");
- AvdInfo[] avds = new AvdInfo[0];
- mPreferredAvdSelector = new AvdSelector(offsetComp, avds);
+ mPreferredAvdSelector = new AvdSelector(offsetComp,
+ null /*avds*/,
+ new AvdSelector.IExtraAction() {
+ public void run() {
+ AvdManagerAction action = new AvdManagerAction();
+ action.run(null);
+ updateAvdList(null);
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String label() {
+ return "AVD Manager...";
+ }
+ },
+ SelectionMode.CHECK);
mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
@Override
@@ -296,6 +316,21 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
return DdmsPlugin.getImageLoader().loadImage("emulator.png", null); //$NON-NLS-1$
}
+
+ private void updateAvdList(AvdManager avdManager) {
+ if (avdManager == null) {
+ avdManager = Sdk.getCurrent().getAvdManager();
+ }
+
+ AvdInfo[] avds = null;
+ // no project? we don't want to display any "compatible" AVDs.
+ if (avdManager != null && mProjectTarget != null) {
+ avds = avdManager.getValidAvds();
+ }
+
+ mPreferredAvdSelector.setAvds(avds, mProjectTarget);
+ }
+
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
*/
@@ -336,19 +371,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
}
// update the AVD list
- AvdInfo[] avds = null;
- if (avdManager != null) {
- avds = avdManager.getValidAvds();
- }
-
- IAndroidTarget projectTarget = null;
if (project != null) {
- projectTarget = Sdk.getCurrent().getTarget(project);
- } else {
- avds = null; // no project? we don't want to display any "compatible" AVDs.
+ mProjectTarget = Sdk.getCurrent().getTarget(project);
}
-
- mPreferredAvdSelector.setAvds(avds, projectTarget);
+
+ updateAvdList(avdManager);
stringValue = "";
try {
@@ -428,7 +455,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
mAutoTargetButton.getSelection());
- AvdInfo avd = mPreferredAvdSelector.getFirstSelected();
+ AvdInfo avd = mPreferredAvdSelector.getSelected();
if (avd != null) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName());
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
index e091b13..08890a2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
@@ -71,6 +71,22 @@ public final class ProjectHelper {
}
/**
+ * Adds the corresponding source folder to the project's class path entries.
+ *
+ * @param javaProject The java project of which path entries to update.
+ * @param new_entry The parent source folder to remove.
+ * @throws JavaModelException
+ */
+ public static void addEntryToClasspath(
+ IJavaProject javaProject, IClasspathEntry new_entry)
+ throws JavaModelException {
+
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ entries = addEntryToClasspath(entries, new_entry);
+ javaProject.setRawClasspath(entries, new NullProgressMonitor());
+ }
+
+ /**
* Remove a classpath entry from the array.
* @param entries The class path entries to read. A copy will be returned
* @param index The index to remove.
@@ -265,6 +281,8 @@ public final class ProjectHelper {
// If needed, check and fix compiler compliance and source compatibility
ProjectHelper.checkAndFixCompilerCompliance(javaProject);
}
+
+
/**
* Checks the project compiler compliance level is supported.
* @param javaProject The project to check
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
index 7303b02..0a365e6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
@@ -200,7 +200,8 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
label.setText("Configuration:");
mConfigSelector = new ConfigurationSelector(group);
- GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
+ GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
+ gd.horizontalSpan = 2;
gd.widthHint = ConfigurationSelector.WIDTH_HINT;
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
mConfigSelector.setLayoutData(gd);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
index 34391c2..406aa6e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
@@ -29,6 +29,7 @@ import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;
@@ -97,6 +98,7 @@ public class AndroidTargetData {
/**
* Creates an AndroidTargetData object.
+ * @param platformLibraries
* @param optionalLibraries
*/
void setExtraData(IResourceRepository systemResourceRepository,
@@ -110,6 +112,7 @@ public class AndroidTargetData {
String[] broadcastIntentActionValues,
String[] serviceIntentActionValues,
String[] intentCategoryValues,
+ String[] platformLibraries,
IOptionalLibrary[] optionalLibraries,
ProjectResources resources,
LayoutBridge layoutBridge) {
@@ -126,7 +129,7 @@ public class AndroidTargetData {
setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues);
- setOptionalLibraries(optionalLibraries);
+ setOptionalLibraries(platformLibraries, optionalLibraries);
}
public DexWrapper getDexWrapper() {
@@ -276,35 +279,40 @@ public class AndroidTargetData {
* @param permissionValues the list of permissions
*/
private void setPermissions(String[] permissionValues) {
- setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$
+ setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$
setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$
- setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$
- setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$
- setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$
- setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$
+ setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$
+ setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$
+ setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$
+ setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$
}
private void setIntentFilterActionsAndCategories(String[] activityIntentActions,
String[] broadcastIntentActions, String[] serviceIntentActions,
String[] intentCategoryValues) {
- setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$
+ setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$
setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$
- setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
- setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
+ setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
+ setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
}
- private void setOptionalLibraries(IOptionalLibrary[] optionalLibraries) {
- String[] values;
+ private void setOptionalLibraries(String[] platformLibraries,
+ IOptionalLibrary[] optionalLibraries) {
- if (optionalLibraries == null) {
- values = new String[0];
- } else {
- values = new String[optionalLibraries.length];
+ ArrayList<String> libs = new ArrayList<String>();
+
+ if (platformLibraries != null) {
+ for (String name : platformLibraries) {
+ libs.add(name);
+ }
+ }
+
+ if (optionalLibraries != null) {
for (int i = 0; i < optionalLibraries.length; i++) {
- values[i] = optionalLibraries[i].getName();
+ libs.add(optionalLibraries[i].getName());
}
}
- setValues("(uses-library,android:name)", values);
+ setValues("(uses-library,android:name)", libs.toArray(new String[libs.size()]));
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
index 67eec78..588a96b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
@@ -267,6 +267,7 @@ public final class AndroidTargetParser {
broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]),
+ mAndroidTarget.getPlatformLibraries(),
mAndroidTarget.getOptionalLibraries(),
resources,
layoutBridge);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/IUpdateWizardDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/IUpdateWizardDialog.java
new file mode 100755
index 0000000..997c6eb
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/IUpdateWizardDialog.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.ui;
+
+import org.eclipse.jface.wizard.WizardDialog;
+
+
+/**
+ * An interface that enables a client to update {@link WizardDialog} after its creation.
+ */
+public interface IUpdateWizardDialog {
+ /**
+ * Invoked after {@link WizardDialog#create()} to let the caller update the dialog.
+ */
+ public void updateWizardDialog(WizardDialogEx dialog);
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/WizardDialogEx.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/WizardDialogEx.java
new file mode 100755
index 0000000..ba83b25
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/ui/WizardDialogEx.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.ui;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A {@link WizardDialog} that gives access to some inner controls.
+ */
+public final class WizardDialogEx extends WizardDialog {
+
+ /**
+ * @see WizardDialog#WizardDialog(Shell, IWizard)
+ */
+ public WizardDialogEx(Shell parentShell, IWizard newWizard) {
+ super(parentShell, newWizard);
+ }
+
+ /**
+ * Returns the cancel button.
+ * <p/>
+ * Note: there is already a protected, deprecated method that does the same thing.
+ * To avoid overriding a deprecated method, the name as be changed to ...Ex.
+ */
+ public Button getCancelButtonEx() {
+ return getButton(IDialogConstants.CANCEL_ID);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java
new file mode 100755
index 0000000..d3f328e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/AvdManagerAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.wizards.actions;
+
+import com.android.ide.eclipse.adt.wizards.avdmanager.AvdManagerWizard;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IWorkbenchWizard;
+
+/**
+ * Delegate for the toolbar/menu action "Android AVD Manager".
+ * It displays the Android AVD Manager.
+ */
+public class AvdManagerAction extends OpenWizardAction {
+
+ @Override
+ protected IWorkbenchWizard instanciateWizard(IAction action) {
+ return new AvdManagerWizard();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
index e0d0d5e..49766d1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
@@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
@@ -23,7 +23,9 @@ import org.eclipse.ui.IWorkbenchWizard;
/**
* Delegate for the toolbar action "Android Project".
- * It displays the Android New Project wizard.
+ * It displays the Android New Project wizard to create a new Android Project (not a test project).
+ *
+ * @see NewTestProjectAction
*/
public class NewProjectAction extends OpenWizardAction {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java
new file mode 100755
index 0000000..9cdf098
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewTestProjectAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.wizards.actions;
+
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectWizard;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IWorkbenchWizard;
+
+/**
+ * Delegate for the toolbar action "Android Test Project".
+ * It displays the Android New Project wizard to create a new Test Project.
+ */
+public class NewTestProjectAction extends OpenWizardAction {
+
+ @Override
+ protected IWorkbenchWizard instanciateWizard(IAction action) {
+ return new NewTestProjectWizard();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
index d1530d4..3ae66df 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
@@ -22,7 +22,9 @@ import org.eclipse.jface.action.IAction;
import org.eclipse.ui.IWorkbenchWizard;
/**
- * Delegate for the toolbar action "Android Project".
+ * Delegate for the toolbar action "Android Project" or for the
+ * project > Android Project context menu.
+ *
* It displays the Android New XML file wizard.
*/
public class NewXmlFileAction extends OpenWizardAction {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java
deleted file mode 100644
index 20cfc82..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileWizardAction.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.wizards.actions;
-
-import com.android.ide.eclipse.adt.wizards.newxmlfile.NewXmlFileWizard;
-
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.wizard.WizardDialog;
-import org.eclipse.ui.IObjectActionDelegate;
-import org.eclipse.ui.IWorkbench;
-import org.eclipse.ui.IWorkbenchPart;
-
-public class NewXmlFileWizardAction implements IObjectActionDelegate {
-
- private ISelection mSelection;
- private IWorkbench mWorkbench;
-
- /**
- * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
- */
- public void setActivePart(IAction action, IWorkbenchPart targetPart) {
- mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
- }
-
- public void run(IAction action) {
- if (mSelection instanceof IStructuredSelection) {
- IStructuredSelection selection = (IStructuredSelection)mSelection;
-
- // call the new xml file wizard on the current selection.
- NewXmlFileWizard wizard = new NewXmlFileWizard();
- wizard.init(mWorkbench, selection);
- WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(),
- wizard);
- dialog.open();
- }
- }
-
- public void selectionChanged(IAction action, ISelection selection) {
- this.mSelection = selection;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
index 4fc9dee..c905d6a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
@@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
@@ -16,15 +16,19 @@
package com.android.ide.eclipse.adt.wizards.actions;
+import com.android.ide.eclipse.adt.ui.IUpdateWizardDialog;
+import com.android.ide.eclipse.adt.ui.WizardDialogEx;
+
import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
@@ -40,7 +44,8 @@ import org.eclipse.ui.internal.util.Util;
* An abstract action that displays one of our wizards.
* Derived classes must provide the actual wizard to display.
*/
-/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate {
+/*package*/ abstract class OpenWizardAction
+ implements IWorkbenchWindowActionDelegate, IObjectActionDelegate {
/**
* The wizard dialog width, extracted from {@link NewWizardShortcutAction}
@@ -52,7 +57,25 @@ import org.eclipse.ui.internal.util.Util;
*/
private static final int SIZING_WIZARD_HEIGHT = 500;
-
+ /** The wizard that was created by {@link #run(IAction)}. */
+ private IWorkbenchWizard mWizard;
+ /** The result from the dialog */
+ private int mDialogResult;
+
+ private ISelection mSelection;
+ private IWorkbench mWorkbench;
+
+ /** Returns the wizard that was created by {@link #run(IAction)}. */
+ public IWorkbenchWizard getWizard() {
+ return mWizard;
+ }
+
+ /** Returns the result from {@link Dialog#open()}, available after
+ * the completion of {@link #run(IAction)}. */
+ public int getDialogResult() {
+ return mDialogResult;
+ }
+
/* (non-Javadoc)
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
*/
@@ -71,18 +94,23 @@ import org.eclipse.ui.internal.util.Util;
* Opens and display the Android New Project Wizard.
* <p/>
* Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}.
- *
+ *
+ * @param action The action that got us here. Can be null when used internally.
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run(IAction action) {
// get the workbench and the current window
- IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbench workbench = mWorkbench != null ? mWorkbench : PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
-
+
// This code from NewWizardShortcutAction#run() gets the current window selection
// and converts it to a workbench structured selection for the wizard, if possible.
- ISelection selection = window.getSelectionService().getSelection();
+ ISelection selection = mSelection;
+ if (selection == null) {
+ selection = window.getSelectionService().getSelection();
+ }
+
IStructuredSelection selectionToPass = StructuredSelection.EMPTY;
if (selection instanceof IStructuredSelection) {
selectionToPass = (IStructuredSelection) selection;
@@ -102,14 +130,18 @@ import org.eclipse.ui.internal.util.Util;
}
// Create the wizard and initialize it with the selection
- IWorkbenchWizard wizard = instanciateWizard(action);
- wizard.init(workbench, selectionToPass);
-
+ mWizard = instanciateWizard(action);
+ mWizard.init(workbench, selectionToPass);
+
// It's not visible yet until a dialog is created and opened
Shell parent = window.getShell();
- WizardDialog dialog = new WizardDialog(parent, wizard);
+ WizardDialogEx dialog = new WizardDialogEx(parent, mWizard);
dialog.create();
-
+
+ if (mWizard instanceof IUpdateWizardDialog) {
+ ((IUpdateWizardDialog) mWizard).updateWizardDialog(dialog);
+ }
+
// This code comes straight from NewWizardShortcutAction#run()
Point defaultSize = dialog.getShell().getSize();
dialog.getShell().setSize(
@@ -117,14 +149,15 @@ import org.eclipse.ui.internal.util.Util;
Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y));
window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(),
IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT);
-
- dialog.open();
+
+ mDialogResult = dialog.open();
}
/**
* Called by {@link #run(IAction)} to instantiate the actual wizard.
- *
+ *
* @param action The action parameter from {@link #run(IAction)}.
+ * This can be null.
* @return A new wizard instance. Must not be null.
*/
protected abstract IWorkbenchWizard instanciateWizard(IAction action);
@@ -133,7 +166,13 @@ import org.eclipse.ui.internal.util.Util;
* @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
*/
public void selectionChanged(IAction action, ISelection selection) {
- // pass
+ mSelection = selection;
}
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IObjectActionDelegate#setActivePart(org.eclipse.jface.action.IAction, org.eclipse.ui.IWorkbenchPart)
+ */
+ public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+ mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java
new file mode 100755
index 0000000..ba5d0a2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerListPage.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.ide.eclipse.adt.wizards.avdmanager;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
+import com.android.sdkuilib.AvdSelector;
+import com.android.sdkuilib.AvdSelector.SelectionMode;
+
+import org.eclipse.core.resources.IProject;
+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.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.TreeMap;
+
+/**
+ * This is the single page of the {@link AvdManagerWizard} which provides the ability to display
+ * the AVDs and edit them quickly.
+ */
+class AvdManagerListPage extends WizardPage {
+
+ private AvdSelector mAvdSelector;
+ private Button mRefreshButton;
+ private Text mCreateName;
+ private Combo mCreateTargetCombo;
+ private Text mCreateSdCard;
+ private Combo mCreateSkinCombo;
+ private Button mCreateForce;
+ private Button mCreateButton;
+ private HashSet<String> mKnownAvdNames = new HashSet<String>();
+ private TreeMap<String, IAndroidTarget> mCurrentTargets = new TreeMap<String, IAndroidTarget>();
+ private ITargetChangeListener mSdkTargetChangeListener;
+
+ /**
+ * Constructs a new {@link AvdManagerListPage}.
+ * <p/>
+ * Called by {@link AvdManagerWizard#createMainPage()}.
+ */
+ protected AvdManagerListPage(String pageName) {
+ super(pageName);
+ setPageComplete(false);
+ }
+
+ /**
+ * Called by the parent Wizard to create the UI for this Wizard Page.
+ *
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
+ */
+ public void createControl(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NULL);
+ composite.setFont(parent.getFont());
+
+ initializeDialogUnits(parent);
+
+ composite.setLayout(new GridLayout(1, false /*makeColumnsEqualWidth*/));
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ createAvdGroup(composite);
+ createCreateGroup(composite);
+ registerSdkChangeListener();
+
+ // Show description the first time
+ setErrorMessage(null);
+ setMessage(null);
+ setControl(composite);
+
+ // Update state the first time
+ reloadAvdList();
+ reloadTargetCombo();
+ validatePage();
+ }
+
+ private void registerSdkChangeListener() {
+
+ mSdkTargetChangeListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ public void onTargetsLoaded() {
+ // Update the AVD list, since the SDK change will influence the "good" avd list
+ reloadAvdList();
+ // Update the sdk target combo with the new targets
+ reloadTargetCombo();
+ validatePage();
+ }
+ };
+ AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
+
+ }
+
+ @Override
+ public void dispose() {
+ if (mSdkTargetChangeListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
+ mSdkTargetChangeListener = null;
+ }
+
+ super.dispose();
+ }
+
+ // --- UI creation ---
+
+ /**
+ * Creates the AVD selector and refresh & delete buttons.
+ */
+ private void createAvdGroup(Composite parent) {
+ final Composite grid2 = new Composite(parent, SWT.NONE);
+ grid2.setLayout(new GridLayout(2, false /*makeColumnsEqualWidth*/));
+ grid2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ Label label = new Label(grid2, SWT.NONE);
+ label.setText("List of existing Android Virtual Devices:");
+ label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
+
+ mRefreshButton = new Button(grid2, SWT.PUSH);
+ mRefreshButton.setText("Refresh");
+ mRefreshButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
+ mRefreshButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ reloadAvdList();
+ }
+ });
+
+ mAvdSelector = new AvdSelector(parent,
+ SelectionMode.SELECT,
+ new AvdSelector.IExtraAction() {
+ public String label() {
+ return "Delete AVD...";
+ }
+
+ public boolean isEnabled() {
+ return mAvdSelector != null && mAvdSelector.getSelected() != null;
+ }
+
+ public void run() {
+ onDelete();
+ }
+ });
+ }
+
+ /**
+ * Creates the "Create" group
+ */
+ private void createCreateGroup(Composite parent) {
+
+ Group grid = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ grid.setLayout(new GridLayout(4, false /*makeColumnsEqualWidth*/));
+ grid.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ grid.setFont(parent.getFont());
+ grid.setText("Create AVD");
+
+ // first line
+
+ Label label = new Label(grid, SWT.NONE);
+ label.setText("Name");
+
+ mCreateName = new Text(grid, SWT.BORDER);
+ mCreateName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mCreateName.addModifyListener(new CreateNameModifyListener());
+
+ label = new Label(grid, SWT.NONE);
+ label.setText("Target");
+
+ mCreateTargetCombo = new Combo(grid, SWT.READ_ONLY | SWT.DROP_DOWN);
+ mCreateTargetCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mCreateTargetCombo.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ reloadSkinCombo();
+ validatePage();
+ }
+ });
+
+ // second line
+
+ label = new Label(grid, SWT.NONE);
+ label.setText("SDCard");
+
+ ValidateListener validateListener = new ValidateListener();
+
+ mCreateSdCard = new Text(grid, SWT.BORDER);
+ mCreateSdCard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mCreateSdCard.addModifyListener(validateListener);
+
+ label = new Label(grid, SWT.NONE);
+ label.setText("Skin");
+
+ mCreateSkinCombo = new Combo(grid, SWT.READ_ONLY | SWT.DROP_DOWN);
+ mCreateSkinCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ // dummies for alignment
+ label = new Label(grid, SWT.NONE);
+ label = new Label(grid, SWT.NONE);
+
+ mCreateForce = new Button(grid, SWT.CHECK);
+ mCreateForce.setText("Force");
+ mCreateForce.setEnabled(false);
+ mCreateForce.addSelectionListener(validateListener);
+
+ mCreateButton = new Button(grid, SWT.PUSH);
+ mCreateButton.setText("Create AVD");
+ mCreateButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
+ mCreateButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ onCreate();
+ }
+ });
+ }
+
+ /**
+ * Callback when the AVD name is changed.
+ * Enables the force checkbox if the name is a duplicate.
+ */
+ private class CreateNameModifyListener implements ModifyListener {
+
+ public void modifyText(ModifyEvent e) {
+ String name = mCreateName.getText().trim();
+ if (mKnownAvdNames.contains(name)) {
+ mCreateForce.setEnabled(true);
+ } else {
+ mCreateForce.setEnabled(false);
+ mCreateForce.setSelection(false);
+ }
+ validatePage();
+ }
+ }
+
+ private class ValidateListener extends SelectionAdapter implements ModifyListener {
+
+ public void modifyText(ModifyEvent e) {
+ validatePage();
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ validatePage();
+ }
+ }
+
+ private void reloadTargetCombo() {
+
+ String selected = null;
+ int index = mCreateTargetCombo.getSelectionIndex();
+ if (index >= 0) {
+ selected = mCreateTargetCombo.getItem(index);
+ }
+
+ mCurrentTargets.clear();
+ mCreateTargetCombo.removeAll();
+
+ boolean found = false;
+ index = -1;
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ for (IAndroidTarget target : sdk.getTargets()) {
+ String name = String.format("%s - %s",
+ target.getName(),
+ target.getApiVersionName());
+ mCurrentTargets.put(name, target);
+ mCreateTargetCombo.add(name);
+ if (!found) {
+ index++;
+ found = name.equals(selected);
+ }
+ }
+ }
+
+ mCreateTargetCombo.setEnabled(mCurrentTargets.size() > 0);
+
+ if (found) {
+ mCreateTargetCombo.select(index);
+ }
+
+ reloadSkinCombo();
+ }
+
+ private void reloadSkinCombo() {
+ String selected = null;
+ int index = mCreateSkinCombo.getSelectionIndex();
+ if (index >= 0) {
+ selected = mCreateSkinCombo.getItem(index);
+ }
+
+ mCreateSkinCombo.removeAll();
+ mCreateSkinCombo.setEnabled(false);
+
+ index = mCreateTargetCombo.getSelectionIndex();
+ if (index >= 0) {
+
+ String targetName = mCreateTargetCombo.getItem(index);
+
+ boolean found = false;
+ IAndroidTarget target = mCurrentTargets.get(targetName);
+ if (target != null) {
+ mCreateSkinCombo.add(String.format("Default (%s)", target.getDefaultSkin()));
+
+ index = -1;
+ for (String skin : target.getSkins()) {
+ mCreateSkinCombo.add(skin);
+ if (!found) {
+ index++;
+ found = skin.equals(selected);
+ }
+ }
+
+ mCreateSkinCombo.setEnabled(true);
+
+ if (found) {
+ mCreateSkinCombo.select(index);
+ } else {
+ mCreateSkinCombo.select(0); // default
+ }
+ }
+ }
+ }
+
+ /**
+ * Validates the fields, displays errors and warnings.
+ * Enables the finish button if there are no errors.
+ * <p/>
+ * Not really used here yet. Keep as a placeholder.
+ */
+ private void validatePage() {
+ String error = null;
+ String warning = null;
+
+
+ // Validate AVD name
+ String avdName = mCreateName.getText().trim();
+ boolean hasAvdName = avdName.length() > 0;
+ if (hasAvdName && !AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
+ error = String.format(
+ "AVD name '%1$s' contains invalid characters. Allowed characters are: %2$s",
+ avdName, AvdManager.CHARS_AVD_NAME);
+ }
+
+ // Validate target
+ if (hasAvdName && error == null && mCreateTargetCombo.getSelectionIndex() < 0) {
+ error = "A target must be selected in order to create an AVD.";
+ }
+
+ // Validate SDCard path or value
+ if (error == null) {
+ String sdName = mCreateSdCard.getText().trim();
+
+ if (sdName.length() > 0 &&
+ !new File(sdName).isFile() &&
+ !AvdManager.SDCARD_SIZE_PATTERN.matcher(sdName).matches()) {
+ error = "SD Card must be either a file path or a size such as 128K or 64M.";
+ }
+ }
+
+ // Check for duplicate AVD name
+ if (hasAvdName && error == null) {
+ if (mKnownAvdNames.contains(avdName) && !mCreateForce.getSelection()) {
+ error = String.format(
+ "The AVD name '%s' is already used. " +
+ "Check \"Force\" if you really want to create a new AVD with that name and delete the previous one.",
+ avdName);
+ }
+ }
+
+ // Validate the create button
+ boolean can_create = hasAvdName && error == null;
+ if (can_create) {
+ can_create &= mCreateTargetCombo.getSelectionIndex() >= 0;
+ }
+ mCreateButton.setEnabled(can_create);
+
+ // -- update UI
+ setPageComplete(true);
+ if (error != null) {
+ setMessage(error, WizardPage.ERROR);
+ } else if (warning != null) {
+ setMessage(warning, WizardPage.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ /**
+ * Reloads the AVD list in the AVD selector.
+ * Tries to preserve the selection.
+ */
+ private void reloadAvdList() {
+ AvdInfo selected = mAvdSelector.getSelected();
+
+ AvdManager avdm = getAvdManager();
+ AvdInfo[] avds = null;
+
+ // For the AVD manager to reload the list, in case AVDs where created using the
+ // command line tool.
+ // The AVD manager may not exist yet, typically when loading the SDK.
+ if (avdm != null) {
+ try {
+ avdm.reloadAvds();
+ } catch (AndroidLocationException e) {
+ AdtPlugin.log(e, "AVD Manager reload failed"); //$NON-NLS-1$
+ }
+
+ avds = avdm.getValidAvds();
+ }
+
+ mAvdSelector.setAvds(avds, null /*filter*/);
+
+ // Keep the list of known AVD names to check if they exist quickly. however
+ // use the list of all AVDs, including broken ones (unless we don't know their
+ // name).
+ mKnownAvdNames.clear();
+ if (avdm != null) {
+ for (AvdInfo avd : avdm.getAllAvds()) {
+ String name = avd.getName();
+ if (name != null) {
+ mKnownAvdNames.add(name);
+ }
+ }
+ }
+
+ mAvdSelector.setSelection(selected);
+ }
+
+ /**
+ * Triggered when the user selects the "delete" button (the extra action in the selector)
+ * Deletes the currently selected AVD, if any.
+ */
+ private void onDelete() {
+ AvdInfo avdInfo = mAvdSelector.getSelected();
+ AvdManager avdm = getAvdManager();
+ if (avdInfo == null || avdm == null) {
+ return;
+ }
+
+ // Confirm you want to delete this AVD
+ if (!AdtPlugin.displayPrompt("Delete Android Virtual Device",
+ String.format("Please confirm that you want to delete the Android Virtual Device named '%s'. This operation cannot be reverted.",
+ avdInfo.getName()))) {
+ return;
+ }
+
+ SdkLog log = new SdkLog(String.format("Result of deleting AVD '%s':", avdInfo.getName()));
+
+ boolean success = avdm.deleteAvd(avdInfo, log);
+
+ log.display(success);
+ reloadAvdList();
+ }
+
+ /**
+ * Triggered when the user selects the "create" button.
+ */
+ private void onCreate() {
+ String avdName = mCreateName.getText().trim();
+ String sdName = mCreateSdCard.getText().trim();
+ int targetIndex = mCreateTargetCombo.getSelectionIndex();
+ int skinIndex = mCreateSkinCombo.getSelectionIndex();
+ boolean force = mCreateForce.getSelection();
+ AvdManager avdm = getAvdManager();
+
+ if (avdm == null ||
+ avdName.length() == 0 ||
+ targetIndex < 0) {
+ return;
+ }
+
+ String targetName = mCreateTargetCombo.getItem(targetIndex);
+ IAndroidTarget target = mCurrentTargets.get(targetName);
+ if (target == null) {
+ return;
+ }
+
+ String skinName = null;
+ if (skinIndex > 0) {
+ // index 0 is the default, we don't use it
+ skinName = mCreateSkinCombo.getItem(skinIndex);
+ }
+
+ SdkLog log = new SdkLog(String.format("Result of creating AVD '%s':", avdName));
+
+ File avdFolder;
+ try {
+ avdFolder = new File(
+ AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ avdName + AvdManager.AVD_FOLDER_EXTENSION);
+ } catch (AndroidLocationException e) {
+ AdtPlugin.logAndPrintError(e, null /*tag*/,
+ "AndroidLocation.getFolder failed"); //$NON-NLS-1$
+ return;
+ }
+
+ ISdkLog oldLog = null;
+ boolean success = false;
+ try {
+ // Temporarily change the AvdManager's logger for ours, since the API no longer
+ // takes a logger argument.
+ // TODO revisit this later. See comments in AvdManager#mSdkLog.
+ oldLog = avdm.setSdkLog(log);
+
+ AvdInfo avdInfo = avdm.createAvd(
+ avdFolder,
+ avdName,
+ target,
+ skinName,
+ sdName,
+ null, // hardwareConfig,
+ force);
+
+ success = avdInfo != null;
+
+ } finally {
+ avdm.setSdkLog(oldLog);
+ }
+
+ log.display(success);
+
+ if (success) {
+ // clear the name field on success
+ mCreateName.setText(""); //$NON-NLS-1$
+ }
+
+ reloadAvdList();
+ }
+
+ /**
+ * Collects all log from the AVD action and displays it in a dialog.
+ */
+ private class SdkLog implements ISdkLog {
+
+ final ArrayList<String> logMessages = new ArrayList<String>();
+ private final String mMessage;
+
+ public SdkLog(String message) {
+ mMessage = message;
+ }
+
+ public void error(Throwable throwable, String errorFormat, Object... arg) {
+ if (errorFormat != null) {
+ logMessages.add(String.format("Error: " + errorFormat, arg));
+ }
+
+ if (throwable != null) {
+ logMessages.add(throwable.getMessage());
+ }
+ }
+
+ public void warning(String warningFormat, Object... arg) {
+ logMessages.add(String.format("Warning: " + warningFormat, arg));
+ }
+
+ public void printf(String msgFormat, Object... arg) {
+ logMessages.add(String.format(msgFormat, arg));
+ }
+
+ /**
+ * Displays the log if anything was captured.
+ */
+ public void display(boolean success) {
+ if (logMessages.size() > 0) {
+ StringBuilder sb = new StringBuilder(mMessage + "\n");
+ for (String msg : logMessages) {
+ sb.append('\n');
+ sb.append(msg);
+ }
+ if (success) {
+ AdtPlugin.displayWarning("Android Virtual Devices Manager", sb.toString());
+ } else {
+ AdtPlugin.displayError("Android Virtual Devices Manager", sb.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the current AVD Manager or null if none has been created yet.
+ * This can happen when the SDK hasn't finished loading or the manager failed to
+ * parse the AVD directory.
+ */
+ private AvdManager getAvdManager() {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ return sdk.getAvdManager();
+ }
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java
new file mode 100755
index 0000000..19ceed8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/avdmanager/AvdManagerWizard.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package com.android.ide.eclipse.adt.wizards.avdmanager;
+
+import com.android.ide.eclipse.adt.ui.IUpdateWizardDialog;
+import com.android.ide.eclipse.adt.ui.WizardDialogEx;
+import com.android.ide.eclipse.editors.IconFactory;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbench;
+
+/**
+ * The "AVD Manager Wizard" provides a quick way to edit AVDs.
+ * <p/>
+ * The wizard has one page, {@link AvdManagerListPage}, used to display and edit the AVDs.
+ * In fact the whole UI is not really a wizard. It has just been implemented that way
+ * to get something quick out of the door. We'll need to revisit this when we implement
+ * the final standalone AVD Manager UI and this Wizard will go away.
+ */
+public class AvdManagerWizard extends Wizard implements INewWizard, IUpdateWizardDialog {
+
+ private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$
+
+ protected static final String MAIN_PAGE_NAME = "avdManagerListPage"; //$NON-NLS-1$
+
+ private AvdManagerListPage mMainPage;
+
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ setHelpAvailable(false); // TODO have help
+ setWindowTitle("Android Virtual Devices Manager");
+ setImageDescriptor();
+
+ mMainPage = createMainPage();
+ mMainPage.setTitle("Android Virtual Devices Manager");
+ mMainPage.setDescription("Displays existing Android Virtual Devices. Lets you create new ones or delete existing ones.");
+ }
+
+ /**
+ * Creates the wizard page.
+ * <p/>
+ * Please do NOT override this method.
+ * <p/>
+ * This is protected so that it can be overridden by unit tests.
+ * However the contract of this class is private and NO ATTEMPT will be made
+ * to maintain compatibility between different versions of the plugin.
+ */
+ protected AvdManagerListPage createMainPage() {
+ return new AvdManagerListPage(MAIN_PAGE_NAME);
+ }
+
+ // -- Methods inherited from org.eclipse.jface.wizard.Wizard --
+ //
+ // The Wizard class implements most defaults and boilerplate code needed by
+ // IWizard
+
+ /**
+ * Adds pages to this wizard.
+ */
+ @Override
+ public void addPages() {
+ addPage(mMainPage);
+ }
+
+ /**
+ * Performs any actions appropriate in response to the user having pressed
+ * the Finish button, or refuse if finishing now is not permitted: here, it does nothing.
+ *
+ * @return True
+ */
+ @Override
+ public boolean performFinish() {
+ return true;
+ }
+
+ /**
+ * Returns an image descriptor for the wizard logo.
+ */
+ private void setImageDescriptor() {
+ ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE);
+ setDefaultPageImageDescriptor(desc);
+ }
+
+ /**
+ * Invoked once the dialog frame as been created.
+ * We use it to hide the cancel button, which looks odd here.
+ */
+ public void updateWizardDialog(WizardDialogEx dialog) {
+ Button cancel = dialog.getCancelButtonEx();
+ if (cancel != null) {
+ cancel.setVisible(false);
+ }
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
index 20aa68b..e666390 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java
@@ -25,6 +25,7 @@ package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
@@ -79,11 +80,13 @@ import java.util.regex.Pattern;
* Note: this class is public so that it can be accessed from unit tests.
* It is however an internal class. Its API may change without notice.
* It should semantically be considered as a private final class.
- * Do not derive from this class.
+ * Do not derive from this class.
*/
public class NewProjectCreationPage extends WizardPage {
// constants
+ private static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
+
/** Initial value for all name fields (project, activity, application, package). Used
* whenever a value is requested before controls are created. */
private static final String INITIAL_NAME = ""; //$NON-NLS-1$
@@ -94,7 +97,7 @@ public class NewProjectCreationPage extends WizardPage {
private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
/** Initial value for the Create Activity check box. */
private static final boolean INITIAL_CREATE_ACTIVITY = true;
-
+
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose. It cannot start with a space. */
@@ -106,7 +109,13 @@ public class NewProjectCreationPage extends WizardPage {
private final int MSG_NONE = 0;
private final int MSG_WARNING = 1;
private final int MSG_ERROR = 2;
-
+
+ /** Structure with the externally visible information from this Main Project page. */
+ private final MainInfo mInfo = new MainInfo();
+ /** Structure with the externally visible information from the Test Project page.
+ * This is null if there's no such page, meaning the main project page is standalone. */
+ private TestInfo mTestInfo;
+
private String mUserPackageName = ""; //$NON-NLS-1$
private String mUserActivityName = ""; //$NON-NLS-1$
private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY;
@@ -128,98 +137,160 @@ public class NewProjectCreationPage extends WizardPage {
private ITargetChangeListener mSdkTargetChangeListener;
private boolean mInternalLocationPathUpdate;
- protected boolean mInternalProjectNameUpdate;
- protected boolean mInternalApplicationNameUpdate;
+ private boolean mInternalProjectNameUpdate;
+ private boolean mInternalApplicationNameUpdate;
private boolean mInternalCreateActivityUpdate;
private boolean mInternalActivityNameUpdate;
- protected boolean mProjectNameModifiedByUser;
- protected boolean mApplicationNameModifiedByUser;
+ private boolean mProjectNameModifiedByUser;
+ private boolean mApplicationNameModifiedByUser;
private boolean mInternalMinSdkVersionUpdate;
- private boolean mMinSdkVersionModifiedByUser;
/**
* Creates a new project creation wizard page.
- *
- * @param pageName the name of this page
*/
- public NewProjectCreationPage(String pageName) {
- super(pageName);
+ public NewProjectCreationPage() {
+ super(MAIN_PAGE_NAME);
setPageComplete(false);
+ setTitle("New Android Project");
+ setDescription("Creates a new Android Project resource.");
}
// --- Getters used by NewProjectWizard ---
+
/**
- * Returns the current project location path as entered by the user, or its
- * anticipated initial value. Note that if the default has been returned the
- * path in a project description used to create a project should not be set.
- *
- * @return the project location path or its anticipated initial value.
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ * <p/>
+ * This interface is provided so that the adt-test counterpart can override the returned
+ * information.
*/
- public IPath getLocationPath() {
- return new Path(getProjectLocation());
+ public interface IMainInfo {
+ public IPath getLocationPath();
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName();
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName();
+ /** Returns the value of the activity name field with spaces trimmed. */
+ public String getActivityName();
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion();
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName();
+ /** Returns the value of the "Create New Project" radio. */
+ public boolean isNewProject();
+ /** Returns the value of the "Create Activity" checkbox. */
+ public boolean isCreateActivity();
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation();
+ /** Returns the internal source folder (for the "existing project" mode) or the default
+ * "src" constant. */
+ public String getSourceFolder();
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget();
}
- /** Returns the value of the project name field with leading and trailing spaces removed. */
- public String getProjectName() {
- return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
- }
- /** Returns the value of the package name field with spaces trimmed. */
- public String getPackageName() {
- return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
- }
+ /**
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public class MainInfo implements IMainInfo {
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ public IPath getLocationPath() {
+ return new Path(getProjectLocation());
+ }
- /** Returns the value of the activity name field with spaces trimmed. */
- public String getActivityName() {
- return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim();
- }
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName() {
+ return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
+ }
- /** Returns the value of the min sdk version field with spaces trimmed. */
- public String getMinSdkVersion() {
- return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim();
- }
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName() {
+ return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
+ }
- /** Returns the value of the application name field with spaces trimmed. */
- public String getApplicationName() {
- // Return the name of the activity as default application name.
- return mApplicationNameField == null ? getActivityName()
- : mApplicationNameField.getText().trim();
+ /** Returns the value of the activity name field with spaces trimmed. */
+ public String getActivityName() {
+ return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim();
+ }
- }
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion() {
+ return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$
+ }
- /** Returns the value of the "Create New Project" radio. */
- public boolean isNewProject() {
- return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT
- : mCreateNewProjectRadio.getSelection();
- }
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName() {
+ // Return the name of the activity as default application name.
+ return mApplicationNameField == null ? getActivityName()
+ : mApplicationNameField.getText().trim();
- /** Returns the value of the "Create Activity" checkbox. */
- public boolean isCreateActivity() {
- return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
- : mCreateActivityCheck.getSelection();
- }
+ }
- /** Returns the value of the Use Default Location field. */
- public boolean useDefaultLocation() {
- return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
- : mUseDefaultLocation.getSelection();
- }
+ /** Returns the value of the "Create New Project" radio. */
+ public boolean isNewProject() {
+ return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT
+ : mCreateNewProjectRadio.getSelection();
+ }
- /** Returns the internal source folder (for the "existing project" mode) or the default
- * "src" constant. */
- public String getSourceFolder() {
- if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) {
- return SdkConstants.FD_SOURCES;
- } else {
- return mSourceFolder;
+ /** Returns the value of the "Create Activity" checkbox. */
+ public boolean isCreateActivity() {
+ return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY
+ : mCreateActivityCheck.getSelection();
+ }
+
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation() {
+ return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
+ : mUseDefaultLocation.getSelection();
}
+
+ /** Returns the internal source folder (for the "existing project" mode) or the default
+ * "src" constant. */
+ public String getSourceFolder() {
+ if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) {
+ return SdkConstants.FD_SOURCES;
+ } else {
+ return mSourceFolder;
+ }
+ }
+
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget() {
+ return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+ }
+ }
+
+ /**
+ * Returns a {@link MainInfo} structure that collects all externally visible information
+ * from this page, to be used by the calling wizard or by other pages.
+ */
+ public IMainInfo getMainInfo() {
+ return mInfo;
}
-
- /** Returns the current sdk target or null if none has been selected yet. */
- public IAndroidTarget getSdkTarget() {
- return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+
+ /**
+ * Grabs the {@link TestInfo} structure that collects externally visible fields from the
+ * test project page. This may be null.
+ */
+ public void setTestInfo(TestInfo testInfo) {
+ mTestInfo = testInfo;
}
/**
@@ -231,6 +302,7 @@ public class NewProjectCreationPage extends WizardPage {
super.setVisible(visible);
if (visible) {
mProjectNameField.setFocus();
+ validatePageComplete();
}
}
@@ -265,17 +337,17 @@ public class NewProjectCreationPage extends WizardPage {
setControl(composite);
// Validate. This will complain about the first empty field.
- setPageComplete(validatePage());
+ validatePageComplete();
}
-
+
@Override
public void dispose() {
-
+
if (mSdkTargetChangeListener != null) {
AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
mSdkTargetChangeListener = null;
}
-
+
super.dispose();
}
@@ -328,7 +400,7 @@ public class NewProjectCreationPage extends WizardPage {
Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
// Layout has 4 columns of non-equal size
group.setLayout(new GridLayout());
- group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
group.setFont(parent.getFont());
group.setText("Contents");
@@ -349,7 +421,7 @@ public class NewProjectCreationPage extends WizardPage {
super.widgetSelected(e);
enableLocationWidgets();
extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
+ validatePageComplete();
}
};
@@ -358,9 +430,9 @@ public class NewProjectCreationPage extends WizardPage {
mUseDefaultLocation.addSelectionListener(location_listener);
Composite location_group = new Composite(group, SWT.NONE);
- location_group.setLayout(new GridLayout(4, /* num columns */
+ location_group.setLayout(new GridLayout(3, /* num columns */
false /* columns of not equal size */));
- location_group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ location_group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
location_group.setFont(parent.getFont());
mLocationLabel = new Label(location_group, SWT.NONE);
@@ -371,7 +443,7 @@ public class NewProjectCreationPage extends WizardPage {
GridData.BEGINNING, /* vertical alignment */
true, /* grabExcessHorizontalSpace */
false, /* grabExcessVerticalSpace */
- 2, /* horizontalSpan */
+ 1, /* horizontalSpan */
1); /* verticalSpan */
mLocationPathField.setLayoutData(data);
mLocationPathField.setFont(parent.getFont());
@@ -387,7 +459,7 @@ public class NewProjectCreationPage extends WizardPage {
mBrowseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- openDirectoryBrowser();
+ onOpenDirectoryBrowser();
}
});
}
@@ -403,7 +475,7 @@ public class NewProjectCreationPage extends WizardPage {
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
group.setText("Build Target");
-
+
// The selector is created without targets. They are added below in the change listener.
mSdkTargetSelector = new SdkTargetSelector(group, null);
@@ -428,52 +500,23 @@ public class NewProjectCreationPage extends WizardPage {
}
}
};
-
+
AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
-
+
// Invoke it once to initialize the targets
mSdkTargetChangeListener.onTargetsLoaded();
-
+
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSdkTargetModified();
updateLocationPathField(null);
- setPageComplete(validatePage());
+ validatePageComplete();
}
});
}
/**
- * Display a directory browser and update the location path field with the selected path
- */
- private void openDirectoryBrowser() {
-
- String existing_dir = getLocationPathFieldValue();
-
- // Disable the path if it doesn't exist
- if (existing_dir.length() == 0) {
- existing_dir = null;
- } else {
- File f = new File(existing_dir);
- if (!f.exists()) {
- existing_dir = null;
- }
- }
-
- DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
- dd.setMessage("Browse for folder");
- dd.setFilterPath(existing_dir);
- String abs_dir = dd.open();
-
- if (abs_dir != null) {
- updateLocationPathField(abs_dir);
- extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
- }
- }
-
- /**
* Creates the group for the project properties:
* - Package name [text field]
* - Activity name [text field]
@@ -508,7 +551,7 @@ public class NewProjectCreationPage extends WizardPage {
if (!mInternalApplicationNameUpdate) {
mApplicationNameModifiedByUser = true;
}
- }
+ }
});
// new package label
@@ -569,7 +612,7 @@ public class NewProjectCreationPage extends WizardPage {
mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
public void handleEvent(Event event) {
onMinSdkVersionFieldModified();
- setPageComplete(validatePage());
+ validatePageComplete();
}
});
}
@@ -579,12 +622,12 @@ public class NewProjectCreationPage extends WizardPage {
/** Returns the location path field value with spaces trimmed. */
private String getLocationPathFieldValue() {
- return mLocationPathField == null ? "" : mLocationPathField.getText().trim();
+ return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
}
/** Returns the current project location, depending on the Use Default Location check box. */
- public String getProjectLocation() {
- if (isNewProject() && useDefaultLocation()) {
+ private String getProjectLocation() {
+ if (mInfo.isNewProject() && mInfo.useDefaultLocation()) {
return Platform.getLocation().toString();
} else {
return getLocationPathFieldValue();
@@ -603,22 +646,51 @@ public class NewProjectCreationPage extends WizardPage {
* @return the new project resource handle
*/
private IProject getProjectHandle() {
- return ResourcesPlugin.getWorkspace().getRoot().getProject(getProjectName());
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
}
// --- UI Callbacks ----
/**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existing_dir = getLocationPathFieldValue();
+
+ // Disable the path if it doesn't exist
+ if (existing_dir.length() == 0) {
+ existing_dir = null;
+ } else {
+ File f = new File(existing_dir);
+ if (!f.exists()) {
+ existing_dir = null;
+ }
+ }
+
+ DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
+ dd.setMessage("Browse for folder");
+ dd.setFilterPath(existing_dir);
+ String abs_dir = dd.open();
+
+ if (abs_dir != null) {
+ updateLocationPathField(abs_dir);
+ extractNamesFromAndroidManifest();
+ validatePageComplete();
+ }
+ }
+
+ /**
* Enables or disable the location widgets depending on the user selection:
* the location path is enabled when using the "existing source" mode (i.e. not new project)
* or in new project mode with the "use default location" turned off.
*/
private void enableLocationWidgets() {
- boolean is_new_project = isNewProject();
- boolean use_default = useDefaultLocation();
+ boolean is_new_project = mInfo.isNewProject();
+ boolean use_default = mInfo.useDefaultLocation();
boolean location_enabled = !is_new_project || !use_default;
- boolean create_activity = isCreateActivity();
-
+ boolean create_activity = mInfo.isCreateActivity();
+
mUseDefaultLocation.setEnabled(is_new_project);
mLocationLabel.setEnabled(location_enabled);
@@ -646,8 +718,8 @@ public class NewProjectCreationPage extends WizardPage {
* @param abs_dir A new absolute directory path or null to use the default.
*/
private void updateLocationPathField(String abs_dir) {
- boolean is_new_project = isNewProject();
- boolean use_default = useDefaultLocation();
+ boolean is_new_project = mInfo.isNewProject();
+ boolean use_default = mInfo.useDefaultLocation();
boolean custom_location = !is_new_project || !use_default;
if (!mInternalLocationPathUpdate) {
@@ -663,7 +735,7 @@ public class NewProjectCreationPage extends WizardPage {
} else if (sAutoComputeCustomLocation ||
(!is_new_project && !new File(sCustomLocationOsPath).isDirectory())) {
// By default select the samples directory of the current target
- IAndroidTarget target = getSdkTarget();
+ IAndroidTarget target = mInfo.getSdkTarget();
if (target != null) {
sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES);
}
@@ -682,13 +754,13 @@ public class NewProjectCreationPage extends WizardPage {
mLocationPathField.setText(sCustomLocationOsPath);
}
} else {
- String value = Platform.getLocation().append(getProjectName()).toString();
+ String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
value = TextProcessor.process(value);
if (!mLocationPathField.getText().equals(value)) {
mLocationPathField.setText(value);
}
}
- setPageComplete(validatePage());
+ validatePageComplete();
mInternalLocationPathUpdate = false;
}
}
@@ -711,7 +783,7 @@ public class NewProjectCreationPage extends WizardPage {
newPath.equals(sCustomLocationOsPath);
sCustomLocationOsPath = newPath;
extractNamesFromAndroidManifest();
- setPageComplete(validatePage());
+ validatePageComplete();
}
}
@@ -723,9 +795,9 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onPackageNameFieldModified() {
- if (isNewProject()) {
- mUserPackageName = getPackageName();
- setPageComplete(validatePage());
+ if (mInfo.isNewProject()) {
+ mUserPackageName = mInfo.getPackageName();
+ validatePageComplete();
}
}
@@ -737,10 +809,10 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onCreateActivityCheckModified() {
- if (isNewProject() && !mInternalCreateActivityUpdate) {
- mUserCreateActivityCheck = isCreateActivity();
+ if (mInfo.isNewProject() && !mInternalCreateActivityUpdate) {
+ mUserCreateActivityCheck = mInfo.isCreateActivity();
}
- setPageComplete(validatePage());
+ validatePageComplete();
}
/**
@@ -751,15 +823,15 @@ public class NewProjectCreationPage extends WizardPage {
* validate the page.
*/
private void onActivityNameFieldModified() {
- if (isNewProject() && !mInternalActivityNameUpdate) {
- mUserActivityName = getActivityName();
- setPageComplete(validatePage());
+ if (mInfo.isNewProject() && !mInternalActivityNameUpdate) {
+ mUserActivityName = mInfo.getActivityName();
+ validatePageComplete();
}
}
/**
* Called when the min sdk version field has been modified.
- *
+ *
* Ignore the internal modifications. When modified by the user, try to match
* a target with the same API level.
*/
@@ -769,16 +841,16 @@ public class NewProjectCreationPage extends WizardPage {
}
try {
- int version = Integer.parseInt(getMinSdkVersion());
-
+ int version = Integer.parseInt(mInfo.getMinSdkVersion());
+
// Before changing, compare with the currently selected one, if any.
// There can be multiple targets with the same sdk api version, so don't change
// it if it's already at the right version.
- IAndroidTarget curr_target = getSdkTarget();
+ IAndroidTarget curr_target = mInfo.getSdkTarget();
if (curr_target != null && curr_target.getApiVersionNumber() == version) {
return;
}
-
+
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getApiVersionNumber() == version) {
mSdkTargetSelector.setSelection(target);
@@ -788,20 +860,18 @@ public class NewProjectCreationPage extends WizardPage {
} catch (NumberFormatException e) {
// ignore
}
-
- mMinSdkVersionModifiedByUser = true;
}
-
+
/**
* Called when an SDK target is modified.
- *
- * If the minSdkVersion field hasn't been modified by the user yet, we change it
- * to reflect the sdk api level that has just been selected.
+ *
+ * Also changes the minSdkVersion field to reflect the sdk api level that has
+ * just been selected.
*/
private void onSdkTargetModified() {
- IAndroidTarget target = getSdkTarget();
-
- if (target != null && !mMinSdkVersionModifiedByUser) {
+ IAndroidTarget target = mInfo.getSdkTarget();
+
+ if (target != null) {
mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber()));
mInternalMinSdkVersionUpdate = false;
@@ -814,7 +884,7 @@ public class NewProjectCreationPage extends WizardPage {
* entered before.
*/
private void updatePackageAndActivityFields() {
- if (isNewProject()) {
+ if (mInfo.isNewProject()) {
if (mUserPackageName.length() > 0 &&
!mPackageNameField.getText().equals(mUserPackageName)) {
mPackageNameField.setText(mUserPackageName);
@@ -826,7 +896,7 @@ public class NewProjectCreationPage extends WizardPage {
mActivityNameField.setText(mUserActivityName);
mInternalActivityNameUpdate = false;
}
-
+
if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) {
mInternalCreateActivityUpdate = true;
mCreateActivityCheck.setSelection(mUserCreateActivityCheck);
@@ -841,7 +911,7 @@ public class NewProjectCreationPage extends WizardPage {
* can actually be found in the custom user directory.
*/
private void extractNamesFromAndroidManifest() {
- if (isNewProject()) {
+ if (mInfo.isNewProject()) {
return;
}
@@ -853,12 +923,12 @@ public class NewProjectCreationPage extends WizardPage {
Path path = new Path(f.getPath());
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
-
+
AndroidManifestParser manifestData = AndroidManifestParser.parseForData(osPath);
if (manifestData == null) {
return;
}
-
+
String packageName = null;
Activity activity = null;
String activityName = null;
@@ -882,7 +952,7 @@ public class NewProjectCreationPage extends WizardPage {
if (packageName != null && packageName.length() > 0) {
mPackageNameField.setText(packageName);
}
-
+
if (activity != null) {
activityName = AndroidManifestParser.extractActivityName(activity.getName(),
packageName);
@@ -922,7 +992,7 @@ public class NewProjectCreationPage extends WizardPage {
mCreateActivityCheck.setSelection(false);
mInternalCreateActivityUpdate = false;
mInternalActivityNameUpdate = false;
-
+
// There is no activity name to use to fill in the project and application
// name. However if there's a package name, we can use this as a base.
if (packageName != null && packageName.length() > 0) {
@@ -944,13 +1014,13 @@ public class NewProjectCreationPage extends WizardPage {
mProjectNameField.setText(packageName);
mInternalProjectNameUpdate = false;
}
-
+
}
}
// Select the target matching the manifest's sdk or build properties, if any
boolean foundTarget = false;
-
+
ProjectProperties p = ProjectProperties.create(projectLocation, null);
if (p != null) {
// Check the {build|default}.properties files if present
@@ -976,7 +1046,7 @@ public class NewProjectCreationPage extends WizardPage {
// ignore
}
}
-
+
if (!foundTarget) {
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (projectLocation.startsWith(target.getLocation())) {
@@ -990,8 +1060,8 @@ public class NewProjectCreationPage extends WizardPage {
if (!foundTarget) {
mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText(
- minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
- Integer.toString(minSdkVersion)); //$NON-NLS-1$
+ minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" //$NON-NLS-1$
+ : Integer.toString(minSdkVersion));
mInternalMinSdkVersionUpdate = false;
}
}
@@ -1002,7 +1072,7 @@ public class NewProjectCreationPage extends WizardPage {
* @return <code>true</code> if all controls are valid, and
* <code>false</code> if at least one is invalid
*/
- protected boolean validatePage() {
+ private boolean validatePage() {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
int status = validateProjectField(workspace);
@@ -1027,31 +1097,38 @@ public class NewProjectCreationPage extends WizardPage {
if (status == MSG_NONE) {
setStatus(null, MSG_NONE);
}
-
+
// Return false if there's an error so that the finish button be disabled.
return (status & MSG_ERROR) == 0;
}
/**
+ * Validates the page and updates the Next/Finish buttons
+ */
+ private void validatePageComplete() {
+ setPageComplete(validatePage());
+ }
+
+ /**
* Validates the project name field.
*
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateProjectField(IWorkspace workspace) {
// Validate project field
- String projectFieldContents = getProjectName();
- if (projectFieldContents.length() == 0) {
+ String projectName = mInfo.getProjectName();
+ if (projectName.length() == 0) {
return setStatus("Project name must be specified", MSG_ERROR);
}
// Limit the project name to shell-agnostic characters since it will be used to
// generate the final package
- if (!sProjectNamePattern.matcher(projectFieldContents).matches()) {
+ if (!sProjectNamePattern.matcher(projectName).matches()) {
return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
MSG_ERROR);
}
- IStatus nameStatus = workspace.validateName(projectFieldContents, IResource.PROJECT);
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
if (!nameStatus.isOK()) {
return setStatus(nameStatus.getMessage(), MSG_ERROR);
}
@@ -1061,6 +1138,13 @@ public class NewProjectCreationPage extends WizardPage {
MSG_ERROR);
}
+ if (mTestInfo != null &&
+ mTestInfo.getCreateTestProject() &&
+ projectName.equals(mTestInfo.getProjectName())) {
+ return setStatus("The main project name and the test project name must be different.",
+ MSG_WARNING);
+ }
+
return MSG_NONE;
}
@@ -1071,8 +1155,8 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validateLocationPath(IWorkspace workspace) {
Path path = new Path(getProjectLocation());
- if (isNewProject()) {
- if (!useDefaultLocation()) {
+ if (mInfo.isNewProject()) {
+ if (!mInfo.useDefaultLocation()) {
// If not using the default value validate the location.
URI uri = URIUtil.toURI(path.toOSString());
IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
@@ -1103,10 +1187,10 @@ public class NewProjectCreationPage extends WizardPage {
return setStatus("A directory name must be specified.", MSG_ERROR);
}
- File dest = path.append(getProjectName()).toFile();
+ File dest = path.append(mInfo.getProjectName()).toFile();
if (dest.exists()) {
return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
- getProjectName()), MSG_ERROR);
+ mInfo.getProjectName()), MSG_ERROR);
}
}
} else {
@@ -1115,7 +1199,7 @@ public class NewProjectCreationPage extends WizardPage {
if (!f.isDirectory()) {
return setStatus("An existing directory name must be specified.", MSG_ERROR);
}
-
+
// Check there's an android manifest in the directory
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
File manifestFile = new File(osPath);
@@ -1144,7 +1228,7 @@ public class NewProjectCreationPage extends WizardPage {
Activity[] activities = manifestData.getActivities();
if (activities == null || activities.length == 0) {
// This is acceptable now as long as no activity needs to be created
- if (isCreateActivity()) {
+ if (mInfo.isCreateActivity()) {
return setStatus(
String.format("No activity name defined in %1$s.", osPath),
MSG_ERROR);
@@ -1163,11 +1247,11 @@ public class NewProjectCreationPage extends WizardPage {
/**
* Validates the sdk target choice.
- *
+ *
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateSdkTarget() {
- if (getSdkTarget() == null) {
+ if (mInfo.getSdkTarget() == null) {
return setStatus("An SDK Target must be specified.", MSG_ERROR);
}
return MSG_NONE;
@@ -1175,29 +1259,29 @@ public class NewProjectCreationPage extends WizardPage {
/**
* Validates the sdk target choice.
- *
+ *
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateMinSdkVersionField() {
// If the min sdk version is empty, it is always accepted.
- if (getMinSdkVersion().length() == 0) {
+ if (mInfo.getMinSdkVersion().length() == 0) {
return MSG_NONE;
}
int version = AndroidManifestParser.INVALID_MIN_SDK;
try {
// If not empty, it must be a valid integer > 0
- version = Integer.parseInt(getMinSdkVersion());
+ version = Integer.parseInt(mInfo.getMinSdkVersion());
} catch (NumberFormatException e) {
// ignore
}
-
+
if (version < 1) {
return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR);
}
-
- if (getSdkTarget() != null && getSdkTarget().getApiVersionNumber() != version) {
+
+ if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) {
return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
MSG_WARNING);
}
@@ -1212,36 +1296,36 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validateActivityField() {
// Disregard if not creating an activity
- if (!isCreateActivity()) {
+ if (!mInfo.isCreateActivity()) {
return MSG_NONE;
}
// Validate activity field
- String activityFieldContents = getActivityName();
+ String activityFieldContents = mInfo.getActivityName();
if (activityFieldContents.length() == 0) {
return setStatus("Activity name must be specified.", MSG_ERROR);
}
// The activity field can actually contain part of a sub-package name
// or it can start with a dot "." to indicates it comes from the parent package name.
- String packageName = "";
+ String packageName = ""; //$NON-NLS-1$
int pos = activityFieldContents.lastIndexOf('.');
if (pos >= 0) {
packageName = activityFieldContents.substring(0, pos);
if (packageName.startsWith(".")) { //$NON-NLS-1$
packageName = packageName.substring(1);
}
-
+
activityFieldContents = activityFieldContents.substring(pos + 1);
}
-
+
// the activity field can contain a simple java identifier, or a
// package name or one that starts with a dot. So if it starts with a dot,
// ignore this dot -- the rest must look like a package name.
if (activityFieldContents.charAt(0) == '.') {
activityFieldContents = activityFieldContents.substring(1);
}
-
+
// Check it's a valid activity string
int result = MSG_NONE;
IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents,
@@ -1272,7 +1356,7 @@ public class NewProjectCreationPage extends WizardPage {
*/
private int validatePackageField() {
// Validate package field
- String packageFieldContents = getPackageName();
+ String packageFieldContents = mInfo.getPackageName();
if (packageFieldContents.length() == 0) {
return setStatus("Package name must be specified.", MSG_ERROR);
}
@@ -1312,16 +1396,16 @@ public class NewProjectCreationPage extends WizardPage {
private int validateSourceFolder() {
// This check does nothing when creating a new project.
// This check is also useless when no activity is present or created.
- if (isNewProject() || !isCreateActivity()) {
+ if (mInfo.isNewProject() || !mInfo.isCreateActivity()) {
return MSG_NONE;
}
- String osTarget = getActivityName();
-
+ String osTarget = mInfo.getActivityName();
+
if (osTarget.indexOf('.') == -1) {
- osTarget = getPackageName() + File.separator + osTarget;
+ osTarget = mInfo.getPackageName() + File.separator + osTarget;
} else if (osTarget.indexOf('.') == 0) {
- osTarget = getPackageName() + osTarget;
+ osTarget = mInfo.getPackageName() + osTarget;
}
osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
index af45fa9..afc67ed 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
@@ -20,6 +20,8 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.AndroidNature;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo;
+import com.android.ide.eclipse.adt.wizards.newproject.NewTestProjectCreationPage.TestInfo;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
@@ -39,6 +41,8 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -71,27 +75,54 @@ import java.util.Map.Entry;
* Note: this class is public so that it can be accessed from unit tests.
* It is however an internal class. Its API may change without notice.
* It should semantically be considered as a private final class.
- * Do not derive from this class.
+ * Do not derive from this class.
*/
public class NewProjectWizard extends Wizard implements INewWizard {
- private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
- private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
- private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
- private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
- private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
- private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
- private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
- private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
- private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
- private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
- private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
-
- private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
- private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
- private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
- private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
+ /**
+ * Indicates which pages should be available in the New Project Wizard.
+ */
+ protected enum AvailablePages {
+ /**
+ * Both the usual "Android Project" and the "Android Test Project" pages will
+ * be available. The first page displayed will be the former one and it can depend
+ * on the soon-to-be created normal project.
+ */
+ ANDROID_AND_TEST_PROJECT,
+ /**
+ * Only the "Android Test Project" page will be available. User will have to
+ * select an existing Android Project. If the selection matches such a project,
+ * it will be used as a default.
+ */
+ TEST_PROJECT_ONLY
+ }
+
+ private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
+ private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
+ private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
+ private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
+ private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
+ private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
+ private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
+ private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
+ private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
+ private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
+ // Warning: The expanded string PARAM_TEST_TARGET_PACKAGE must not contain the
+ // string "PACKAGE" since it collides with the replacement of PARAM_PACKAGE.
+ private static final String PARAM_TEST_TARGET_PACKAGE = "TEST_TARGET_PCKG"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_SELF = "TARGET_SELF"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_MAIN = "TARGET_MAIN"; //$NON-NLS-1$
+ private static final String PARAM_TARGET_EXISTING = "TARGET_EXISTING"; //$NON-NLS-1$
+ private static final String PARAM_REFERENCE_PROJECT = "REFERENCE_PROJECT"; //$NON-NLS-1$
+
+ private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
+ private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
+ private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
+ private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
+ private static final String PH_TEST_USES_LIBRARY = "TEST-USES-LIBRARY"; //$NON-NLS-1$
+ private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION"; //$NON-NLS-1$
private static final String BIN_DIRECTORY =
SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP;
@@ -117,6 +148,12 @@ public class NewProjectWizard extends Wizard implements INewWizard {
+ "uses-sdk.template"; //$NON-NLS-1$
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
+ "launcher_intent_filter.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_USES_LIBRARY = TEMPLATES_DIRECTORY
+ + "test_uses-library.template"; //$NON-NLS-1$
+ private static final String TEMPLATE_TEST_INSTRUMENTATION = TEMPLATES_DIRECTORY
+ + "test_instrumentation.template"; //$NON-NLS-1$
+
+
private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ "strings.template"; //$NON-NLS-1$
@@ -124,11 +161,11 @@ public class NewProjectWizard extends Wizard implements INewWizard {
+ "string.template"; //$NON-NLS-1$
private static final String ICON = "icon.png"; //$NON-NLS-1$
- private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
+ private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
- private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$
- private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
- private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
+ private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$
+ private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
+ private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
private static final String[] DEFAULT_DIRECTORIES = new String[] {
BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
@@ -136,13 +173,23 @@ public class NewProjectWizard extends Wizard implements INewWizard {
DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY};
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
- private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
- private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
- private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
-
- protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
+ private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
+ private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
+ private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
private NewProjectCreationPage mMainPage;
+ private NewTestProjectCreationPage mTestPage;
+ /** Package name available when the wizard completes. */
+ private String mPackageName;
+ private final AvailablePages mAvailablePages;
+
+ public NewProjectWizard() {
+ this(AvailablePages.ANDROID_AND_TEST_PROJECT);
+ }
+
+ protected NewProjectWizard(AvailablePages availablePages) {
+ mAvailablePages = availablePages;
+ }
/**
* Initializes this creation wizard using the passed workbench and object
@@ -153,13 +200,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
setWindowTitle("New Android Project");
setImageDescriptor();
- mMainPage = createMainPage();
- mMainPage.setTitle("New Android Project");
- mMainPage.setDescription("Creates a new Android Project resource.");
+ if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
+ mMainPage = createMainPage();
+ }
+ mTestPage = createTestPage();
}
-
+
/**
- * Creates the wizard page.
+ * Creates the main wizard page.
* <p/>
* Please do NOT override this method.
* <p/>
@@ -168,7 +216,20 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* to maintain compatibility between different versions of the plugin.
*/
protected NewProjectCreationPage createMainPage() {
- return new NewProjectCreationPage(MAIN_PAGE_NAME);
+ return new NewProjectCreationPage();
+ }
+
+ /**
+ * Creates the test wizard page.
+ * <p/>
+ * Please do NOT override this method.
+ * <p/>
+ * This is protected so that it can be overridden by unit tests.
+ * However the contract of this class is private and NO ATTEMPT will be made
+ * to maintain compatibility between different versions of the plugin.
+ */
+ protected NewTestProjectCreationPage createTestPage() {
+ return new NewTestProjectCreationPage();
}
// -- Methods inherited from org.eclipse.jface.wizard.Wizard --
@@ -180,7 +241,15 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*/
@Override
public void addPages() {
- addPage(mMainPage);
+ if (mAvailablePages == AvailablePages.ANDROID_AND_TEST_PROJECT) {
+ addPage(mMainPage);
+ }
+ addPage(mTestPage);
+
+ if (mMainPage != null && mTestPage != null) {
+ mTestPage.setMainInfo(mMainPage.getMainInfo());
+ mMainPage.setTestInfo(mTestPage.getTestInfo());
+ }
}
/**
@@ -193,7 +262,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*/
@Override
public boolean performFinish() {
- if (!createAndroidProject()) {
+ if (!createAndroidProjects()) {
return false;
}
@@ -203,15 +272,22 @@ public class NewProjectWizard extends Wizard implements INewWizard {
return true;
}
+ // -- Public Fields --
+
+ /** Returns the main project package name. Only valid once the wizard finishes. */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
// -- Custom Methods --
/**
* Before actually creating the project for a new project (as opposed to using an
* existing project), we check if the target location is a directory that either does
* not exist or is empty.
- *
+ *
* If it's not empty, ask the user for confirmation.
- *
+ *
* @param destination The destination folder where the new project is to be created.
* @return True if the destination doesn't exist yet or is an empty directory or is
* accepted by the user.
@@ -226,28 +302,107 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
+ * Structure that describes all the information needed to create a project.
+ * This is collected from the pages by {@link NewProjectWizard#createAndroidProjects()}
+ * and then used by
+ * {@link NewProjectWizard#createProjectAsync(IProgressMonitor, ProjectInfo, ProjectInfo)}.
+ */
+ private static class ProjectInfo {
+ private final IProject mProject;
+ private final IProjectDescription mDescription;
+ private final Map<String, Object> mParameters;
+ private final HashMap<String, String> mDictionary;
+
+ public ProjectInfo(IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ HashMap<String, String> dictionary) {
+ mProject = project;
+ mDescription = description;
+ mParameters = parameters;
+ mDictionary = dictionary;
+ }
+
+ public IProject getProject() {
+ return mProject;
+ }
+
+ public IProjectDescription getDescription() {
+ return mDescription;
+ }
+
+ public Map<String, Object> getParameters() {
+ return mParameters;
+ }
+
+ public HashMap<String, String> getDictionary() {
+ return mDictionary;
+ }
+ }
+
+ /**
* Creates the android project.
* @return True if the project could be created.
*/
- private boolean createAndroidProject() {
+ private boolean createAndroidProjects() {
+
+ final ProjectInfo mainData = collectMainPageInfo();
+ if (mMainPage != null && mainData == null) {
+ return false;
+ }
+
+ final ProjectInfo testData = collectTestPageInfo();
+ if (mTestPage != null && testData == null) {
+ return false;
+ }
+
+ // Create a monitored operation to create the actual project
+ WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
+ createProjectAsync(monitor, mainData, testData);
+ }
+ };
+
+ // Run the operation in a different thread
+ runAsyncOperation(op);
+ return true;
+ }
+
+ /**
+ * Collects all the parameters needed to create the main project.
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no main page.
+ */
+ private ProjectInfo collectMainPageInfo() {
+ if (mMainPage == null) {
+ return null;
+ }
+
+ IMainInfo info = mMainPage.getMainInfo();
+
IWorkspace workspace = ResourcesPlugin.getWorkspace();
- final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
+ final IProject project = workspace.getRoot().getProject(info.getProjectName());
final IProjectDescription description = workspace.newProjectDescription(project.getName());
+ // keep some variables to make them available once the wizard closes
+ mPackageName = info.getPackageName();
+
final Map<String, Object> parameters = new HashMap<String, Object>();
- parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
- parameters.put(PARAM_PACKAGE, mMainPage.getPackageName());
+ parameters.put(PARAM_PROJECT, info.getProjectName());
+ parameters.put(PARAM_PACKAGE, mPackageName);
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
- parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject());
- parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
- parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
- parameters.put(PARAM_MIN_SDK_VERSION, mMainPage.getMinSdkVersion());
+ parameters.put(PARAM_IS_NEW_PROJECT, info.isNewProject());
+ parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
+ parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
+ parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
- if (mMainPage.isCreateActivity()) {
+ if (info.isCreateActivity()) {
// An activity name can be of the form ".package.Class" or ".Class".
// The initial dot is ignored, as it is always added later in the templates.
- String activityName = mMainPage.getActivityName();
+ String activityName = info.getActivityName();
if (activityName.startsWith(".")) { //$NON-NLS-1$
activityName = activityName.substring(1);
}
@@ -256,31 +411,81 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// create a dictionary of string that will contain name+content.
// we'll put all the strings into values/strings.xml
- final HashMap<String, String> stringDictionary = new HashMap<String, String>();
- stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName());
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, info.getApplicationName());
- IPath path = mMainPage.getLocationPath();
+ IPath path = info.getLocationPath();
IPath defaultLocation = Platform.getLocation();
if (!path.equals(defaultLocation)) {
description.setLocation(path);
}
-
- if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() &&
+
+ if (info.isNewProject() && !info.useDefaultLocation() &&
!validateNewProjectLocationIsEmpty(path)) {
- return false;
+ return null;
}
- // Create a monitored operation to create the actual project
- WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
- @Override
- protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
- createProjectAsync(project, description, monitor, parameters, stringDictionary);
- }
- };
+ return new ProjectInfo(project, description, parameters, dictionary);
+ }
- // Run the operation in a different thread
- runAsyncOperation(op);
- return true;
+ /**
+ * Collects all the parameters needed to create the test project.
+ *
+ * @return A new {@link ProjectInfo} on success. Returns null if the project cannot be
+ * created because parameters are incorrect or should not be created because there
+ * is no test page.
+ */
+ private ProjectInfo collectTestPageInfo() {
+ if (mTestPage == null) {
+ return null;
+ }
+
+ TestInfo info = mTestPage.getTestInfo();
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProject project = workspace.getRoot().getProject(info.getProjectName());
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ final Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_PROJECT, info.getProjectName());
+ parameters.put(PARAM_PACKAGE, info.getPackageName());
+ parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
+ parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
+ parameters.put(PARAM_IS_NEW_PROJECT, true);
+ parameters.put(PARAM_SRC_FOLDER, info.getSourceFolder());
+ parameters.put(PARAM_SDK_TARGET, info.getSdkTarget());
+ parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion());
+
+ // Test-specific parameters
+ parameters.put(PARAM_TEST_TARGET_PACKAGE, info.getTargetPackageName());
+
+ if (info.isTestingSelf()) {
+ parameters.put(PARAM_TARGET_SELF, true);
+ }
+ if (info.isTestingMain()) {
+ parameters.put(PARAM_TARGET_MAIN, true);
+ }
+ if (info.isTestingExisting()) {
+ parameters.put(PARAM_TARGET_EXISTING, true);
+ parameters.put(PARAM_REFERENCE_PROJECT, info.getExistingTestedProject());
+ }
+
+ // create a dictionary of string that will contain name+content.
+ // we'll put all the strings into values/strings.xml
+ final HashMap<String, String> dictionary = new HashMap<String, String>();
+ dictionary.put(STRING_APP_NAME, info.getApplicationName());
+
+ IPath path = info.getLocationPath();
+ IPath defaultLocation = Platform.getLocation();
+ if (!path.equals(defaultLocation)) {
+ description.setLocation(path);
+ }
+
+ if (!info.useDefaultLocation() && !validateNewProjectLocationIsEmpty(path)) {
+ return null;
+ }
+
+ return new ProjectInfo(project, description, parameters, dictionary);
}
/**
@@ -315,82 +520,46 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
- * Creates the actual project, sets its nature and adds the required folders
- * and files to it. This is run asynchronously in a different thread.
+ * Creates the actual project(s). This is run asynchronously in a different thread.
*
- * @param project The project to create.
- * @param description A description of the project.
* @param monitor An existing monitor.
- * @param parameters Template parameters.
- * @param stringDictionary String definition.
+ * @param mainData Data for main project. Can be null.
* @throws InvocationTargetException to wrap any unmanaged exception and
* return it to the calling thread. The method can fail if it fails
* to create or modify the project or if it is canceled by the user.
*/
- private void createProjectAsync(IProject project, IProjectDescription description,
- IProgressMonitor monitor, Map<String, Object> parameters,
- Map<String, String> stringDictionary)
- throws InvocationTargetException {
+ private void createProjectAsync(IProgressMonitor monitor,
+ ProjectInfo mainData,
+ ProjectInfo testData)
+ throws InvocationTargetException {
monitor.beginTask("Create Android Project", 100);
try {
- // Create project and open it
- project.create(description, new SubProgressMonitor(monitor, 10));
- if (monitor.isCanceled()) throw new OperationCanceledException();
- project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
-
- // Add the Java and android nature to the project
- AndroidNature.setupProjectNatures(project, monitor);
-
- // Create folders in the project if they don't already exist
- addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
- String[] sourceFolders = new String[] {
- (String) parameters.get(PARAM_SRC_FOLDER),
- GEN_SRC_DIRECTORY
- };
- addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
-
- // Create the resource folders in the project if they don't already exist.
- addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
-
- // Setup class path: mark folders as source folders
- IJavaProject javaProject = JavaCore.create(project);
- for (String sourceFolder : sourceFolders) {
- setupSourceFolder(javaProject, sourceFolder, monitor);
- }
-
- // Mark the gen source folder as derived
- IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
- if (genSrcFolder.exists()) {
- genSrcFolder.setDerived(true);
+ IProject mainProject = null;
+
+ if (mainData != null) {
+ mainProject = createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ mainData.getProject(),
+ mainData.getDescription(),
+ mainData.getParameters(),
+ mainData.getDictionary());
}
- if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
- // Create files in the project if they don't already exist
- addManifest(project, parameters, stringDictionary, monitor);
-
- // add the default app icon
- addIcon(project, monitor);
-
- // Create the default package components
- addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor);
+ if (testData != null) {
- // add the string definition file if needed
- if (stringDictionary.size() > 0) {
- addStringDictionaryFile(project, stringDictionary, monitor);
+ Map<String, Object> parameters = testData.getParameters();
+ if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) {
+ parameters.put(PARAM_REFERENCE_PROJECT, mainProject);
}
- // Set output location
- javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
- monitor);
+ createEclipseProject(
+ new SubProgressMonitor(monitor, 50),
+ testData.getProject(),
+ testData.getDescription(),
+ parameters,
+ testData.getDictionary());
}
- Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
- null /* apkConfigMap*/);
-
- // Fix the project to make sure all properties are as expected.
- // Necessary for existing projects and good for new ones to.
- ProjectHelper.fixProject(project);
-
} catch (CoreException e) {
throw new InvocationTargetException(e);
} catch (IOException e) {
@@ -401,6 +570,111 @@ public class NewProjectWizard extends Wizard implements INewWizard {
}
/**
+ * Creates the actual project, sets its nature and adds the required folders
+ * and files to it. This is run asynchronously in a different thread.
+ *
+ * @param monitor An existing monitor.
+ * @param project The project to create.
+ * @param description A description of the project.
+ * @param parameters Template parameters.
+ * @param dictionary String definition.
+ * @return The project newly created
+ */
+ private IProject createEclipseProject(IProgressMonitor monitor,
+ IProject project,
+ IProjectDescription description,
+ Map<String, Object> parameters,
+ Map<String, String> dictionary)
+ throws CoreException, IOException {
+
+ // Create project and open it
+ project.create(description, new SubProgressMonitor(monitor, 10));
+ if (monitor.isCanceled()) throw new OperationCanceledException();
+
+ project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
+
+ // Add the Java and android nature to the project
+ AndroidNature.setupProjectNatures(project, monitor);
+
+ // Create folders in the project if they don't already exist
+ addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
+ String[] sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
+
+ // Create the resource folders in the project if they don't already exist.
+ addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
+
+ // Setup class path: mark folders as source folders
+ IJavaProject javaProject = JavaCore.create(project);
+ for (String sourceFolder : sourceFolders) {
+ setupSourceFolder(javaProject, sourceFolder, monitor);
+ }
+
+ // Mark the gen source folder as derived
+ IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
+ if (genSrcFolder.exists()) {
+ genSrcFolder.setDerived(true);
+ }
+
+ if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
+ // Create files in the project if they don't already exist
+ addManifest(project, parameters, dictionary, monitor);
+
+ // add the default app icon
+ addIcon(project, monitor);
+
+ // Create the default package components
+ addSampleCode(project, sourceFolders[0], parameters, dictionary, monitor);
+
+ // add the string definition file if needed
+ if (dictionary.size() > 0) {
+ addStringDictionaryFile(project, dictionary, monitor);
+ }
+
+ // Set output location
+ javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
+ monitor);
+ }
+
+ // Create the reference to the target project
+ if (parameters.containsKey(PARAM_REFERENCE_PROJECT)) {
+ IProject refProject = (IProject) parameters.get(PARAM_REFERENCE_PROJECT);
+ if (refProject != null) {
+ IProjectDescription desc = project.getDescription();
+
+ // Add out reference to the existing project reference.
+ // We just created a project with no references so we don't need to expand
+ // the currently-empty current list.
+ desc.setReferencedProjects(new IProject[] { refProject });
+
+ project.setDescription(desc, IResource.KEEP_HISTORY, new SubProgressMonitor(monitor, 10));
+
+ IClasspathEntry entry = JavaCore.newProjectEntry(
+ refProject.getFullPath(), //path
+ new IAccessRule[0], //accessRules
+ false, //combineAccessRules
+ new IClasspathAttribute[0], //extraAttributes
+ false //isExported
+
+ );
+ ProjectHelper.addEntryToClasspath(javaProject, entry);
+ }
+ }
+
+ Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
+ null /* apkConfigMap*/);
+
+ // Fix the project to make sure all properties are as expected.
+ // Necessary for existing projects and good for new ones to.
+ ProjectHelper.fixProject(project);
+
+ return project;
+ }
+
+ /**
* Adds default directories to the project.
*
* @param project The Java Project to update.
@@ -429,7 +703,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
- * @param stringDictionary String List to be added to a string definition
+ * @param dictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
@@ -437,7 +711,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addManifest(IProject project, Map<String, Object> parameters,
- Map<String, String> stringDictionary, IProgressMonitor monitor)
+ Map<String, String> dictionary, IProgressMonitor monitor)
throws CoreException, IOException {
// get IFile to the manifest and check if it's not already there.
@@ -453,23 +727,42 @@ public class NewProjectWizard extends Wizard implements INewWizard {
if (parameters.containsKey(PARAM_ACTIVITY)) {
// now get the activity template
String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
-
+
// Replace all keyword parameters to make main activity.
String activities = replaceParameters(activityTemplate, parameters);
-
+
// set the intent.
String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
-
+
// set the intent to the main activity
activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
-
+
// set the activity(ies) in the manifest
manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
} else {
// remove the activity(ies) from the manifest
- manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, "");
+ manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); //$NON-NLS-1$
+ }
+
+ // Handle the case of the test projects
+ if (parameters.containsKey(PARAM_TEST_TARGET_PACKAGE)) {
+ // Set the uses-library needed by the test project
+ String usesLibrary = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_USES_LIBRARY);
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, usesLibrary);
+
+ // Set the instrumentation element needed by the test project
+ String instru = AdtPlugin.readEmbeddedTextFile(TEMPLATE_TEST_INSTRUMENTATION);
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, instru);
+
+ // Replace PARAM_TEST_TARGET_PACKAGE itself now
+ manifestTemplate = replaceParameters(manifestTemplate, parameters);
+
+ } else {
+ // remove the unused entries
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_USES_LIBRARY, ""); //$NON-NLS-1$
+ manifestTemplate = manifestTemplate.replaceAll(PH_TEST_INSTRUMENTATION, ""); //$NON-NLS-1$
}
-
+
String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
if (minSdkVersion != null && minSdkVersion.length() > 0) {
String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
@@ -571,7 +864,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
- * @param stringDictionary String List to be added to a string definition
+ * @param dictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
@@ -579,12 +872,12 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addSampleCode(IProject project, String sourceFolder,
- Map<String, Object> parameters, Map<String, String> stringDictionary,
+ Map<String, Object> parameters, Map<String, String> dictionary,
IProgressMonitor monitor) throws CoreException, IOException {
// create the java package directories.
IFolder pkgFolder = project.getFolder(sourceFolder);
String packageName = (String) parameters.get(PARAM_PACKAGE);
-
+
// The PARAM_ACTIVITY key will be absent if no activity should be created,
// in which case activityName will be null.
String activityName = (String) parameters.get(PARAM_ACTIVITY);
@@ -597,7 +890,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
int pos = packageName.lastIndexOf('.');
activityName = packageName.substring(pos + 1);
packageName = packageName.substring(0, pos);
-
+
// Also update the values used in the JAVA_FILE_TEMPLATE below
// (but not the ones from the manifest so don't change the caller's dictionary)
java_activity_parameters = new HashMap<String, Object>(parameters);
@@ -630,9 +923,9 @@ public class NewProjectWizard extends Wizard implements INewWizard {
if (!file.exists()) {
copyFile(LAYOUT_TEMPLATE, file, parameters, monitor);
if (activityName != null) {
- stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
+ dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
} else {
- stringDictionary.put(STRING_HELLO_WORLD, "Hello World!");
+ dictionary.put(STRING_HELLO_WORLD, "Hello World!");
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java
new file mode 100755
index 0000000..7b99053
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectCreationPage.java
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * References:
+ * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
+ * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
+ */
+
+package com.android.ide.eclipse.adt.wizards.newproject;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.IMainInfo;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectCreationPage.MainInfo;
+import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.ProjectChooserHelper;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdkuilib.SdkTargetSelector;
+
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaConventions;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.TextProcessor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+/**
+ * NewAndroidProjectCreationPage is a project creation page that provides the
+ * following fields:
+ * <ul>
+ * <li> Project name
+ * <li> SDK Target
+ * <li> Application name
+ * <li> Package name
+ * <li> Activity name
+ * </ul>
+ * Note: this class is public so that it can be accessed from unit tests.
+ * It is however an internal class. Its API may change without notice.
+ * It should semantically be considered as a private final class.
+ * Do not derive from this class.
+ */
+public class NewTestProjectCreationPage extends WizardPage {
+
+ // constants
+ static final String TEST_PAGE_NAME = "newAndroidTestProjectPage"; //$NON-NLS-1$
+
+ /** Initial value for all name fields (project, activity, application, package). Used
+ * whenever a value is requested before controls are created. */
+ private static final String INITIAL_NAME = ""; //$NON-NLS-1$
+ /** Initial value for the Use Default Location check box. */
+ private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
+ /** Initial value for the Create Test Project check box. */
+ private static final boolean INITIAL_CREATE_TEST_PROJECT = false;
+
+
+ /** Pattern for characters accepted in a project name. Since this will be used as a
+ * directory name, we're being a bit conservative on purpose. It cannot start with a space. */
+ private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$
+ /** Last user-browsed location, static so that it be remembered for the whole session */
+ private static String sCustomLocationOsPath = ""; //$NON-NLS-1$
+
+ private final int MSG_NONE = 0;
+ private final int MSG_WARNING = 1;
+ private final int MSG_ERROR = 2;
+
+ /** Structure with the externally visible information from this Test Project page. */
+ private final TestInfo mInfo = new TestInfo();
+ /** Structure with the externally visible information from the Main Project page.
+ * This is null if there's no such page, meaning the test project page is standalone. */
+ private IMainInfo mMainInfo;
+
+ // widgets
+ private Text mProjectNameField;
+ private Text mPackageNameField;
+ private Text mApplicationNameField;
+ private Button mUseDefaultLocation;
+ private Label mLocationLabel;
+ private Text mLocationPathField;
+ private Button mBrowseButton;
+ private Text mMinSdkVersionField;
+ private SdkTargetSelector mSdkTargetSelector;
+ private ITargetChangeListener mSdkTargetChangeListener;
+ private Button mCreateTestProjectField;
+ private Text mTestedProjectNameField;
+ private Button mProjectBrowseButton;
+ private ProjectChooserHelper mProjectChooserHelper;
+ private Button mTestSelfProjectRadio;
+ private Button mTestExistingProjectRadio;
+
+ /** A list of composites that are disabled when the "Create Test Project" toggle is off. */
+ private ArrayList<Composite> mToggleComposites = new ArrayList<Composite>();
+
+ private boolean mInternalProjectNameUpdate;
+ private boolean mInternalLocationPathUpdate;
+ private boolean mInternalPackageNameUpdate;
+ private boolean mInternalApplicationNameUpdate;
+ private boolean mInternalMinSdkVersionUpdate;
+ private boolean mInternalSdkTargetUpdate;
+ private IProject mExistingTestedProject;
+ private boolean mProjectNameModifiedByUser;
+ private boolean mApplicationNameModifiedByUser;
+ private boolean mPackageNameModifiedByUser;
+ private boolean mMinSdkVersionModifiedByUser;
+ private boolean mSdkTargetModifiedByUser;
+
+ private Label mTestTargetPackageLabel;
+
+ private String mLastExistingPackageName;
+
+
+ /**
+ * Creates a new project creation wizard page.
+ */
+ public NewTestProjectCreationPage() {
+ super(TEST_PAGE_NAME);
+ setPageComplete(false);
+ setTitle("New Android Test Project");
+ setDescription("Creates a new Android Test Project resource.");
+ }
+
+ // --- Getters used by NewProjectWizard ---
+
+ /**
+ * Structure that collects all externally visible information from this page.
+ * This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public class TestInfo {
+
+ /** Returns true if a new Test Project should be created. */
+ public boolean getCreateTestProject() {
+ return mCreateTestProjectField == null ? true : mCreateTestProjectField.getSelection();
+ }
+
+ /**
+ * Returns the current project location path as entered by the user, or its
+ * anticipated initial value. Note that if the default has been returned the
+ * path in a project description used to create a project should not be set.
+ *
+ * @return the project location path or its anticipated initial value.
+ */
+ public IPath getLocationPath() {
+ return new Path(getProjectLocation());
+ }
+
+ /** Returns the value of the project name field with leading and trailing spaces removed. */
+ public String getProjectName() {
+ return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
+ }
+
+ /** Returns the value of the package name field with spaces trimmed. */
+ public String getPackageName() {
+ return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
+ }
+
+ /** Returns the value of the test target package name field with spaces trimmed. */
+ public String getTargetPackageName() {
+ return mTestTargetPackageLabel == null ? INITIAL_NAME
+ : mTestTargetPackageLabel.getText().trim();
+ }
+
+ /** Returns the value of the min sdk version field with spaces trimmed. */
+ public String getMinSdkVersion() {
+ return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the value of the application name field with spaces trimmed. */
+ public String getApplicationName() {
+ // Return the name of the activity as default application name.
+ return mApplicationNameField == null ? "" : mApplicationNameField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the value of the Use Default Location field. */
+ public boolean useDefaultLocation() {
+ return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
+ : mUseDefaultLocation.getSelection();
+ }
+
+ /** Returns the the default "src" constant. */
+ public String getSourceFolder() {
+ return SdkConstants.FD_SOURCES;
+ }
+
+ /** Returns the current sdk target or null if none has been selected yet. */
+ public IAndroidTarget getSdkTarget() {
+ return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
+ }
+
+ public boolean isTestingSelf() {
+ return mMainInfo == null &&
+ (mTestSelfProjectRadio == null ? false : mTestSelfProjectRadio.getSelection());
+ }
+
+ public boolean isTestingMain() {
+ return mMainInfo != null;
+ }
+
+ public boolean isTestingExisting() {
+ return mMainInfo == null &&
+ (mTestExistingProjectRadio == null ? false
+ : mTestExistingProjectRadio.getSelection());
+ }
+
+ public IProject getExistingTestedProject() {
+ return mExistingTestedProject;
+ }
+ }
+
+ /**
+ * Returns a {@link TestInfo} structure that collects all externally visible information
+ * from this page. This is used by the calling wizard to actually do the work or by other pages.
+ */
+ public TestInfo getTestInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Grabs the {@link MainInfo} structure with visible parameters from the main project page.
+ * This may be null.
+ */
+ public void setMainInfo(IMainInfo mainInfo) {
+ mMainInfo = mainInfo;
+ }
+
+ // --- UI creation ---
+
+ /**
+ * Creates the top level control for this dialog page under the given parent
+ * composite.
+ *
+ * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
+ */
+ public void createControl(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NULL);
+ composite.setFont(parent.getFont());
+
+ initializeDialogUnits(parent);
+
+ composite.setLayout(new GridLayout());
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ createToggleTestProject(composite);
+ createTestProjectGroup(composite);
+ createLocationGroup(composite);
+ createTestTargetGroup(composite);
+ createTargetGroup(composite);
+ createPropertiesGroup(composite);
+
+ // Update state the first time
+ enableLocationWidgets();
+
+ // Show description the first time
+ setErrorMessage(null);
+ setMessage(null);
+ setControl(composite);
+
+ // Validate. This will complain about the first empty field.
+ validatePageComplete();
+ }
+
+ /**
+ * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
+ * the dialog is made visible and to also update the enabled/disabled state of some
+ * controls (doing so in createControl doesn't always change their state somehow.)
+ */
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ if (visible) {
+ mProjectNameField.setFocus();
+ validatePageComplete();
+ onCreateTestProjectToggle();
+ onExistingProjectChanged();
+ }
+ }
+
+ @Override
+ public void dispose() {
+
+ if (mSdkTargetChangeListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
+ mSdkTargetChangeListener = null;
+ }
+
+ super.dispose();
+ }
+
+ /**
+ * Creates the "create test project" checkbox but only if there's a main page in the wizard.
+ *
+ * @param parent the parent composite
+ */
+ private final void createToggleTestProject(Composite parent) {
+
+ if (mMainInfo != null) {
+ mCreateTestProjectField = new Button(parent, SWT.CHECK);
+ mCreateTestProjectField.setText("Create a Test Project");
+ mCreateTestProjectField.setToolTipText("Select this if you also want to create a Test Project.");
+ mCreateTestProjectField.setSelection(INITIAL_CREATE_TEST_PROJECT);
+ mCreateTestProjectField.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onCreateTestProjectToggle();
+ }
+ });
+ }
+ }
+
+ /**
+ * Creates the group for the project name:
+ * [label: "Project Name"] [text field]
+ *
+ * @param parent the parent composite
+ */
+ private final void createTestProjectGroup(Composite parent) {
+ Composite group = new Composite(parent, SWT.NONE);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ mToggleComposites.add(group);
+
+ // --- test project name ---
+
+ // new project label
+ String tooltip = "Name of the Eclipse test project to create. It cannot be empty.";
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Test Project Name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText(tooltip);
+
+ // new project name entry field
+ mProjectNameField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_HORIZONTAL);
+ mProjectNameField.setToolTipText(tooltip);
+ mProjectNameField.setLayoutData(data);
+ mProjectNameField.setFont(parent.getFont());
+ mProjectNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalProjectNameUpdate) {
+ mProjectNameModifiedByUser = true;
+ }
+ updateLocationPathField(null);
+ }
+ });
+
+ }
+
+ private final void createLocationGroup(Composite parent) {
+
+ // --- project location ---
+
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ group.setLayout(new GridLayout(3, /* num columns */
+ false /* columns of not equal size */));
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ group.setFont(parent.getFont());
+ group.setText("Content");
+
+ mToggleComposites.add(group);
+
+ mUseDefaultLocation = new Button(group, SWT.CHECK);
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ mUseDefaultLocation.setLayoutData(gd);
+ mUseDefaultLocation.setText("Use default location");
+ mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION);
+
+ mUseDefaultLocation.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ enableLocationWidgets();
+ validatePageComplete();
+ }
+ });
+
+
+ mLocationLabel = new Label(group, SWT.NONE);
+ mLocationLabel.setText("Location:");
+
+ mLocationPathField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL, /* horizontal alignment */
+ GridData.BEGINNING, /* vertical alignment */
+ true, /* grabExcessHorizontalSpace */
+ false, /* grabExcessVerticalSpace */
+ 1, /* horizontalSpan */
+ 1); /* verticalSpan */
+ mLocationPathField.setLayoutData(data);
+ mLocationPathField.setFont(parent.getFont());
+ mLocationPathField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ onLocationPathFieldModified();
+ }
+ });
+
+ mBrowseButton = new Button(group, SWT.PUSH);
+ mBrowseButton.setText("Browse...");
+ setButtonLayoutData(mBrowseButton);
+ mBrowseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onOpenDirectoryBrowser();
+ }
+ });
+ }
+
+ /**
+ * Creates the group for Test Target options.
+ *
+ * There are two different modes here:
+ * <ul>
+ * <li>When mMainInfo exists, this is part of a new Android Project. In which case
+ * the new test is tied to the soon-to-be main project and there is actually no choice.
+ * <li>When mMainInfo does not exist, this is a standalone new test project. In this case
+ * we offer 2 options for the test target: self test or against an existing Android project.
+ * </ul>
+ *
+ * @param parent the parent composite
+ */
+ private final void createTestTargetGroup(Composite parent) {
+
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 3;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+ group.setText("Test Target");
+
+ mToggleComposites.add(group);
+
+ if (mMainInfo == null) {
+ // Standalone mode: choose between self-test and existing-project test
+
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Select the project to test:");
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ label.setLayoutData(gd);
+
+ mTestSelfProjectRadio = new Button(group, SWT.RADIO);
+ mTestSelfProjectRadio.setText("This project");
+ mTestSelfProjectRadio.setSelection(false);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ mTestSelfProjectRadio.setLayoutData(gd);
+
+ mTestExistingProjectRadio = new Button(group, SWT.RADIO);
+ mTestExistingProjectRadio.setText("An existing Android project");
+ mTestExistingProjectRadio.setSelection(mMainInfo == null);
+ mTestExistingProjectRadio.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onExistingProjectChanged();
+ }
+ });
+
+ String tooltip = "The existing Android Project that is being tested.";
+
+ mTestedProjectNameField = new Text(group, SWT.BORDER);
+ mTestedProjectNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mTestedProjectNameField.setToolTipText(tooltip);
+ mTestedProjectNameField.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ onProjectFieldUpdated();
+ }
+ });
+
+ mProjectBrowseButton = new Button(group, SWT.NONE);
+ mProjectBrowseButton.setText("Browse...");
+ mProjectBrowseButton.setToolTipText("Allows you to select the Android project to test.");
+ mProjectBrowseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onProjectBrowse();
+ }
+ });
+
+ mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
+ } else {
+ // Part of NPW mode: no selection.
+
+ }
+
+ // package label line
+
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Test Target Package:");
+ mTestTargetPackageLabel = new Label(group, SWT.NONE);
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 2;
+ mTestTargetPackageLabel.setLayoutData(gd);
+ }
+
+ /**
+ * Creates the target group.
+ * It only contains an SdkTargetSelector.
+ */
+ private void createTargetGroup(Composite parent) {
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ // Layout has 1 column
+ group.setLayout(new GridLayout());
+ group.setLayoutData(new GridData(GridData.FILL_BOTH));
+ group.setFont(parent.getFont());
+ group.setText("Build Target");
+
+ mToggleComposites.add(group);
+
+ // The selector is created without targets. They are added below in the change listener.
+ mSdkTargetSelector = new SdkTargetSelector(group, null);
+
+ mSdkTargetChangeListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ // Ignore
+ }
+
+ public void onTargetsLoaded() {
+ // Update the sdk target selector with the new targets
+
+ // get the targets from the sdk
+ IAndroidTarget[] targets = null;
+ if (Sdk.getCurrent() != null) {
+ targets = Sdk.getCurrent().getTargets();
+ }
+ mSdkTargetSelector.setTargets(targets);
+
+ // If there's only one target, select it
+ if (targets != null && targets.length == 1) {
+ mSdkTargetSelector.setSelection(targets[0]);
+ }
+ }
+ };
+
+ AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
+
+ // Invoke it once to initialize the targets
+ mSdkTargetChangeListener.onTargetsLoaded();
+
+ mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onSdkTargetModified();
+ updateLocationPathField(null);
+ validatePageComplete();
+ }
+ });
+ }
+
+ /**
+ * Creates the group for the project properties:
+ * - Package name [text field]
+ * - Activity name [text field]
+ * - Application name [text field]
+ *
+ * @param parent the parent composite
+ */
+ private final void createPropertiesGroup(Composite parent) {
+ // package specification group
+ Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ group.setLayout(layout);
+ group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ group.setFont(parent.getFont());
+ group.setText("Properties");
+
+ mToggleComposites.add(group);
+
+ // new application label
+ Label label = new Label(group, SWT.NONE);
+ label.setText("Application name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("Name of the Application. This is a free string. It can be empty.");
+
+ // new application name entry field
+ mApplicationNameField = new Text(group, SWT.BORDER);
+ GridData data = new GridData(GridData.FILL_HORIZONTAL);
+ mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty.");
+ mApplicationNameField.setLayoutData(data);
+ mApplicationNameField.setFont(parent.getFont());
+ mApplicationNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalApplicationNameUpdate) {
+ mApplicationNameModifiedByUser = true;
+ }
+ }
+ });
+
+ // new package label
+ label = new Label(group, SWT.NONE);
+ label.setText("Package name:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
+
+ // new package name entry field
+ mPackageNameField = new Text(group, SWT.BORDER);
+ data = new GridData(GridData.FILL_HORIZONTAL);
+ mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
+ mPackageNameField.setLayoutData(data);
+ mPackageNameField.setFont(parent.getFont());
+ mPackageNameField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mInternalPackageNameUpdate) {
+ mPackageNameModifiedByUser = true;
+ }
+ onPackageNameFieldModified();
+ }
+ });
+
+ // min sdk version label
+ label = new Label(group, SWT.NONE);
+ label.setText("Min SDK Version:");
+ label.setFont(parent.getFont());
+ label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
+
+ // min sdk version entry field
+ mMinSdkVersionField = new Text(group, SWT.BORDER);
+ data = new GridData(GridData.FILL_HORIZONTAL);
+ label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
+ mMinSdkVersionField.setLayoutData(data);
+ mMinSdkVersionField.setFont(parent.getFont());
+ mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
+ public void handleEvent(Event event) {
+ onMinSdkVersionFieldModified();
+ validatePageComplete();
+ }
+ });
+ }
+
+
+ //--- Internal getters & setters ------------------
+
+ /** Returns the location path field value with spaces trimmed. */
+ private String getLocationPathFieldValue() {
+ return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); //$NON-NLS-1$
+ }
+
+ /** Returns the current project location, depending on the Use Default Location check box. */
+ private String getProjectLocation() {
+ if (mInfo.useDefaultLocation()) {
+ return Platform.getLocation().toString();
+ } else {
+ return getLocationPathFieldValue();
+ }
+ }
+
+ /**
+ * Creates a project resource handle for the current project name field
+ * value.
+ * <p>
+ * This method does not create the project resource; this is the
+ * responsibility of <code>IProject::create</code> invoked by the new
+ * project resource wizard.
+ * </p>
+ *
+ * @return the new project resource handle
+ */
+ private IProject getProjectHandle() {
+ return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
+ }
+
+ // --- UI Callbacks ----
+
+ /**
+ * Callback invoked when the user toggles the "Test target: Existing Android Project"
+ * checkbox. It enables or disable the UI to select an existing project.
+ */
+ private void onExistingProjectChanged() {
+ if (mInfo.isTestingExisting()) {
+ boolean enabled = mTestExistingProjectRadio.getSelection();
+ mTestedProjectNameField.setEnabled(enabled);
+ mProjectBrowseButton.setEnabled(enabled);
+ setExistingProject(mInfo.getExistingTestedProject());
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * Tries to load the defaults from the main page if possible.
+ */
+ private void useMainProjectInformation() {
+ if (mInfo.isTestingMain() && mMainInfo != null) {
+
+ String projName = String.format("%1$sTest", mMainInfo.getProjectName());
+ String appName = String.format("%1$sTest", mMainInfo.getApplicationName());
+
+ String packageName = mMainInfo.getPackageName();
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+
+ updateTestTargetPackageField(packageName);
+
+ if (!mProjectNameModifiedByUser) {
+ mInternalProjectNameUpdate = true;
+ mProjectNameField.setText(projName); //$NON-NLS-1$
+ mInternalProjectNameUpdate = false;
+ }
+
+ if (!mApplicationNameModifiedByUser) {
+ mInternalApplicationNameUpdate = true;
+ mApplicationNameField.setText(appName);
+ mInternalApplicationNameUpdate = false;
+ }
+
+ if (!mPackageNameModifiedByUser) {
+ mInternalPackageNameUpdate = true;
+ packageName += ".test"; //$NON-NLS-1$
+ mPackageNameField.setText(packageName);
+ mInternalPackageNameUpdate = false;
+ }
+
+ if (!mSdkTargetModifiedByUser) {
+ mInternalSdkTargetUpdate = true;
+ mSdkTargetSelector.setSelection(mMainInfo.getSdkTarget());
+ mInternalSdkTargetUpdate = false;
+ }
+
+ if (!mMinSdkVersionModifiedByUser) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(mMainInfo.getMinSdkVersion());
+ mInternalMinSdkVersionUpdate = false;
+ }
+ }
+ }
+
+ /**
+ * Callback invoked when the user edits the project text field.
+ */
+ private void onProjectFieldUpdated() {
+ String project = mTestedProjectNameField.getText();
+
+ // Is this a valid project?
+ IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/);
+ for (IJavaProject p : projects) {
+ if (p.getProject().getName().equals(project)) {
+ setExistingProject(p.getProject());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Callback called when the user uses the "Browse Projects" button.
+ */
+ private void onProjectBrowse() {
+ IJavaProject p = mProjectChooserHelper.chooseJavaProject(mTestedProjectNameField.getText());
+ if (p != null) {
+ setExistingProject(p.getProject());
+ mTestedProjectNameField.setText(mExistingTestedProject.getName());
+ }
+ }
+
+ private void setExistingProject(IProject project) {
+ mExistingTestedProject = project;
+
+ // Try to update the application, package, sdk target and minSdkVersion accordingly
+ if (project != null &&
+ (!mApplicationNameModifiedByUser ||
+ !mPackageNameModifiedByUser ||
+ !mSdkTargetModifiedByUser ||
+ !mMinSdkVersionModifiedByUser)) {
+
+ IFile file = AndroidManifestParser.getManifest(project);
+ AndroidManifestParser manifestData = null;
+ if (file != null) {
+ try {
+ manifestData = AndroidManifestParser.parseForData(file);
+ } catch (CoreException e) {
+ // pass
+ }
+ }
+
+ if (manifestData != null) {
+ String appName = String.format("%1$sTest", project.getName());
+ String packageName = manifestData.getPackage();
+ int minSdkVersion = manifestData.getApiLevelRequirement();
+ IAndroidTarget sdkTarget = null;
+ if (Sdk.getCurrent() != null) {
+ sdkTarget = Sdk.getCurrent().getTarget(project);
+ }
+
+ if (packageName == null) {
+ packageName = ""; //$NON-NLS-1$
+ }
+ mLastExistingPackageName = packageName;
+
+ if (!mProjectNameModifiedByUser) {
+ mInternalProjectNameUpdate = true;
+ mProjectNameField.setText(appName);
+ mInternalProjectNameUpdate = false;
+ }
+
+ if (!mApplicationNameModifiedByUser) {
+ mInternalApplicationNameUpdate = true;
+ mApplicationNameField.setText(appName);
+ mInternalApplicationNameUpdate = false;
+ }
+
+ if (!mPackageNameModifiedByUser) {
+ mInternalPackageNameUpdate = true;
+ packageName += ".test"; //$NON-NLS-1$
+ mPackageNameField.setText(packageName); //$NON-NLS-1$
+ mInternalPackageNameUpdate = false;
+ }
+
+ if (!mSdkTargetModifiedByUser && sdkTarget != null) {
+ mInternalSdkTargetUpdate = true;
+ mSdkTargetSelector.setSelection(sdkTarget);
+ mInternalSdkTargetUpdate = false;
+ }
+
+ if (!mMinSdkVersionModifiedByUser) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(
+ minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK ?
+ Integer.toString(minSdkVersion) : ""); //$NON-NLS-1$
+ if (sdkTarget == null) {
+ updateSdkSelectorToMatchMinSdkVersion();
+ }
+ mInternalMinSdkVersionUpdate = false;
+ }
+ }
+ }
+
+ updateTestTargetPackageField(mLastExistingPackageName);
+ validatePageComplete();
+ }
+
+ /**
+ * Display a directory browser and update the location path field with the selected path
+ */
+ private void onOpenDirectoryBrowser() {
+
+ String existing_dir = getLocationPathFieldValue();
+
+ // Disable the path if it doesn't exist
+ if (existing_dir.length() == 0) {
+ existing_dir = null;
+ } else {
+ File f = new File(existing_dir);
+ if (!f.exists()) {
+ existing_dir = null;
+ }
+ }
+
+ DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
+ dd.setMessage("Browse for folder");
+ dd.setFilterPath(existing_dir);
+ String abs_dir = dd.open();
+
+ if (abs_dir != null) {
+ updateLocationPathField(abs_dir);
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * Callback when the "create test project" checkbox is changed.
+ * It enables or disables all UI groups accordingly.
+ */
+ private void onCreateTestProjectToggle() {
+ boolean enabled = mInfo.getCreateTestProject();
+ for (Composite c : mToggleComposites) {
+ enableControl(c, enabled);
+ }
+ mSdkTargetSelector.setEnabled(enabled);
+
+ if (enabled) {
+ useMainProjectInformation();
+ }
+ validatePageComplete();
+ }
+
+ /** Enables or disables controls; recursive for composite controls. */
+ private void enableControl(Control c, boolean enabled) {
+ c.setEnabled(enabled);
+ if (c instanceof Composite)
+ for (Control c2 : ((Composite) c).getChildren()) {
+ enableControl(c2, enabled);
+ }
+ }
+
+ /**
+ * Enables or disable the location widgets depending on the user selection:
+ * the location path is enabled when using the "existing source" mode (i.e. not new project)
+ * or in new project mode with the "use default location" turned off.
+ */
+ private void enableLocationWidgets() {
+ boolean use_default = mInfo.useDefaultLocation();
+ boolean location_enabled = !use_default;
+
+ mLocationLabel.setEnabled(location_enabled);
+ mLocationPathField.setEnabled(location_enabled);
+ mBrowseButton.setEnabled(location_enabled);
+
+ updateLocationPathField(null);
+ }
+
+ /**
+ * Updates the location directory path field.
+ * <br/>
+ * When custom user selection is enabled, use the abs_dir argument if not null and also
+ * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the
+ * user selection to be remembered when the user switches from default to custom.
+ * <br/>
+ * When custom user selection is disabled, use the workspace default location with the
+ * current project name. This does not change the internally cached abs_dir.
+ *
+ * @param abs_dir A new absolute directory path or null to use the default.
+ */
+ private void updateLocationPathField(String abs_dir) {
+ boolean use_default = mInfo.useDefaultLocation();
+ boolean custom_location = !use_default;
+
+ if (!mInternalLocationPathUpdate) {
+ mInternalLocationPathUpdate = true;
+ if (custom_location) {
+ if (abs_dir != null) {
+ // We get here if the user selected a directory with the "Browse" button.
+ sCustomLocationOsPath = TextProcessor.process(abs_dir);
+ }
+ if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) {
+ mLocationPathField.setText(sCustomLocationOsPath);
+ }
+ } else {
+ String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
+ value = TextProcessor.process(value);
+ if (!mLocationPathField.getText().equals(value)) {
+ mLocationPathField.setText(value);
+ }
+ }
+ validatePageComplete();
+ mInternalLocationPathUpdate = false;
+ }
+ }
+
+ /**
+ * The location path field is either modified internally (from updateLocationPathField)
+ * or manually by the user when the custom_location mode is not set.
+ *
+ * Ignore the internal modification. When modified by the user, memorize the choice and
+ * validate the page.
+ */
+ private void onLocationPathFieldModified() {
+ if (!mInternalLocationPathUpdate) {
+ // When the updates doesn't come from updateLocationPathField, it must be the user
+ // editing the field manually, in which case we want to save the value internally
+ String newPath = getLocationPathFieldValue();
+ sCustomLocationOsPath = newPath;
+ validatePageComplete();
+ }
+ }
+
+ /**
+ * The package name field is either modified internally (from extractNamesFromAndroidManifest)
+ * or manually by the user when the custom_location mode is not set.
+ *
+ * Ignore the internal modification. When modified by the user, memorize the choice and
+ * validate the page.
+ */
+ private void onPackageNameFieldModified() {
+ updateTestTargetPackageField(null);
+ validatePageComplete();
+ }
+
+ /**
+ * Changes the {@link #mTestTargetPackageLabel} field.
+ *
+ * When using the "self-test" option, the packageName argument is ignored and the
+ * current value from the project package is used.
+ *
+ * Otherwise the packageName is used if it is not null.
+ */
+ private void updateTestTargetPackageField(String packageName) {
+ if (mInfo.isTestingSelf()) {
+ mTestTargetPackageLabel.setText(mInfo.getPackageName());
+
+ } else if (packageName != null) {
+ mTestTargetPackageLabel.setText(packageName);
+ }
+ }
+
+ /**
+ * Called when the min sdk version field has been modified.
+ *
+ * Ignore the internal modifications. When modified by the user, try to match
+ * a target with the same API level.
+ */
+ private void onMinSdkVersionFieldModified() {
+ if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
+ return;
+ }
+
+ updateSdkSelectorToMatchMinSdkVersion();
+
+ mMinSdkVersionModifiedByUser = true;
+ }
+
+ /**
+ * Try to find an SDK Target that matches the current MinSdkVersion.
+ *
+ * There can be multiple targets with the same sdk api version, so don't change
+ * it if it's already at the right version. Otherwise pick the first target
+ * that matches.
+ */
+ private void updateSdkSelectorToMatchMinSdkVersion() {
+ try {
+ int version = Integer.parseInt(mInfo.getMinSdkVersion());
+
+ IAndroidTarget curr_target = mInfo.getSdkTarget();
+ if (curr_target != null && curr_target.getApiVersionNumber() == version) {
+ return;
+ }
+
+ for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
+ if (target.getApiVersionNumber() == version) {
+ mSdkTargetSelector.setSelection(target);
+ return;
+ }
+ }
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * Called when an SDK target is modified.
+ *
+ * Also changes the minSdkVersion field to reflect the sdk api level that has
+ * just been selected.
+ */
+ private void onSdkTargetModified() {
+ if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
+ return;
+ }
+
+ IAndroidTarget target = mInfo.getSdkTarget();
+
+ if (target != null) {
+ mInternalMinSdkVersionUpdate = true;
+ mMinSdkVersionField.setText(Integer.toString(target.getApiVersionNumber()));
+ mInternalMinSdkVersionUpdate = false;
+ }
+
+ mSdkTargetModifiedByUser = true;
+ }
+
+ /**
+ * Returns whether this page's controls currently all contain valid values.
+ *
+ * @return <code>true</code> if all controls are valid, and
+ * <code>false</code> if at least one is invalid
+ */
+ private boolean validatePage() {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+
+ int status = MSG_NONE;
+
+ // there is nothing to validate if we're not going to create a test project
+ if (mInfo.getCreateTestProject()) {
+ status = validateProjectField(workspace);
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateLocationPath(workspace);
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateTestTarget();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateSdkTarget();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validatePackageField();
+ }
+ if ((status & MSG_ERROR) == 0) {
+ status |= validateMinSdkVersionField();
+ }
+ }
+ if (status == MSG_NONE) {
+ setStatus(null, MSG_NONE);
+ }
+
+ // Return false if there's an error so that the finish button be disabled.
+ return (status & MSG_ERROR) == 0;
+ }
+
+ /**
+ * Validates the page and updates the Next/Finish buttons
+ */
+ private void validatePageComplete() {
+ setPageComplete(validatePage());
+ }
+
+ /**
+ * Validates the test target (self, main project or existing project)
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateTestTarget() {
+ if (mInfo.isTestingExisting() && mInfo.getExistingTestedProject() == null) {
+ return setStatus("Please select an existing Android project as a test target.",
+ MSG_ERROR);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the project name field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateProjectField(IWorkspace workspace) {
+ // Validate project field
+ String projectName = mInfo.getProjectName();
+ if (projectName.length() == 0) {
+ return setStatus("Project name must be specified", MSG_ERROR);
+ }
+
+ // Limit the project name to shell-agnostic characters since it will be used to
+ // generate the final package
+ if (!sProjectNamePattern.matcher(projectName).matches()) {
+ return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
+ MSG_ERROR);
+ }
+
+ IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
+ if (!nameStatus.isOK()) {
+ return setStatus(nameStatus.getMessage(), MSG_ERROR);
+ }
+
+ if (mMainInfo != null && projectName.equals(mMainInfo.getProjectName())) {
+ return setStatus("The main project name and the test project name must be different.",
+ MSG_ERROR);
+ }
+
+ if (getProjectHandle().exists()) {
+ return setStatus("A project with that name already exists in the workspace",
+ MSG_ERROR);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the location path field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateLocationPath(IWorkspace workspace) {
+ Path path = new Path(getProjectLocation());
+ if (!mInfo.useDefaultLocation()) {
+ // If not using the default value validate the location.
+ URI uri = URIUtil.toURI(path.toOSString());
+ IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
+ uri);
+ if (!locationStatus.isOK()) {
+ return setStatus(locationStatus.getMessage(), MSG_ERROR);
+ } else {
+ // The location is valid as far as Eclipse is concerned (i.e. mostly not
+ // an existing workspace project.) Check it either doesn't exist or is
+ // a directory that is empty.
+ File f = path.toFile();
+ if (f.exists() && !f.isDirectory()) {
+ return setStatus("A directory name must be specified.", MSG_ERROR);
+ } else if (f.isDirectory()) {
+ // However if the directory exists, we should put a warning if it is not
+ // empty. We don't put an error (we'll ask the user again for confirmation
+ // before using the directory.)
+ String[] l = f.list();
+ if (l.length != 0) {
+ return setStatus("The selected output directory is not empty.",
+ MSG_WARNING);
+ }
+ }
+ }
+ } else {
+ // Otherwise validate the path string is not empty
+ if (getProjectLocation().length() == 0) {
+ return setStatus("A directory name must be specified.", MSG_ERROR);
+ }
+
+ File dest = path.append(mInfo.getProjectName()).toFile();
+ if (dest.exists()) {
+ return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
+ mInfo.getProjectName()), MSG_ERROR);
+ }
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the sdk target choice.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateSdkTarget() {
+ if (mInfo.getSdkTarget() == null) {
+ return setStatus("An SDK Target must be specified.", MSG_ERROR);
+ }
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the sdk target choice.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validateMinSdkVersionField() {
+
+ // If the min sdk version is empty, it is always accepted.
+ if (mInfo.getMinSdkVersion().length() == 0) {
+ return MSG_NONE;
+ }
+
+ int version = AndroidManifestParser.INVALID_MIN_SDK;
+ try {
+ // If not empty, it must be a valid integer > 0
+ version = Integer.parseInt(mInfo.getMinSdkVersion());
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+
+ if (version < 1) {
+ return setStatus("Min SDK Version must be an integer > 0.", MSG_ERROR);
+ }
+
+ if (mInfo.getSdkTarget() != null && mInfo.getSdkTarget().getApiVersionNumber() != version) {
+ return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
+ MSG_WARNING);
+ }
+
+ return MSG_NONE;
+ }
+
+ /**
+ * Validates the package name field.
+ *
+ * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
+ */
+ private int validatePackageField() {
+ // Validate package field
+ String packageName = mInfo.getPackageName();
+ if (packageName.length() == 0) {
+ return setStatus("Project package name must be specified.", MSG_ERROR);
+ }
+
+ // Check it's a valid package string
+ int result = MSG_NONE;
+ IStatus status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
+ if (!status.isOK()) {
+ result = setStatus(String.format("Project package: %s", status.getMessage()),
+ status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
+ }
+
+ // The Android Activity Manager does not accept packages names with only one
+ // identifier. Check the package name has at least one dot in them (the previous rule
+ // validated that if such a dot exist, it's not the first nor last characters of the
+ // string.)
+ if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
+ return setStatus("Project package name must have at least two identifiers.", MSG_ERROR);
+ }
+
+ // Check that the target package name is valid too
+ packageName = mInfo.getTargetPackageName();
+ if (packageName.length() == 0) {
+ return setStatus("Target package name must be specified.", MSG_ERROR);
+ }
+
+ // Check it's a valid package string
+ status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
+ if (!status.isOK()) {
+ result = setStatus(String.format("Target package: %s", status.getMessage()),
+ status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
+ }
+
+ if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
+ return setStatus("Target name must have at least two identifiers.", MSG_ERROR);
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets the error message for the wizard with the given message icon.
+ *
+ * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING.
+ * @return As a convenience, always returns messageType so that the caller can return
+ * immediately.
+ */
+ private int setStatus(String message, int messageType) {
+ if (message == null) {
+ setErrorMessage(null);
+ setMessage(null);
+ } else if (!message.equals(getMessage())) {
+ setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR);
+ }
+ return messageType;
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java
new file mode 100755
index 0000000..dd5cf76
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewTestProjectWizard.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.wizards.newproject;
+
+
+/**
+ * A "New Test Android Project" Wizard.
+ * <p/>
+ * This is really the {@link NewProjectWizard} that only displays the "test project" page.
+ */
+public class NewTestProjectWizard extends NewProjectWizard {
+
+ public NewTestProjectWizard() {
+ super(AvailablePages.TEST_PROJECT_ONLY);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java
deleted file mode 100644
index 345c663..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.common;
-
-import com.android.sdkstats.SdkStatsService;
-
-import org.osgi.framework.Version;
-
-/**
- * Helper class to access the ping usage stat server.
- */
-public class SdkStatsHelper {
-
- /**
- * Pings the usage start server.
- * @param pluginName the name of the plugin to appear in the stats
- * @param pluginVersion the {@link Version} of the plugin.
- */
- public static void pingUsageServer(String pluginName, Version pluginVersion) {
- String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(),
- pluginVersion.getMinor(), pluginVersion.getMicro());
-
- SdkStatsService.ping(pluginName, versionString);
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
index 3b5c823..cd02e27 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
@@ -334,10 +334,12 @@ public class AndroidManifestParser {
value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION,
true /* hasNamespace */);
- try {
- mApiLevelRequirement = Integer.parseInt(value);
- } catch (NumberFormatException e) {
- handleError(e, -1 /* lineNumber */);
+ if (value != null) {
+ try {
+ mApiLevelRequirement = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ handleError(e, -1 /* lineNumber */);
+ }
}
} else if (NODE_INSTRUMENTATION.equals(localName)) {
processInstrumentationNode(attributes);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java
index 1810ad2..589e70b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java
@@ -109,17 +109,22 @@ public class XmlErrorHandler extends DefaultHandler {
mErrorListener.errorFound();
}
+ String message = exception.getMessage();
+ if (message == null) {
+ message = "Unknown error " + exception.getClass().getCanonicalName();
+ }
+
if (mFile != null) {
if (lineNumber != -1) {
BaseProjectHelper.addMarker(mFile,
AndroidConstants.MARKER_XML,
- exception.getMessage(),
+ message,
lineNumber,
IMarker.SEVERITY_ERROR);
} else {
BaseProjectHelper.addMarker(mFile,
AndroidConstants.MARKER_XML,
- exception.getMessage(),
+ message,
IMarker.SEVERITY_ERROR);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java
index dc32383..c2cd975 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java
@@ -55,8 +55,10 @@ import javax.xml.xpath.XPathExpressionException;
* Multi-page form editor for AndroidManifest.xml.
*/
public final class ManifestEditor extends AndroidEditor {
- private final static String EMPTY = ""; //$NON-NLS-1$
+ public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".manifest.ManifestEditor"; //$NON-NLS-1$
+
+ private final static String EMPTY = ""; //$NON-NLS-1$
/** Root node of the UI element hierarchy */
private UiElementNode mUiManifestNode;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
index 5d1abab..2da79c3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
@@ -193,7 +193,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider {
overrides.put("*/theme", ThemeAttributeDescriptor.class); //$NON-NLS-1$
overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$
- overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$
+ overrides.put("*/targetPackage", ManifestPkgAttrDescriptor.class); //$NON-NLS-1$
overrides.put("uses-library/name", ListAttributeDescriptor.class); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java
new file mode 100755
index 0000000..804d0ad
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestPkgAttrDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.editors.manifest.descriptors;
+
+import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.editors.manifest.model.UiManifestPkgAttrNode;
+import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
+import com.android.ide.eclipse.editors.uimodel.UiElementNode;
+
+/**
+ * Describes a package XML attribute. It is displayed by a {@link UiManifestPkgAttrNode}.
+ */
+public class ManifestPkgAttrDescriptor extends TextAttributeDescriptor {
+
+ public ManifestPkgAttrDescriptor(String xmlLocalName, String uiName, String nsUri,
+ String tooltip) {
+ super(xmlLocalName, uiName, nsUri, tooltip);
+ }
+
+ /**
+ * @return A new {@link UiManifestPkgAttrNode} linked to this descriptor.
+ */
+ @Override
+ public UiAttributeNode createUiNode(UiElementNode uiParent) {
+ return new UiManifestPkgAttrNode(this, uiParent);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
index c872b6f..f5c0cfa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
@@ -463,7 +463,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
* Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of
* the specified project.
* @param project the project
- * @param b
+ * @param include_containers True to include containers
* @return an array of IPackageFragmentRoot.
*/
private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestPkgAttrNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestPkgAttrNode.java
new file mode 100755
index 0000000..4677129
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestPkgAttrNode.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.editors.manifest.model;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.wizards.actions.NewProjectAction;
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard;
+import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
+import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.editors.ui.SectionHelper;
+import com.android.ide.eclipse.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Text;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormText;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.part.FileEditorInput;
+
+import java.util.TreeSet;
+
+/**
+ * Represents an XML attribute to select an exisintg manifest package, that can be modified using
+ * a simple text field or a dialog to choose an existing package.
+ * <p/>
+ * See {@link UiTextAttributeNode} for more information.
+ */
+public class UiManifestPkgAttrNode extends UiTextAttributeNode {
+
+ /**
+ * Creates a {@link UiManifestPkgAttrNode} object that will display ui to select or create
+ * a manifest package.
+ * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
+ */
+ public UiManifestPkgAttrNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
+ super(attributeDescriptor, uiParent);
+ }
+
+ /* (non-java doc)
+ * Creates a label widget and an associated text field.
+ * <p/>
+ * As most other parts of the android manifest editor, this assumes the
+ * parent uses a table layout with 2 columns.
+ */
+ @Override
+ public void createUiControl(final Composite parent, final IManagedForm managedForm) {
+ setManagedForm(managedForm);
+ FormToolkit toolkit = managedForm.getToolkit();
+ TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
+
+ StringBuilder label = new StringBuilder();
+ label.append("<form><p><a href='unused'>"); //$NON-NLS-1$
+ label.append(desc.getUiName());
+ label.append("</a></p></form>"); //$NON-NLS-1$
+ FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
+ label.toString(), true /* setupLayoutData */);
+ formText.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(HyperlinkEvent e) {
+ super.linkActivated(e);
+ doLabelClick();
+ }
+ });
+ formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
+ SectionHelper.addControlTooltip(formText, desc.getTooltip());
+
+ Composite composite = toolkit.createComposite(parent);
+ composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
+ GridLayout gl = new GridLayout(2, false);
+ gl.marginHeight = gl.marginWidth = 0;
+ composite.setLayout(gl);
+ // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
+ // for the text field below
+ toolkit.paintBordersFor(composite);
+
+ final Text text = toolkit.createText(composite, getCurrentValue());
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK
+ text.setLayoutData(gd);
+
+ setTextWidget(text);
+
+ Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
+
+ browseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ doBrowseClick();
+ }
+ });
+
+ }
+
+ /* (non-java doc)
+ * Adds a validator to the text field that calls managedForm.getMessageManager().
+ */
+ @Override
+ protected void onAddValidators(final Text text) {
+ ModifyListener listener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ String package_name = text.getText();
+ if (package_name.indexOf('.') < 1) {
+ getManagedForm().getMessageManager().addMessage(text,
+ "Package name should contain at least two identifiers.",
+ null /* data */, IMessageProvider.ERROR, text);
+ } else {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ }
+ };
+
+ text.addModifyListener(listener);
+
+ // Make sure the validator removes its message(s) when the widget is disposed
+ text.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ getManagedForm().getMessageManager().removeMessage(text, text);
+ }
+ });
+
+ // Finally call the validator once to make sure the initial value is processed
+ listener.modifyText(null);
+ }
+
+ /**
+ * Handles response to the Browse button by creating a Package dialog.
+ * */
+ private void doBrowseClick() {
+
+ // Display the list of AndroidManifest packages in a selection dialog
+ ElementListSelectionDialog dialog = new ElementListSelectionDialog(
+ getTextWidget().getShell(),
+ new ILabelProvider() {
+ public Image getImage(Object element) {
+ return null;
+ }
+
+ public String getText(Object element) {
+ return element.toString();
+ }
+
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ public void dispose() {
+ }
+
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ public void removeListener(ILabelProviderListener listener) {
+ }
+ });
+
+ dialog.setTitle("Android Manifest Package Selection");
+ dialog.setMessage("Select the Android Manifest package to target.");
+
+ dialog.setElements(getPossibleValues(null));
+
+ // open the dialog and use the object selected if OK was clicked, or null otherwise
+ if (dialog.open() == Window.OK) {
+ String result = (String) dialog.getFirstResult();
+ if (result != null && result.length() > 0) {
+ getTextWidget().setText(result);
+ }
+ }
+ }
+
+ /**
+ * Handles response to the Label hyper link being activated.
+ */
+ private void doLabelClick() {
+ // get the current package name
+ String package_name = getTextWidget().getText().trim();
+
+ if (package_name.length() == 0) {
+ createNewProject();
+ } else {
+ displayExistingManifest(package_name);
+ }
+ }
+
+ /**
+ * When the label is clicked and there's already a package name, this method
+ * attempts to find the project matching the android package name and it attempts
+ * to open the manifest editor.
+ *
+ * @param package_name The android package name to find. Must not be null.
+ */
+ private void displayExistingManifest(String package_name) {
+
+ // Look for the first project that uses this package name
+ for (IJavaProject project : BaseProjectHelper.getAndroidProjects()) {
+ // check that there is indeed a manifest file.
+ IFile manifestFile = AndroidManifestParser.getManifest(project.getProject());
+ if (manifestFile == null) {
+ // no file? skip this project.
+ continue;
+ }
+
+ AndroidManifestParser parser = null;
+ try {
+ parser = AndroidManifestParser.parseForData(manifestFile);
+ } catch (CoreException e) {
+ // ignore, handled below.
+ }
+ if (parser == null) {
+ // skip this project.
+ continue;
+ }
+
+ if (package_name.equals(parser.getPackage())) {
+ // Found the project.
+
+ IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (win != null) {
+ IWorkbenchPage page = win.getActivePage();
+ if (page != null) {
+ try {
+ page.openEditor(
+ new FileEditorInput(manifestFile),
+ ManifestEditor.ID,
+ true, /* activate */
+ IWorkbenchPage.MATCH_INPUT);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e,
+ "Opening editor failed for %s", //$NON-NLS-1$
+ manifestFile.getFullPath());
+ }
+ }
+ }
+
+ // We found the project; even if we failed there's no need to keep looking.
+ return;
+ }
+ }
+ }
+
+ /**
+ * Displays the New Project Wizard to create a new project.
+ * If one is successfully created, use the Android Package name.
+ */
+ private void createNewProject() {
+
+ NewProjectAction npwAction = new NewProjectAction();
+ npwAction.run(null /*action*/);
+ if (npwAction.getDialogResult() == Dialog.OK) {
+ NewProjectWizard npw = (NewProjectWizard) npwAction.getWizard();
+ String name = npw.getPackageName();
+ if (name != null && name.length() > 0) {
+ getTextWidget().setText(name);
+ }
+ }
+ }
+
+ /**
+ * Returns all the possible android package names that could be used.
+ * The prefix is not used.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getPossibleValues(String prefix) {
+ TreeSet<String> packages = new TreeSet<String>();
+
+ for (IJavaProject project : BaseProjectHelper.getAndroidProjects()) {
+ // check that there is indeed a manifest file.
+ IFile manifestFile = AndroidManifestParser.getManifest(project.getProject());
+ if (manifestFile == null) {
+ // no file? skip this project.
+ continue;
+ }
+
+ AndroidManifestParser parser = null;
+ try {
+ parser = AndroidManifestParser.parseForData(manifestFile);
+ } catch (CoreException e) {
+ // ignore, handled below.
+ }
+ if (parser == null) {
+ // skip this project.
+ continue;
+ }
+
+ packages.add(parser.getPackage());
+ }
+
+ return packages.toArray(new String[packages.size()]);
+ }
+}
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
index b43e75f..5d57413 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template
@@ -5,6 +5,8 @@
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="APPLICATION_NAME">
ACTIVITIES
+TEST-USES-LIBRARY
</application>
USES-SDK
+TEST-INSTRUMENTATION
</manifest>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template
new file mode 100755
index 0000000..c282fbc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_instrumentation.template
@@ -0,0 +1 @@
+ <instrumentation android:targetPackage="TEST_TARGET_PCKG" android:name="android.test.InstrumentationTestRunner" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template
new file mode 100755
index 0000000..28ae7a4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/test_uses-library.template
@@ -0,0 +1 @@
+ <uses-library android:name="android.test.runner" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
index 09b8085..2488fd1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Dalvik Debug Monitor Service
Bundle-SymbolicName: com.android.ide.eclipse.ddms;singleton:=true
-Bundle-Version: 0.9.0.qualifier
+Bundle-Version: 0.9.1.qualifier
Bundle-Activator: com.android.ide.eclipse.ddms.DdmsPlugin
Bundle-Vendor: The Android Open Source Project
Bundle-Localization: plugin
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
index c7f5ba8..22cda71 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Plugin Tests
Bundle-SymbolicName: com.android.ide.eclipse.tests
-Bundle-Version: 0.9.0.qualifier
+Bundle-Version: 0.9.1.qualifier
Bundle-Activator: com.android.ide.eclipse.tests.AndroidTestPlugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java
index 42f8df0..50fb622 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java
@@ -15,6 +15,11 @@
*/
package com.android.ide.eclipse.adt.wizards.newproject;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
import java.io.File;
/**
@@ -26,46 +31,60 @@ public class StubSampleProjectCreationPage extends NewProjectCreationPage {
private String mSampleProjectName;
private String mOsSdkLocation;
- public StubSampleProjectCreationPage(String pageName,
- String sampleProjectName, String osSdkLocation) {
- super(pageName);
+ public StubSampleProjectCreationPage(String sampleProjectName, String osSdkLocation) {
+ super();
this.mSampleProjectName = sampleProjectName;
this.mOsSdkLocation = osSdkLocation;
}
@Override
- public String getProjectName() {
- return mSampleProjectName;
- }
+ public IMainInfo getMainInfo() {
+ return new IMainInfo() {
+ public String getProjectName() {
+ return mSampleProjectName;
+ }
- @Override
- public String getPackageName() {
- return "com.android.samples";
- }
+ public String getPackageName() {
+ return "com.android.samples";
+ }
- @Override
- public String getActivityName() {
- return mSampleProjectName;
- }
+ public String getActivityName() {
+ return mSampleProjectName;
+ }
- @Override
- public String getApplicationName() {
- return mSampleProjectName;
- }
+ public String getApplicationName() {
+ return mSampleProjectName;
+ }
- @Override
- public boolean isNewProject() {
- return false;
- }
+ public boolean isNewProject() {
+ return false;
+ }
- @Override
- public String getProjectLocation() {
- return mOsSdkLocation + File.separator + "samples" + File.separator + mSampleProjectName;
- }
+ public String getSourceFolder() {
+ return "src";
+ }
- @Override
- public String getSourceFolder() {
- return "src";
- }
+ public IPath getLocationPath() {
+ return new Path(mOsSdkLocation + File.separator +
+ "samples" + File.separator +
+ mSampleProjectName);
+ }
+ public String getMinSdkVersion() {
+ return null;
+ }
+
+ public IAndroidTarget getSdkTarget() {
+ return null;
+ }
+
+ public boolean isCreateActivity() {
+ return false;
+ }
+
+ public boolean useDefaultLocation() {
+ return false;
+ }
+ };
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java
index 40cd636..e598df3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java
@@ -49,8 +49,7 @@ public class StubSampleProjectWizard extends NewProjectWizard {
*/
@Override
protected NewProjectCreationPage createMainPage() {
- return new StubSampleProjectCreationPage(MAIN_PAGE_NAME,
- mSampleProjectName, mOsSdkLocation);
+ return new StubSampleProjectCreationPage(mSampleProjectName, mOsSdkLocation);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java
index 6aaa209..5a413fa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java
@@ -67,8 +67,6 @@ public class EclipseTestCollector {
/**
* Returns true if given class should be added to suite
- * @param testClass
- * @return
*/
protected boolean isTestClass(Class<?> testClass) {
return TestCase.class.isAssignableFrom(testClass) &&
@@ -78,8 +76,6 @@ public class EclipseTestCollector {
/**
* Returns true if given class has a public constructor
- * @param testClass
- * @return
*/
protected boolean hasPublicConstructor(Class<?> testClass) {
try {
@@ -94,7 +90,6 @@ public class EclipseTestCollector {
* Load the class given by the plugin aka bundle file path
* @param filePath - path of class in bundle
* @param expectedPackage - expected package of class
- * @return
* @throws ClassNotFoundException
*/
protected Class<?> getClass(String filePath, String expectedPackage) throws ClassNotFoundException {
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
index 2f93e51..884efe8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.common.project;
+import com.android.ide.eclipse.common.project.AndroidManifestParser.Activity;
import com.android.ide.eclipse.tests.AdtTestData;
import junit.framework.TestCase;
@@ -63,11 +64,29 @@ public class AndroidManifestParserTest extends TestCase {
public void testGetActivities() {
assertEquals(1, mManifestTestApp.getActivities().length);
- assertEquals(ACTIVITY_NAME, mManifestTestApp.getActivities()[0]);
+ Activity activity = new AndroidManifestParser.Activity(ACTIVITY_NAME, true);
+ activity.setHasAction(true);
+ activity.setHasLauncherCategory(true);
+ activity.setHasMainAction(true);
+ assertEquals(activity, mManifestTestApp.getActivities()[0]);
}
public void testGetLauncherActivity() {
- assertEquals(ACTIVITY_NAME, mManifestTestApp.getLauncherActivity());
+ Activity activity = new AndroidManifestParser.Activity(ACTIVITY_NAME, true);
+ activity.setHasAction(true);
+ activity.setHasLauncherCategory(true);
+ activity.setHasMainAction(true);
+ assertEquals(activity, mManifestTestApp.getLauncherActivity());
+ }
+
+ private void assertEquals(Activity lhs, Activity rhs) {
+ assertTrue(lhs == rhs || (lhs != null && rhs != null));
+ if (lhs != null && rhs != null) {
+ assertEquals(lhs.getName(), rhs.getName());
+ assertEquals(lhs.isExported(), rhs.isExported());
+ assertEquals(lhs.hasAction(), rhs.hasAction());
+ assertEquals(lhs.isHomeActivity(), rhs.isHomeActivity());
+ }
}
public void testGetUsesLibraries() {
diff --git a/eclipse/sites/external/site.xml b/eclipse/sites/external/site.xml
index 9fca8d2..404abe5 100644
--- a/eclipse/sites/external/site.xml
+++ b/eclipse/sites/external/site.xml
@@ -3,10 +3,10 @@
<description url="https://dl-ssl.google.com/android/eclipse/">
Update Site for Android Development Toolkit
</description>
- <feature url="features/com.android.ide.eclipse.adt_0.9.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.0.qualifier">
+ <feature url="features/com.android.ide.eclipse.adt_0.9.1.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.1.qualifier">
<category name="developer"/>
</feature>
- <feature url="features/com.android.ide.eclipse.ddms_0.9.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.0.qualifier">
+ <feature url="features/com.android.ide.eclipse.ddms_0.9.1.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.1.qualifier">
<category name="developer"/>
</feature>
<category-def name="developer" label="Developer Tools">
diff --git a/eclipse/sites/internal/site.xml b/eclipse/sites/internal/site.xml
index 9f2642f..b53af92 100644
--- a/eclipse/sites/internal/site.xml
+++ b/eclipse/sites/internal/site.xml
@@ -3,13 +3,13 @@
<description url="https://android.corp.google.com/adt/">
Update Site for Android Development Toolkit
</description>
- <feature url="features/com.android.ide.eclipse.adt_0.9.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.0.qualifier">
+ <feature url="features/com.android.ide.eclipse.adt_0.9.1.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.1.qualifier">
<category name="developer"/>
</feature>
- <feature url="features/com.android.ide.eclipse.ddms_0.9.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.0.qualifier">
+ <feature url="features/com.android.ide.eclipse.ddms_0.9.1.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.1.qualifier">
<category name="developer"/>
</feature>
- <feature url="features/com.android.ide.eclipse.tests_0.9.0.qualifier.jar" id="com.android.ide.eclipse.tests" version="0.9.0.qualifier">
+ <feature url="features/com.android.ide.eclipse.tests_0.9.1.qualifier.jar" id="com.android.ide.eclipse.tests" version="0.9.1.qualifier">
<category name="test"/>
</feature>
<category-def name="developer" label="Application Developer Tools">
diff --git a/eclipse/source_package_readme.txt b/eclipse/source_package_readme.txt
deleted file mode 100644
index 456bae7..0000000
--- a/eclipse/source_package_readme.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-HOW TO PACKAGE THE SOURCE OF THE PLUGINS FOR RELEASE.
-
-Note: this is only useful before we move to the public source repository, after which this will
-be obsolete.
-
-The source archive must contains:
-1/ Source of the EPL plugins that are released.
-2/ Any closed source dependencies that were created by Google.
-3/ The readme file explaining how to build the plugins.
-
-
-1/ PLUGIN SOURCE
-
-The Plugins that are currently released and that are EPL are:
-- Android Developer Tools => com.android.ide.eclipse.adt
-- Common => com.android.ide.eclipse.common
-- Android Editors => com.android.ide.eclipse.editors
-
-All three plugins are located in
- device/tools/eclipse/plugins/
-
-Before packing them up, it is important to:
-- remove the bin directory if it exists
-- remove any symlinks to jar files from the top level folder of each plugin
-
-2/ PLUGIN DEPENDENCIES
-
-The plugin dependencies are jar files embedded in some of the plugins. Some of those jar files
-are android libraries for which the source code is not yet being released (They will be released
-under the APL).
-
-Those libraries are not part of the SDK, and need to be taken from a engineering build.
-They will be located in
- device/out/host/<platform>/framework/
-
-The libraries to copy are:
- - layoutlib_api.jar
- - layoutlib_utils.jar
- - ninepatch.jar
-
-They should be placed in a "libs" folder in the source archive.
-
-3/ README
-
-In the source archive, at the top level, needs to be present a file explaining how to compile
-the plugins.
-
-This file is located at:
- device/tools/eclipse/plugins/README.txt \ No newline at end of file
diff --git a/jarutils/.gitignore b/jarutils/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/jarutils/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/layoutlib_utils/.gitignore b/layoutlib_utils/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/layoutlib_utils/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/sdkmanager/.gitignore b/sdkmanager/.gitignore
new file mode 100644
index 0000000..48f206a
--- /dev/null
+++ b/sdkmanager/.gitignore
@@ -0,0 +1,4 @@
+app/bin
+libs/sdklib/bin
+libs/sdkuilib/bin
+
diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java
index fc9a2be..1e6b585 100644
--- a/sdkmanager/app/src/com/android/sdkmanager/Main.java
+++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -35,7 +35,6 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.regex.Pattern;
/**
* Main class for the 'android' application.
@@ -50,11 +49,6 @@ class Main {
private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
-
- /** Regex used to validate characters that compose an AVD name. */
- private final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+");
- /** List of valid characters for an AVD name. Used for display purposes. */
- private final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -";
/** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
@@ -454,16 +448,18 @@ class Main {
// display some extra values.
Map<String, String> properties = info.getProperties();
- String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
- if (skin != null) {
- mSdkLog.printf(" Skin: %s\n", skin);
- }
- String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
- if (sdcard == null) {
- sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
- }
- if (sdcard != null) {
- mSdkLog.printf(" Sdcard: %s\n", sdcard);
+ if (properties != null) {
+ String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
+ if (skin != null) {
+ mSdkLog.printf(" Skin: %s\n", skin);
+ }
+ String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
+ if (sdcard == null) {
+ sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
+ }
+ if (sdcard != null) {
+ mSdkLog.printf(" Sdcard: %s\n", sdcard);
+ }
}
}
@@ -508,22 +504,21 @@ class Main {
}
try {
- boolean removePrevious = false;
+ boolean removePrevious = mSdkCommandLine.getFlagForce();
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
String avdName = mSdkCommandLine.getParamName();
- if (!RE_AVD_NAME.matcher(avdName).matches()) {
+ if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
errorAndExit(
"AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
- avdName, CHARS_AVD_NAME);
+ avdName, AvdManager.CHARS_AVD_NAME);
return;
}
AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
if (info != null) {
- if (mSdkCommandLine.getFlagForce()) {
- removePrevious = true;
+ if (removePrevious) {
mSdkLog.warning(
"Android Virtual Device '%s' already exists and will be replaced.",
avdName);
@@ -592,18 +587,6 @@ class Main {
hardwareConfig,
removePrevious);
- if (newAvdInfo != null &&
- oldAvdInfo != null &&
- !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
- mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
- // Remove the old data directory
- File dir = new File(oldAvdInfo.getPath());
- avdManager.recursiveDelete(dir);
- dir.delete();
- // Remove old AVD info from manager
- avdManager.removeAvd(oldAvdInfo);
- }
-
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
index 2336f47..b9da961 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
@@ -192,6 +192,15 @@ final class AddOnTarget implements IAndroidTarget {
return mLibraries;
}
+ /**
+ * Returns the list of libraries of the underlying platform.
+ *
+ * {@inheritDoc}
+ */
+ public String[] getPlatformLibraries() {
+ return mBasePlatform.getPlatformLibraries();
+ }
+
public boolean isCompatibleBaseFor(IAndroidTarget target) {
// basic test
if (target == this) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
index 896a83c..3042950 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
@@ -149,7 +149,14 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
* @return an array of optional libraries or <code>null</code> if there is none.
*/
IOptionalLibrary[] getOptionalLibraries();
-
+
+ /**
+ * Returns the list of libraries available for a given platform.
+ *
+ * @return an array of libraries provided by the platform or <code>null</code> if there is none.
+ */
+ String[] getPlatformLibraries();
+
/**
* Returns whether the given target is compatible with the receiver.
* <p/>A target is considered compatible if applications developed for the receiver can run on
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index d4e40b1..326d722 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -149,15 +149,25 @@ final class PlatformTarget implements IAndroidTarget {
return "HVGA";
}
- /*
- * Always returns null, as a standard platforms have no optional libraries.
+ /**
+ * Always returns null, as a standard platform ha no optional libraries.
*
- * (non-Javadoc)
+ * {@inheritDoc}
* @see com.android.sdklib.IAndroidTarget#getOptionalLibraries()
*/
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
+
+ /**
+ * Currently always return a fixed list with "android.test.runner" in it.
+ * <p/>
+ * TODO change the fixed library list to be build-dependent later.
+ * {@inheritDoc}
+ */
+ public String[] getPlatformLibraries() {
+ return new String[] { SdkConstants.ANDROID_TEST_RUNNER_LIB };
+ }
public boolean isCompatibleBaseFor(IAndroidTarget target) {
// basic test
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 9eb6ade..b53bd5e 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -167,6 +167,9 @@ public final class SdkConstants {
/** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
+ /** The name of the uses-library that provides "android.test.runner" */
+ public final static String ANDROID_TEST_RUNNER_LIB = "android.test.runner";
+
/* Folder path relative to the SDK root */
/** Path of the documentation directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
index 1936f8a..041e8b5 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -125,7 +125,13 @@ public final class AvdManager {
/**
* Pattern for matching SD Card sizes, e.g. "4K" or "16M".
*/
- private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
+ public final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]");
+
+ /** Regex used to validate characters that compose an AVD name. */
+ public final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+");
+
+ /** List of valid characters for an AVD name. Used for display purposes. */
+ public final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -";
/** An immutable structure describing an Android Virtual Device. */
public static final class AvdInfo {
@@ -185,7 +191,7 @@ public final class AvdManager {
* @param targetHash the target hash
* @param target The target. Can be null, if the target was not resolved.
* @param properties The property map. Can be null.
- * @param error The error describing why this AVD is invalid. Cannot be null.
+ * @param status The {@link AvdStatus} of this AVD. Cannot be null.
*/
public AvdInfo(String name, String path, String targetHash, IAndroidTarget target,
Map<String, String> properties, AvdStatus status) {
@@ -193,7 +199,7 @@ public final class AvdManager {
mPath = path;
mTargetHash = targetHash;
mTarget = target;
- mProperties = Collections.unmodifiableMap(properties);
+ mProperties = properties == null ? null : Collections.unmodifiableMap(properties);
mStatus = status;
}
@@ -214,7 +220,7 @@ public final class AvdManager {
return mTargetHash;
}
- /** Returns the target of the AVD, or <code>null</code> if it has not been resolved */
+ /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */
public IAndroidTarget getTarget() {
return mTarget;
}
@@ -257,7 +263,7 @@ public final class AvdManager {
}
/**
- * Returns a map of properties for the AVD.
+ * Returns an unmodifiable map of properties for the AVD. This can be null.
*/
public Map<String, String> getProperties() {
return mProperties;
@@ -301,9 +307,18 @@ public final class AvdManager {
private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
private AvdInfo[] mValidAvdList;
private AvdInfo[] mBrokenAvdList;
- private ISdkLog mSdkLog;
private final SdkManager mSdk;
+ /**
+ * TODO remove this field. Each caller should give its own logger to the methods
+ * here instead of relying on a global logger. Otherwise that means that each
+ * caller needs to re-instantiate this just to change the logger, which is plain
+ * ugly.
+ *
+ * We'll revisit this in the final AVD Manager UI for donut.
+ */
+ private ISdkLog mSdkLog;
+
public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
mSdk = sdk;
mSdkLog = sdkLog;
@@ -311,6 +326,20 @@ public final class AvdManager {
}
/**
+ * Changes the current {@link ISdkLog}.
+ *
+ * This method should not exist. See comments for {@link #mSdkLog}.
+ *
+ * @return The {@link ISdkLog} that was currently being used.
+ * @see #mSdkLog
+ */
+ public ISdkLog setSdkLog(ISdkLog sdkLog) {
+ ISdkLog oldLogger = mSdkLog;
+ mSdkLog = sdkLog;
+ return oldLogger;
+ }
+
+ /**
* Returns all the existing AVDs.
* @return a newly allocated array containing all the AVDs.
*/
@@ -405,14 +434,17 @@ public final class AvdManager {
/**
* Creates a new AVD. It is expected that there is no existing AVD with this name already.
+ *
* @param avdFolder the data folder for the AVD. It will be created as needed.
* @param name the name of the AVD
* @param target the target of the AVD
* @param skinName the name of the skin. Can be null. Must have been verified by caller.
* @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
- * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
- * @param hardwareConfig the hardware setup for the AVD
+ * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
+ * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
* @param removePrevious If true remove any previous files.
+ * @return The new {@link AvdInfo} in case of success (which has just been added to the
+ * internal list) or null in case of failure.
*/
public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
String skinName, String sdcard, Map<String,String> hardwareConfig,
@@ -482,7 +514,7 @@ public final class AvdManager {
}
// Now the skin.
- if (skinName == null) {
+ if (skinName == null || skinName.length() == 0) {
skinName = target.getDefaultSkin();
}
@@ -505,7 +537,7 @@ public final class AvdManager {
values.put(AVD_INI_SKIN_NAME, skinName);
}
- if (sdcard != null) {
+ if (sdcard != null && sdcard.length() > 0) {
File sdcardFile = new File(sdcard);
if (sdcardFile.isFile()) {
// sdcard value is an external sdcard, so we put its path into the config.ini
@@ -571,15 +603,33 @@ public final class AvdManager {
}
// create the AvdInfo object, and add it to the list
- AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target.hashString(),
+ AvdInfo newAvdInfo = new AvdInfo(name,
+ avdFolder.getAbsolutePath(),
+ target.hashString(),
target, values);
+
+ AvdInfo oldAvdInfo = getAvd(name, false /*validAvdOnly*/);
synchronized (mAllAvdList) {
- mAllAvdList.add(avdInfo);
+ if (oldAvdInfo != null && removePrevious) {
+ mAllAvdList.remove(oldAvdInfo);
+ }
+ mAllAvdList.add(newAvdInfo);
mValidAvdList = mBrokenAvdList = null;
}
-
- return avdInfo;
+
+ if (removePrevious &&
+ newAvdInfo != null &&
+ oldAvdInfo != null &&
+ !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
+ mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
+ // Remove the old data directory
+ File dir = new File(oldAvdInfo.getPath());
+ recursiveDelete(dir);
+ dir.delete();
+ }
+
+ return newAvdInfo;
} catch (AndroidLocationException e) {
if (mSdkLog != null) {
mSdkLog.error(e, null);
@@ -724,8 +774,9 @@ public final class AvdManager {
* these operations fail.
*
* @param avdInfo the information on the AVD to delete
+ * @return True if the AVD was deleted with no error.
*/
- public void deleteAvd(AvdInfo avdInfo, ISdkLog log) {
+ public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) {
try {
boolean error = false;
@@ -758,6 +809,7 @@ public final class AvdManager {
avdInfo.getName());
} else {
log.printf("AVD '%1$s' deleted.\n", avdInfo.getName());
+ return true;
}
} catch (AndroidLocationException e) {
@@ -765,6 +817,7 @@ public final class AvdManager {
} catch (IOException e) {
log.error(e, null);
}
+ return false;
}
/**
@@ -905,10 +958,8 @@ public final class AvdManager {
* Parses an AVD .ini file to create an {@link AvdInfo}.
*
* @param path The path to the AVD .ini file
- * @param acceptError When false, an AVD that fails to load will be discarded and null will be
- * returned. When true, such an AVD will be returned with an error description.
- * @return A new {@link AvdInfo} or null if the file is not valid or null if the AVD is not
- * valid and acceptError is false.
+ * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
+ * valid or not.
*/
private AvdInfo parseAvdInfo(File path) {
Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
@@ -1012,7 +1063,6 @@ public final class AvdManager {
* @param toolLocation The path to the mksdcard tool.
* @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
* @param location The path of the new sdcard image file to generate.
- * @param log The logger object, to report errors.
* @return True if the sdcard could be created.
*/
private boolean createSdCard(String toolLocation, String size, String location) {
@@ -1173,7 +1223,9 @@ public final class AvdManager {
// create a new map
Map<String, String> properties = new HashMap<String, String>();
- properties.putAll(oldProperties);
+ if (oldProperties != null) {
+ properties.putAll(oldProperties);
+ }
AvdStatus status;
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
index 20c211b..05a31e6 100644
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
@@ -22,12 +22,14 @@ import com.android.sdklib.avd.AvdManager.AvdInfo;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
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.Event;
import org.eclipse.swt.widgets.Label;
@@ -40,9 +42,9 @@ import org.eclipse.swt.widgets.TableItem;
/**
* The AVD selector is a table that is added to the given parent composite.
* <p/>
- * To use, create it using {@link #AvdSelector(Composite, AvdInfo[])} then
+ * To use, create it using {@link #AvdSelector(Composite, SelectionMode, IExtraAction)} then
* call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)}
- * and finally use {@link #getFirstSelected()} to retrieve the selection.
+ * and finally use {@link #getSelected()} to retrieve the selection.
*/
public final class AvdSelector {
@@ -51,6 +53,53 @@ public final class AvdSelector {
private Table mTable;
private Label mDescription;
+ private static int NUM_COL = 2;
+ private final SelectionMode mSelectionMode;
+ private final IExtraAction mExtraAction;
+ private Button mExtraActionButton;
+
+ /** The selection mode, either {@link #SELECT} or {@link #CHECK} */
+ public enum SelectionMode {
+ /**
+ * In the "check" selection mode, checkboxes are displayed on each line
+ * and {@link AvdSelector#getSelected()} returns the line that is checked
+ * even if it is not the currently selected line. Only one line can
+ * be checked at once.
+ */
+ CHECK,
+ /**
+ * In the "select" selection mode, there are no checkboxes and
+ * {@link AvdSelector#getSelected()} returns the line currently selected.
+ * Only one line can be selected at once.
+ */
+ SELECT
+ }
+
+ /**
+ * Defines an "extra action" button that can be shown under the AVD Selector.
+ */
+ public interface IExtraAction {
+ /**
+ * Label of the button that will be created.
+ * This is invoked once when the button is created and cannot be changed later.
+ */
+ public String label();
+
+ /**
+ * This is invoked just after the selection has changed to update the "enabled"
+ * state of the action. Implementation should use {@link AvdSelector#getSelected()}.
+ */
+ public boolean isEnabled();
+
+ /**
+ * Run the action, invoked when the button is clicked.
+ *
+ * The caller's action is responsible for reloading the AVD list
+ * using {@link AvdSelector#setAvds(AvdInfo[], IAndroidTarget)}.
+ */
+ public void run();
+ }
+
/**
* Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
* by a {@link IAndroidTarget}.
@@ -59,30 +108,51 @@ public final class AvdSelector {
*
* @param parent The parent composite where the selector will be added.
* @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+ * It can be null.
+ * @param filter When non-null, will display only the AVDs matching this target.
+ * @param extraAction When non-null, displays an extra action button.
+ * @param selectionMode One of {@link SelectionMode#SELECT} or {@link SelectionMode#CHECK}
*/
- public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter) {
+ public AvdSelector(Composite parent,
+ AvdInfo[] avds,
+ IAndroidTarget filter,
+ IExtraAction extraAction,
+ SelectionMode selectionMode) {
mAvds = avds;
+ mExtraAction = extraAction;
+ mSelectionMode = selectionMode;
- // Layout has 1 column
+ // Layout has 2 columns
Composite group = new Composite(parent, SWT.NONE);
- group.setLayout(new GridLayout());
+ group.setLayout(new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
-
- mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
+
+ int style = SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER;
+ if (selectionMode == SelectionMode.CHECK) {
+ style |= SWT.CHECK;
+ }
+ mTable = new Table(group, style);
mTable.setHeaderVisible(true);
mTable.setLinesVisible(false);
-
- GridData data = new GridData();
- data.grabExcessVerticalSpace = true;
- data.grabExcessHorizontalSpace = true;
- data.horizontalAlignment = GridData.FILL;
- data.verticalAlignment = GridData.FILL;
- mTable.setLayoutData(data);
+ setTableHeightHint(0);
mDescription = new Label(group, SWT.WRAP);
mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ if (extraAction != null) {
+ mExtraActionButton = new Button(group, SWT.PUSH);
+ mExtraActionButton.setText(extraAction.label());
+ mExtraActionButton.setEnabled(extraAction.isEnabled());
+ mExtraActionButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ mExtraAction.run();
+ }
+ });
+ }
+
// create the table columns
final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
column0.setText("AVD Name");
@@ -98,23 +168,49 @@ public final class AvdSelector {
fillTable(mTable, filter);
setupTooltip(mTable);
}
-
+
/**
* Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
*
* @param parent The parent composite where the selector will be added.
* @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+ * It can be null.
+ * @param extraAction When non-null, displays an extra action button.
+ * @param selectionMode One of {@link SelectionMode#SELECT} or {@link SelectionMode#CHECK}
*/
- public AvdSelector(Composite parent, AvdInfo[] avds) {
- this(parent, avds, null /* filter */);
+ public AvdSelector(Composite parent,
+ AvdInfo[] avds,
+ IExtraAction extraAction,
+ SelectionMode selectionMode) {
+ this(parent, avds, null /* filter */, extraAction, selectionMode);
}
-
+ /**
+ * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
+ *
+ * @param parent The parent composite where the selector will be added.
+ * @param extraAction When non-null, displays an extra action button.
+ * @param selectionMode One of {@link SelectionMode#SELECT} or {@link SelectionMode#CHECK}
+ */
+ public AvdSelector(Composite parent,
+ SelectionMode selectionMode,
+ IExtraAction extraAction) {
+ this(parent, null /*avds*/, null /* filter */, extraAction, selectionMode);
+ }
+
+ /**
+ * Sets the table grid layout data.
+ *
+ * @param heightHint If > 0, the height hint is set to the requested value.
+ */
public void setTableHeightHint(int heightHint) {
GridData data = new GridData();
- data.heightHint = heightHint;
+ if (heightHint > 0) {
+ data.heightHint = heightHint;
+ }
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
+ data.horizontalSpan = NUM_COL;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
mTable.setLayoutData(data);
@@ -122,15 +218,24 @@ public final class AvdSelector {
/**
* Sets a new set of AVD, with an optional filter.
- * <p/>This must be called from the UI thread.
+ * Tries to keep the selection.
+ * <p/>
+ * This must be called from the UI thread.
+ *
*
* @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+ * It can be null.
* @param filter An IAndroidTarget. If non-null, only AVD whose target are compatible with the
* filter target will displayed an available for selection.
*/
public void setAvds(AvdInfo[] avds, IAndroidTarget filter) {
+
+ AvdInfo selected = getSelected();
+
mAvds = avds;
fillTable(mTable, filter);
+
+ setSelection(selected);
}
/**
@@ -150,7 +255,7 @@ public final class AvdSelector {
* The event's item contains a {@link TableItem}.
* The {@link TableItem#getData()} contains an {@link IAndroidTarget}.
* <p/>
- * It is recommended that the caller uses the {@link #getFirstSelected()} method instead.
+ * It is recommended that the caller uses the {@link #getSelected()} method instead.
*
* @param selectionListener The new listener or null to remove it.
*/
@@ -164,22 +269,39 @@ public final class AvdSelector {
* If the selection is actually changed, this will invoke the selection listener
* (if any) with a null event.
*
- * @param target the target to be selection
+ * @param target the target to be selected. Use null to deselect everything.
* @return true if the target could be selected, false otherwise.
*/
public boolean setSelection(AvdInfo target) {
boolean found = false;
boolean modified = false;
+
+ int selIndex = mTable.getSelectionIndex();
+ int index = 0;
for (TableItem i : mTable.getItems()) {
- if ((AvdInfo) i.getData() == target) {
- found = true;
- if (!i.getChecked()) {
+ if (mSelectionMode == SelectionMode.SELECT) {
+ if ((AvdInfo) i.getData() == target) {
+ found = true;
+ if (index != selIndex) {
+ mTable.setSelection(index);
+ modified = true;
+ }
+ break;
+ }
+
+ index++;
+
+ } else if (mSelectionMode == SelectionMode.CHECK){
+ if ((AvdInfo) i.getData() == target) {
+ found = true;
+ if (!i.getChecked()) {
+ modified = true;
+ i.setChecked(true);
+ }
+ } else if (i.getChecked()) {
modified = true;
- i.setChecked(true);
+ i.setChecked(false);
}
- } else if (i.getChecked()) {
- modified = true;
- i.setChecked(false);
}
}
@@ -187,19 +309,30 @@ public final class AvdSelector {
mSelectionListener.widgetSelected(null);
}
+ if (mExtraAction != null && mExtraActionButton != null) {
+ mExtraActionButton.setEnabled(mExtraAction.isEnabled());
+ }
+
return found;
}
/**
- * Returns the first selected item.
- * This is useful when the table is in single-selection mode.
+ * Returns the currently selected item.
*
- * @return The first selected item or null.
+ * @return The currently selected item or null.
*/
- public AvdInfo getFirstSelected() {
- for (TableItem i : mTable.getItems()) {
- if (i.getChecked()) {
- return (AvdInfo) i.getData();
+ public AvdInfo getSelected() {
+ if (mSelectionMode == SelectionMode.SELECT) {
+ int selIndex = mTable.getSelectionIndex();
+ if (selIndex >= 0) {
+ return (AvdInfo) mTable.getItem(selIndex).getData();
+ }
+
+ } else if (mSelectionMode == SelectionMode.CHECK) {
+ for (TableItem i : mTable.getItems()) {
+ if (i.getChecked()) {
+ return (AvdInfo) i.getData();
+ }
}
}
return null;
@@ -264,6 +397,10 @@ public final class AvdSelector {
if (mSelectionListener != null) {
mSelectionListener.widgetSelected(e);
}
+
+ if (mExtraAction != null && mExtraActionButton != null) {
+ mExtraActionButton.setEnabled(mExtraAction.isEnabled());
+ }
}
/**
@@ -277,7 +414,9 @@ public final class AvdSelector {
public void widgetDefaultSelected(SelectionEvent e) {
if (e.item instanceof TableItem) {
TableItem i = (TableItem) e.item;
- i.setChecked(true);
+ if (mSelectionMode == SelectionMode.CHECK) {
+ i.setChecked(true);
+ }
enforceSingleSelection(i);
updateDescription(i);
}
@@ -285,6 +424,10 @@ public final class AvdSelector {
if (mSelectionListener != null) {
mSelectionListener.widgetDefaultSelected(e);
}
+
+ if (mExtraAction != null && mExtraActionButton != null) {
+ mExtraActionButton.setEnabled(mExtraAction.isEnabled());
+ }
}
/**
@@ -292,11 +435,16 @@ public final class AvdSelector {
* This makes the chekboxes act as radio buttons.
*/
private void enforceSingleSelection(TableItem item) {
- if (item.getChecked()) {
- Table parentTable = item.getParent();
- for (TableItem i2 : parentTable.getItems()) {
- if (i2 != item && i2.getChecked()) {
- i2.setChecked(false);
+ if (mSelectionMode == SelectionMode.SELECT) {
+ // pass
+
+ } else if (mSelectionMode == SelectionMode.CHECK) {
+ if (item.getChecked()) {
+ Table parentTable = item.getParent();
+ for (TableItem i2 : parentTable.getItems()) {
+ if (i2 != item && i2.getChecked()) {
+ i2.setChecked(false);
+ }
}
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
index cd789f6..b23c865 100644
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
@@ -28,6 +28,7 @@ import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
@@ -397,4 +398,21 @@ public class SdkTargetSelector {
}
}
}
+
+ /** Enables or disables the controls. */
+ public void setEnabled(boolean enabled) {
+ if (mInnerGroup != null && mTable != null && !mTable.isDisposed()) {
+ enableControl(mInnerGroup, enabled);
+ }
+ }
+
+ /** Enables or disables controls; recursive for composite controls. */
+ private void enableControl(Control c, boolean enabled) {
+ c.setEnabled(enabled);
+ if (c instanceof Composite)
+ for (Control c2 : ((Composite) c).getChildren()) {
+ enableControl(c2, enabled);
+ }
+ }
+
}
diff --git a/sdkstats/.gitignore b/sdkstats/.gitignore
new file mode 100644
index 0000000..fe99505
--- /dev/null
+++ b/sdkstats/.gitignore
@@ -0,0 +1,2 @@
+bin
+
diff --git a/sdkstats/src/com/android/sdkstats/SdkStatsService.java b/sdkstats/src/com/android/sdkstats/SdkStatsService.java
index 0b3d41b..688474e 100644
--- a/sdkstats/src/com/android/sdkstats/SdkStatsService.java
+++ b/sdkstats/src/com/android/sdkstats/SdkStatsService.java
@@ -110,8 +110,10 @@ public class SdkStatsService {
*
* @param app name to report in the ping
* @param version to report in the ping
+ * @param display an optional {@link Display} object to use, or null, if a new one should be
+ * created.
*/
- public static void ping(final String app, final String version) {
+ public static void ping(final String app, final String version, final Display display) {
// Validate the application and version input.
final String normalVersion = normalizeVersion(app, version);
@@ -123,7 +125,7 @@ public class SdkStatsService {
prefs.setValue(PING_ID, new Random().nextLong());
// Also give them a chance to opt out.
- prefs.setValue(PING_OPT_IN, getUserPermission());
+ prefs.setValue(PING_OPT_IN, getUserPermission(display));
try {
prefs.save();
}
@@ -273,77 +275,90 @@ public class SdkStatsService {
* Prompt the user for whether they want to opt out of reporting.
* @return whether the user allows reporting (they do not opt out).
*/
- private static boolean getUserPermission() {
- // Use dialog trim for the shell, but without a close button.
- final Display display = new Display();
- final Shell shell = new Shell(display, SWT.TITLE | SWT.BORDER);
- shell.setText(WINDOW_TITLE_TEXT);
- shell.setLayout(new GridLayout(1, false)); // 1 column
-
- // Take the default font and scale it up for the title.
- final Label title = new Label(shell, SWT.CENTER | SWT.WRAP);
- final FontData[] fontdata = title.getFont().getFontData();
- for (int i = 0; i < fontdata.length; i++) {
- fontdata[i].setHeight(fontdata[i].getHeight() * 4 / 3);
+ private static boolean getUserPermission(Display display) {
+ // Whether the user gave permission (size-1 array for writing to).
+ // Initialize to false, set when the user clicks the button.
+ final boolean[] permission = new boolean[] { false };
+
+ boolean dispose = false;
+ if (display == null) {
+ display = new Display();
+ dispose = true;
}
- title.setFont(new Font(display, fontdata));
- title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- title.setText(HEADER_TEXT);
-
- final Label notice = new Label(shell, SWT.WRAP);
- notice.setFont(title.getFont());
- notice.setForeground(new Color(display, 255, 0, 0));
- notice.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- notice.setText(NOTICE_TEXT);
-
- final Link text = new Link(shell, SWT.WRAP);
- text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- text.setText(BODY_TEXT);
- text.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- openUrl(event.text);
- }
- });
- final Button checkbox = new Button(shell, SWT.CHECK);
- checkbox.setSelection(true); // Opt-in by default.
- checkbox.setText(CHECKBOX_TEXT);
+ final Display currentDisplay = display;
+ final boolean disposeDisplay = dispose;
- final Link footer = new Link(shell, SWT.WRAP);
- footer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- footer.setText(FOOTER_TEXT);
+ display.syncExec(new Runnable() {
+ public void run() {
+ final Shell shell = new Shell(currentDisplay, SWT.TITLE | SWT.BORDER);
+ shell.setText(WINDOW_TITLE_TEXT);
+ shell.setLayout(new GridLayout(1, false)); // 1 column
- // Whether the user gave permission (size-1 array for writing to).
- // Initialize to false, set when the user clicks the button.
- final boolean[] permission = new boolean[] { false };
+ // Take the default font and scale it up for the title.
+ final Label title = new Label(shell, SWT.CENTER | SWT.WRAP);
+ final FontData[] fontdata = title.getFont().getFontData();
+ for (int i = 0; i < fontdata.length; i++) {
+ fontdata[i].setHeight(fontdata[i].getHeight() * 4 / 3);
+ }
+ title.setFont(new Font(currentDisplay, fontdata));
+ title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ title.setText(HEADER_TEXT);
+
+ final Label notice = new Label(shell, SWT.WRAP);
+ notice.setFont(title.getFont());
+ notice.setForeground(new Color(currentDisplay, 255, 0, 0));
+ notice.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ notice.setText(NOTICE_TEXT);
+
+ final Link text = new Link(shell, SWT.WRAP);
+ text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ text.setText(BODY_TEXT);
+ text.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ openUrl(event.text);
+ }
+ });
+
+ final Button checkbox = new Button(shell, SWT.CHECK);
+ checkbox.setSelection(true); // Opt-in by default.
+ checkbox.setText(CHECKBOX_TEXT);
+
+ final Link footer = new Link(shell, SWT.WRAP);
+ footer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ footer.setText(FOOTER_TEXT);
+
+ final Button button = new Button(shell, SWT.PUSH);
+ button.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
+ button.setText(BUTTON_TEXT);
+ button.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ permission[0] = checkbox.getSelection();
+ shell.close();
+ }
+ });
+
+ // Size the window to a fixed width, as high as necessary,
+ // centered.
+ final Point size = shell.computeSize(450, SWT.DEFAULT, true);
+ final Rectangle screen = currentDisplay.getClientArea();
+ shell.setBounds(screen.x + screen.width / 2 - size.x / 2, screen.y + screen.height
+ / 2 - size.y / 2, size.x, size.y);
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!currentDisplay.readAndDispatch())
+ currentDisplay.sleep();
+ }
- final Button button = new Button(shell, SWT.PUSH);
- button.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
- button.setText(BUTTON_TEXT);
- button.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- permission[0] = checkbox.getSelection();
- shell.close();
+ if (disposeDisplay) {
+ currentDisplay.dispose();
+ }
}
});
- // Size the window to a fixed width, as high as necessary, centered.
- final Point size = shell.computeSize(450, SWT.DEFAULT, true);
- final Rectangle screen = display.getClientArea();
- shell.setBounds(
- screen.x + screen.width / 2 - size.x / 2,
- screen.y + screen.height / 2 - size.y / 2,
- size.x, size.y);
-
- shell.open();
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
-
- display.dispose(); // Otherwise ddms' own Display can't be created
return permission[0];
}
diff --git a/traceview/.gitignore b/traceview/.gitignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/traceview/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/traceview/src/com/android/traceview/MainWindow.java b/traceview/src/com/android/traceview/MainWindow.java
index b0c24e9..5800f81 100644
--- a/traceview/src/com/android/traceview/MainWindow.java
+++ b/traceview/src/com/android/traceview/MainWindow.java
@@ -133,7 +133,7 @@ public class MainWindow extends ApplicationWindow {
boolean regression = false;
// ping the usage server
- SdkStatsService.ping(PING_NAME, PING_VERSION);
+ SdkStatsService.ping(PING_NAME, PING_VERSION, null);
// Process command line arguments
int argc = 0;