diff options
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 Binary files differnew file mode 100755 index 0000000..7dbbbb6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/avd_manager.png 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; |