diff options
105 files changed, 3744 insertions, 878 deletions
diff --git a/anttasks/.classpath b/anttasks/.classpath new file mode 100644 index 0000000..08ced21 --- /dev/null +++ b/anttasks/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> + <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/ant/ant.jar"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/anttasks/.project b/anttasks/.project new file mode 100644 index 0000000..aed1b61 --- /dev/null +++ b/anttasks/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ant-tasks</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/anttasks/Android.mk b/anttasks/Android.mk new file mode 100644 index 0000000..15ee903 --- /dev/null +++ b/anttasks/Android.mk @@ -0,0 +1,17 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# +ANTTASKS_LOCAL_DIR := $(call my-dir) +include $(ANTTASKS_LOCAL_DIR)/src/Android.mk diff --git a/anttasks/src/Android.mk b/anttasks/src/Android.mk new file mode 100644 index 0000000..dbaf2bc --- /dev/null +++ b/anttasks/src/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 +# +# 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. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_JAVA_LIBRARIES := \ + sdklib \ + ant + +LOCAL_MODULE := anttasks + +include $(BUILD_HOST_JAVA_LIBRARY) + diff --git a/anttasks/src/com/android/ant/AndroidInitTask.java b/anttasks/src/com/android/ant/AndroidInitTask.java new file mode 100644 index 0000000..84c1d27 --- /dev/null +++ b/anttasks/src/com/android/ant/AndroidInitTask.java @@ -0,0 +1,150 @@ +/* + * 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.ant; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkManager; +import com.android.sdklib.project.ProjectProperties; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ImportTask; + +import java.io.File; +import java.util.ArrayList; + +/** + * Import Target Ant task. This task accomplishes: + * <ul> + * <li>Gets the project target hash string from {@link ProjectProperties#PROPERTY_TARGET}, + * and resolves it.</li> + * <li>Sets up ant properties so that the rest of the Ant scripts finds: + * <ul> + * <li>Path to the underlying platform to access the build rules ('android-platform')<li> + * </ul> + * </li> + * </ul> + * + * This is used in build.xml/template. + * + */ +public class AndroidInitTask extends ImportTask { + private final static String ANDROID_RULES = "android_rules.xml"; + + private final static String PROPERTY_ANDROID_JAR = "android-jar"; + private final static String PROPERTY_ANDROID_AIDL = "android-aidl"; + + @Override + public void execute() throws BuildException { + Project antProject = getProject(); + + // get the SDK location + String sdkLocation = antProject.getProperty(ProjectProperties.PROPERTY_SDK); + + // check if it's valid and exists + if (sdkLocation == null || sdkLocation.length() == 0) { + throw new BuildException("SDK Location is not set."); + } + + File sdk = new File(sdkLocation); + if (sdk.isDirectory() == false) { + throw new BuildException(String.format("SDK Location '%s' is not valid.", sdkLocation)); + } + + // get the target property value + String targetHashString = antProject.getProperty(ProjectProperties.PROPERTY_TARGET); + if (targetHashString == null) { + throw new BuildException("Android Target is not set."); + } + + // load up the sdk targets. + final ArrayList<String> messages = new ArrayList<String>(); + SdkManager manager = SdkManager.createManager(sdkLocation, new ISdkLog() { + public void error(Throwable t, String errorFormat, Object... args) { + if (errorFormat != null) { + messages.add(String.format("Error: " + errorFormat, args)); + } + if (t != null) { + messages.add("Error: " + t.getMessage()); + } + } + + public void printf(String msgFormat, Object... args) { + messages.add(String.format(msgFormat, args)); + } + + public void warning(String warningFormat, Object... args) { + messages.add(String.format("Warning: " + warningFormat, args)); + } + }); + + if (manager == null) { + // since we failed to parse the SDK, lets display the parsing output. + for (String msg : messages) { + System.out.println(msg); + } + throw new BuildException("Failed to parse SDK content."); + } + + // resolve it + IAndroidTarget androidTarget = manager.getTargetFromHashString(targetHashString); + + if (androidTarget == null) { + throw new BuildException(String.format( + "Unable to resolve target '%s'", targetHashString)); + } + + // display it + System.out.println("Project Target: " + androidTarget.getName()); + if (androidTarget.isPlatform() == false) { + System.out.println("Vendor: " + androidTarget.getVendor()); + } + System.out.println("Platform Version: " + androidTarget.getApiVersionName()); + System.out.println("API level: " + androidTarget.getApiVersionNumber()); + + // sets up the properties. + String androidJar = androidTarget.getPath(IAndroidTarget.ANDROID_JAR); + String androidAidl = androidTarget.getPath(IAndroidTarget.ANDROID_AIDL); + + antProject.setProperty(PROPERTY_ANDROID_JAR, androidJar); + antProject.setProperty(PROPERTY_ANDROID_AIDL, androidAidl); + + // find the file to import, and import it. + String templateFolder = androidTarget.getPath(IAndroidTarget.TEMPLATES); + + // make sure the file exists. + File templates = new File(templateFolder); + if (templates.isDirectory() == false) { + throw new BuildException(String.format("Template directory '%s' is missing.", + templateFolder)); + } + + // now check the rules file exists. + File rules = new File(templateFolder, ANDROID_RULES); + if (rules.isFile() == false) { + throw new BuildException(String.format("Build rules file '%s' is missing.", + templateFolder)); + } + + // set the file location to import + setFile(rules.getAbsolutePath()); + + // and import it + super.execute(); + } +} diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java index a031a1b..8291f59 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java @@ -69,6 +69,9 @@ public final class Device implements IDevice { /** Serial number of the device */ String serialNumber = null; + /** Name of the vm */ + String mVmName = null; + /** State of the device. */ DeviceState state = null; @@ -91,6 +94,11 @@ public final class Device implements IDevice { return serialNumber; } + public String getVmName() { + return mVmName; + } + + /* * (non-Javadoc) * @see com.android.ddmlib.IDevice#getState() diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java index 4b81116..8547ac1 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java @@ -349,7 +349,7 @@ final class DeviceMonitor { } if (device.getPropertyCount() == 0) { - queryForBuild(device); + queryNewDeviceForInfo(device); } } } @@ -387,7 +387,7 @@ final class DeviceMonitor { // look for their build info. if (newDevice.isOnline()) { - queryForBuild(newDevice); + queryNewDeviceForInfo(newDevice); } } } @@ -413,11 +413,20 @@ final class DeviceMonitor { * Queries a device for its build info. * @param device the device to query. */ - private void queryForBuild(Device device) { + private void queryNewDeviceForInfo(Device device) { // TODO: do this in a separate thread. try { + // first get the list of properties. device.executeShellCommand(GetPropReceiver.GETPROP_COMMAND, new GetPropReceiver(device)); + + // now get the emulator VM name (if applicable). + if (device.isEmulator()) { + EmulatorConsole console = EmulatorConsole.getConsole(device); + if (console != null) { + device.mVmName = console.getVmName(); + } + } } catch (IOException e) { // if we can't get the build info, it doesn't matter too much } diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java b/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java index 4aac0e1..e00073c 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java @@ -54,6 +54,7 @@ public final class EmulatorConsole { private final static String HOST = "127.0.0.1"; //$NON-NLS-1$ private final static String COMMAND_PING = "help\r\n"; //$NON-NLS-1$ + private final static String COMMAND_VM_NAME = "vm name\r\n"; //$NON-NLS-1$ private final static String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$ private final static String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$ private final static String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$ @@ -307,6 +308,24 @@ public final class EmulatorConsole { RemoveConsole(mPort); } } + + public synchronized String getVmName() { + if (sendCommand(COMMAND_VM_NAME)) { + String[] result = readLines(); + if (result != null && result.length == 2) { // this should be the name on first line, + // and ok on 2nd line + return result[0]; + } else { + // try to see if there's a message after KO + Matcher m = RE_KO.matcher(result[result.length-1]); + if (m.matches()) { + return m.group(1); + } + } + } + + return null; + } /** * Get the network status of the emulator. diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java index 106b76f..61d1ca4 100755 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java @@ -44,6 +44,15 @@ public interface IDevice { * Returns the serial number of the device. */ public String getSerialNumber(); + + /** + * Returns the name of the VM the emulator is running. + * <p/>This is only valid if {@link #isEmulator()} returns true. + * <p/>If the emulator is not running any VM (for instance it's running from an Android source + * tree build), this method will return "<code><build></code>". + * @return the name of the VM or <code>null</code> if there isn't any. + */ + public String getVmName(); /** * Returns the state of the device. diff --git a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java index cd4aa26..556fc9b 100644 --- a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java +++ b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java @@ -201,6 +201,10 @@ public class RemoteAndroidTestRunnerTest extends TestCase { throw new UnsupportedOperationException(); } + public String getVmName() { + return ""; + } + } /** An empty implementation of TestRunListener diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java index adaa3c3..1331a09 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java @@ -200,12 +200,22 @@ public final class DevicePanel extends Panel implements IDebugBridgeChangeListen case DEVICE_COL_STATE: return getStateString(device); case DEVICE_COL_BUILD: { + String vmName = device.getVmName(); String debuggable = device.getProperty(Device.PROP_DEBUGGABLE); String version = device.getProperty(Device.PROP_BUILD_VERSION); - if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ - return String.format("%1$s (debug)", version); //$NON-NLS-1$ + if (device.isEmulator()) { + if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ + return String.format("%1$s [%2$s, debug]", vmName, //$NON-NLS-1$ + version); + } else { + return String.format("%1$s [%2$s]", vmName, version); //$NON-NLS-1$ + } } else { - return String.format("%1$s", version); //$NON-NLS-1$ + if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ + return String.format("%1$s, debug", version); //$NON-NLS-1$ + } else { + return String.format("%1$s", version); //$NON-NLS-1$ + } } } } diff --git a/eclipse/features/com.android.ide.eclipse.adt/feature.xml b/eclipse/features/com.android.ide.eclipse.adt/feature.xml index 191b76d..676a89e 100644 --- a/eclipse/features/com.android.ide.eclipse.adt/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.adt/feature.xml @@ -40,6 +40,13 @@ <import plugin="org.eclipse.ui"/> <import plugin="org.eclipse.ui.ide"/> <import plugin="org.eclipse.ui.forms"/> + <import plugin="org.eclipse.gef"/> + <import plugin="org.eclipse.ui.browser"/> + <import plugin="org.eclipse.ui.views"/> + <import plugin="org.eclipse.wst.sse.core"/> + <import plugin="org.eclipse.wst.sse.ui"/> + <import plugin="org.eclipse.wst.xml.core"/> + <import plugin="org.eclipse.wst.xml.ui"/> </requires> <plugin diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java index 61b3f4d..7304e5e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt; +import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; /** @@ -42,8 +43,8 @@ public class AdtConstants { /** Marker for Android Target errors. * This is not cleared on each like other markers. Instead, it's cleared - * when a ContainerClasspathInitialized has succeeded in creating an - * {@link AndroidClasspathContainer}*/ + * when an {@link AndroidClasspathContainerInitializer} has succeeded in creating an + * AndroidClasspathContainer */ public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$ /** Build verbosity "Always". Those messages are always displayed. */ 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 d9c18cf..62bc7ed 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 @@ -1201,7 +1201,7 @@ public class AdtPlugin extends AbstractUIPlugin { if (file.getFullPath().segmentCount() == 4) { // check if we are inside the res folder. String segment = file.getFullPath().segment(1); - if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) { + if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) { // we are inside a res/ folder, get the actual ResourceFolder ProjectResources resources = ResourceManager.getInstance(). getProjectResources(file.getProject()); 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 4d16120..e38419a 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 @@ -31,6 +31,7 @@ import com.android.jarutils.DebugKeyProvider.KeytoolException; import com.android.jarutils.SignedJarBuilder.IZipEntryFilter; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -120,6 +121,10 @@ public class ApkBuilder extends BaseBuilder { } } + /** + * {@inheritDoc} + * @throws CoreException + */ public boolean visit(IResourceDelta delta) throws CoreException { // no need to keep looking if we already know we need to convert // to dex and make the final package. @@ -589,7 +594,7 @@ public class ApkBuilder extends BaseBuilder { * @param osBinPath the path to the output folder of the project * @param osOutFilePath the path of the dex file to create. * @param referencedJavaProjects the list of referenced projects for this project. - * @return + * * @throws CoreException */ private boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath, @@ -756,8 +761,7 @@ public class ApkBuilder extends BaseBuilder { // now write the native libraries. // First look if the lib folder is there. - IResource libFolder = javaProject.getProject().findMember( - AndroidConstants.FD_NATIVE_LIBS); + IResource libFolder = javaProject.getProject().findMember(SdkConstants.FD_NATIVE_LIBS); if (libFolder != null && libFolder.exists() && libFolder.getType() == IResource.FOLDER) { // look inside and put .so in lib/* by keeping the relative folder path. @@ -829,7 +833,7 @@ public class ApkBuilder extends BaseBuilder { * lib folder directly goes in this "lib" folder in the archive. * * - * @param rooSegmentCount The number of segment of the path of the folder containing the + * @param rootSegmentCount The number of segment of the path of the folder containing the * libraries. This is used to compute the path in the archive. * @param jarBuilder the {@link SignedJarBuilder} used to create the archive. * @param resource the IResource to write. @@ -847,7 +851,7 @@ public class ApkBuilder extends BaseBuilder { path = path.removeFirstSegments(rootSegmentCount); // add it to the archive. - IPath apkPath = new Path(AndroidConstants.FD_APK_NATIVE_LIBS); + IPath apkPath = new Path(SdkConstants.FD_APK_NATIVE_LIBS); apkPath = apkPath.append(path); // writes the file in the apk. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java index 47ef626..aec703d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java @@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor; import com.android.ide.eclipse.common.AndroidConstants; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -98,17 +99,17 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor mOutputPath = outputfolder.getFullPath(); } - IResource assetFolder = builder.getProject().findMember(AndroidConstants.FD_ASSETS); + IResource assetFolder = builder.getProject().findMember(SdkConstants.FD_ASSETS); if (assetFolder != null) { mAssetPath = assetFolder.getFullPath(); } - IResource resFolder = builder.getProject().findMember(AndroidConstants.FD_RESOURCES); + IResource resFolder = builder.getProject().findMember(SdkConstants.FD_RESOURCES); if (resFolder != null) { mResPath = resFolder.getFullPath(); } - IResource libFolder = builder.getProject().findMember(AndroidConstants.FD_NATIVE_LIBS); + IResource libFolder = builder.getProject().findMember(SdkConstants.FD_NATIVE_LIBS); if (libFolder != null) { mLibFolder = libFolder.getFullPath(); } @@ -126,8 +127,9 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor return mMakeFinalPackage; } - /* - * (non-Javadoc) + /** + * {@inheritDoc} + * @throws CoreException * * @see org.eclipse.core.resources.IResourceDeltaVisitor * #visit(org.eclipse.core.resources.IResourceDelta) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index f94bdc7..534c123 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -69,15 +69,15 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { /** * First line of dual line aapt error.<br> * "ERROR at line <line>: <error>"<br> - * " (Occured while parsing <path>)" + * " (Occurred while parsing <path>)" */ private final static Pattern sPattern1Line1 = Pattern.compile( "^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$ /** * Second line of dual line aapt error.<br> * "ERROR at line <line>: <error>"<br> - * " (Occured while parsing <path>)"<br> - * @see sPattern1Line1 + * " (Occurred while parsing <path>)"<br> + * @see #sPattern1Line1 */ private final static Pattern sPattern1Line2 = Pattern.compile( "^\\s+\\(Occurred while parsing\\s+(.*)\\)$"); //$NON-NLS-1$ @@ -92,7 +92,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { * Second line of dual line aapt error.<br> * "ERROR: <error>"<br> * "Defined at file <path> line <line>"<br> - * @see sPattern2Line1 + * @see #sPattern2Line1 */ private final static Pattern sPattern2Line2 = Pattern.compile( "Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$ @@ -113,7 +113,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { * Second line of dual line aapt error.<br> * "ERROR parsing XML file <path>"<br> * "<error> at line <line>"<br> - * @see sPattern4Line1 + * @see #sPattern4Line1 */ private final static Pattern sPattern4Line2 = Pattern.compile( "^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$ @@ -263,7 +263,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { /** * Adds a marker to the current project. - * @param file the file to be marked + * * @param markerId The id of the marker to add. * @param message the message associated with the mark * @param severity the severity of the marker. @@ -292,12 +292,11 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { /** * Removes markers from a container and its children. - * @param container The container from which to delete the markers. + * @param folder The container from which to delete the markers. * @param markerId The id of the markers to remove. If null, all marker of * type <code>IMarker.PROBLEM</code> will be removed. */ - protected final void removeMarkersFromContainer(IContainer folder, - String markerId) { + protected final void removeMarkersFromContainer(IContainer folder, String markerId) { try { if (folder.exists()) { folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE); 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 1a4aa8c..9fc4348 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 @@ -1128,10 +1128,8 @@ public class PreCompilerBuilder extends BaseBuilder { * Finish a file created/modified by an outside command line process. * The file is marked as modified by Android, and the parent folder is refreshed, so that, * in case the file didn't exist beforehand, the file appears in the package explorer. - * @param file The file to "finish". - * @param parent The parent container. Can be null (in which case it'll be - * figured out from the file's IResource. - * @param monitor a monitor to display progress. + * @param rFile The R file to "finish". + * @param manifestFile The manifest file to "finish". * @throws CoreException */ private void finishJavaFilesAfterExternalModification(IFile rFile, IFile manifestFile) @@ -1150,9 +1148,7 @@ public class PreCompilerBuilder extends BaseBuilder { * The file is marked as modified by Android, and the parent folder is refreshed, so that, * in case the file didn't exist beforehand, the file appears in the package explorer. * @param file The file to "finish". - * @param parent The parent container. Can be null (in which case it'll be - * figured out from the file's IResource. - * @param monitor a monitor to display progress. + * @param aidlFile The AIDL file to "finish". * @throws CoreException */ private void finishFileAfterExternalModification(IFile file, IFile aidlFile) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java index 33d5fa6..f4778d7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java @@ -23,6 +23,7 @@ import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -154,7 +155,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // then we are not yet in a source or resource folder mInRes = mInSrc = false; - if (AndroidConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) { + if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) { // this is the resource folder that was modified. we want to // see its content. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java index 48ec7c3..2b7d01d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java @@ -31,8 +31,12 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.debug.launching.DeviceChooserDialog.DeviceChooserResponse; import com.android.ide.eclipse.adt.debug.ui.EmulatorConfigTab; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.project.AndroidManifestHelper; -import com.android.sdklib.SdkConstants; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkManager; +import com.android.sdklib.vm.VmManager; +import com.android.sdklib.vm.VmManager.VmInfo; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -58,6 +62,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -69,9 +74,9 @@ import java.util.regex.Pattern; public final class AndroidLaunchController implements IDebugBridgeChangeListener, IDeviceChangeListener, IClientChangeListener { + private static final String FLAG_VM = "-vm"; //$NON-NLS-1$ private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$ private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$ - private static final String FLAG_SKIN = "-skin"; //$NON-NLS-1$ private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$ private static final String FLAG_NO_BOOT_ANIM = "-no-boot-anim"; //$NON-NLS-1$ @@ -223,11 +228,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; /** - * Screen size parameters. - * This value can be provided to the emulator directly for the option "-skin" + * Vm Name. */ - public String mSkin = null; - + public String mVmName = null; + public String mNetworkSpeed = EmulatorConfigTab.getSpeed( LaunchConfigDelegate.DEFAULT_SPEED); public String mNetworkDelay = EmulatorConfigTab.getDelay( @@ -258,12 +262,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } try { - mSkin = config.getAttribute(LaunchConfigDelegate.ATTR_SKIN, mSkin); - if (mSkin == null) { - mSkin = SdkConstants.SKIN_DEFAULT; - } + mVmName = config.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME, mVmName); } catch (CoreException e) { - mSkin = SdkConstants.SKIN_DEFAULT; } int index = LaunchConfigDelegate.DEFAULT_SPEED; @@ -526,11 +526,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // set the launch mode to default. wc.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); - + // set default target mode wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, LaunchConfigDelegate.DEFAULT_TARGET_MODE); + // default VM: None + wc.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null); + // set the default network speed wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED, LaunchConfigDelegate.DEFAULT_SPEED); @@ -539,9 +542,6 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener wc.setAttribute(LaunchConfigDelegate.ATTR_DELAY, LaunchConfigDelegate.DEFAULT_DELAY); - // default skin - wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, SdkConstants.SKIN_DEFAULT); - // default wipe data mode wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, LaunchConfigDelegate.DEFAULT_WIPE_DATA); @@ -627,32 +627,171 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // set the debug mode launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE); - // device chooser response. + // get the SDK + Sdk currentSdk = Sdk.getCurrent(); + VmManager vmManager = currentSdk.getVmManager(); + + // get the project target + final IAndroidTarget projectTarget = currentSdk.getTarget(project); + + // FIXME: check errors on missing sdk, vm manager, or project target. + + // device chooser response object. final DeviceChooserResponse response = new DeviceChooserResponse(); + /* + * Launch logic: + * - Manually Mode + * Always display a UI that lets a user see the current running emulators/devices. + * The UI must show which devices are compatibles, and allow launching new emulators + * with compatible (and not yet running) VM. + * - Automatic Way + * * Preferred VM set. + * If Preferred VM is not running: launch it. + * Launch the application on the preferred VM. + * * No preferred VM. + * Count the number of compatible emulators/devices. + * If != 1, display a UI similar to manual mode. + * If == 1, launch the application on this VM/device. + */ + if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) { // if we are in automatic target mode, we need to find the current devices Device[] devices = AndroidDebugBridge.getBridge().getDevices(); - // depending on the number of devices, we'll simulate an automatic choice - // from the device chooser or simply show up the device chooser. - if (devices.length == 0) { - // if zero devices, we launch the device. - AdtPlugin.printToConsole(project, "Automatic Target Mode: launching new emulator."); + // first check if we have a preferred VM name, and if it actually exists, and is valid + // (ie able to run the project). + // We need to check this in case the VM was recreated with a different target that is + // not compatible. + VmInfo preferredVm = null; + if (config.mVmName != null) { + preferredVm = vmManager.getVm(config.mVmName); + if (projectTarget.isCompatibleBaseFor(preferredVm.getTarget()) == false) { + preferredVm = null; + + AdtPlugin.printErrorToConsole(project, String.format( + "Preferred VM '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible VM...", + config.mVmName, projectTarget.getName())); + } + } + + if (preferredVm != null) { + // look for a matching device + for (Device d : devices) { + String deviceVm = d.getVmName(); + if (deviceVm != null && deviceVm.equals(config.mVmName)) { + response.mustContinue = true; + response.mustLaunchEmulator = false; + response.deviceToUse = d; + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: Preferred VM '%1$s' is available on emulator '%2$s'", + config.mVmName, d)); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } + } + + // at this point we have a valid preferred VM that is not running. + // We need to start it. response.mustContinue = true; response.mustLaunchEmulator = true; + response.vmToLaunch = preferredVm; + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: Preferred VM '%1$s' is not available. Launching new emulator.", + config.mVmName)); + continueLaunch(response, project, launch, launchInfo, config); return; - } else if (devices.length == 1) { + } + + // no (valid) preferred VM? look for one. + HashMap<Device, VmInfo> compatibleRunningVms = new HashMap<Device, VmInfo>(); + boolean hasDevice = false; // if there's 1+ device running, we may force manual mode, + // as we cannot always detect proper compatibility with + // devices. This is the case if the project target is not + // a standard platform + for (Device d : devices) { + String deviceVm = d.getVmName(); + if (deviceVm != null) { // physical devices return null. + VmInfo info = vmManager.getVm(deviceVm); + if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) { + compatibleRunningVms.put(d, info); + } + } else { + if (projectTarget.isPlatform()) { // means this can run on any device as long + // as api level is high enough + String apiString = d.getProperty(SdkManager.PROP_VERSION_SDK); + try { + int apiNumber = Integer.parseInt(apiString); + if (apiNumber >= projectTarget.getApiVersionNumber()) { + // device is compatible with project + compatibleRunningVms.put(d, null); + continue; + } + } catch (NumberFormatException e) { + // do nothing, we'll consider it a non compatible device below. + } + } + hasDevice = true; + } + } + + // depending on the number of devices, we'll simulate an automatic choice + // from the device chooser or simply show up the device chooser. + if (hasDevice == false && compatibleRunningVms.size() == 0) { + // if zero emulators/devices, we launch an emulator. + // We need to figure out which VM first. + + // we are going to take the closest VM. ie a compatible VM that has the API level + // closest to the project target. + VmInfo[] vms = vmManager.getVms(); + VmInfo defaultVm = null; + for (VmInfo vm : vms) { + if (projectTarget.isCompatibleBaseFor(vm.getTarget())) { + if (defaultVm == null || + vm.getTarget().getApiVersionNumber() < + defaultVm.getTarget().getApiVersionNumber()) { + defaultVm = vm; + } + } + } + + if (defaultVm != null) { + response.mustContinue = true; + response.mustLaunchEmulator = true; + response.vmToLaunch = defaultVm; + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: launching new emulator with compatible VM '%1$s'", + defaultVm.getName())); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } else { + // FIXME: ask the user if he wants to create a VM. + // we found no compatible VM. + AdtPlugin.printErrorToConsole(project, String.format( + "Failed to find a VM compatible with target '%1$s'. Launch aborted.", + projectTarget.getName())); + launch.stopLaunch(); + return; + } + } else if (hasDevice == false && compatibleRunningVms.size() == 1) { + Entry<Device, VmInfo> e = compatibleRunningVms.entrySet().iterator().next(); response.mustContinue = true; response.mustLaunchEmulator = false; - response.deviceToUse = devices[0]; + response.deviceToUse = e.getKey(); - if (response.deviceToUse.isEmulator()) { - message = String.format("Automatic Target Mode: using existing emulator: %1$s", - response.deviceToUse); + // get the VmInfo, if null, the device is a physical device. + VmInfo vmInfo = e.getValue(); + if (vmInfo != null) { + message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible VM '%2$s'", + response.deviceToUse, e.getValue().getName()); } else { - message = String.format("Automatic Target Mode: using existing device: %1$s", + message = String.format("Automatic Target Mode: using device '%1$s'", response.deviceToUse); } AdtPlugin.printToConsole(project, message); @@ -662,8 +801,13 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } // if more than one device, we'll bring up the DeviceChooser dialog below. - AdtPlugin.printToConsole(project, - "Automatic Target Mode: user selection for 2+ devices."); + if (compatibleRunningVms.size() >= 2) { + message = "Automatic Target Mode: Several compatible targets. Please select a target device."; + } else if (hasDevice) { + message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device."; + } + + AdtPlugin.printToConsole(project, message); } // bring up the device chooser. @@ -671,7 +815,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener public void run() { DeviceChooserDialog dialog = new DeviceChooserDialog( AdtPlugin.getDisplay().getActiveShell()); - dialog.open(response, project, launch, launchInfo, config); + dialog.open(response, project, projectTarget, launch, launchInfo, config); } }); @@ -705,7 +849,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener synchronized (sListLock) { mWaitingForEmulatorLaunches.add(launchInfo); AdtPlugin.printToConsole(project, "Launching a new emulator."); - boolean status = launchEmulator(config); + boolean status = launchEmulator(config, response.vmToLaunch); if (status == false) { // launching the emulator failed! @@ -775,9 +919,6 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * the device requires it and it is not set in the manifest, the launch will be forced to * "release" mode instead of "debug"</li> * <ul> - * @param launchInfo - * @param device - * @return */ private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) { if (device != null) { @@ -1122,7 +1263,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener /** * launches an application on a device or emulator * - * @param classToLaunch the fully-qualified name of the activity to launch + * @param info the {@link DelayedLaunchInfo} that indicates the activity to launch * @param device the device or emulator to launch the application on */ private void launchApp(final DelayedLaunchInfo info, Device device) { @@ -1182,7 +1323,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } - private boolean launchEmulator(AndroidLaunchConfiguration config) { + private boolean launchEmulator(AndroidLaunchConfiguration config, VmInfo vmToLaunch) { // split the custom command line in segments ArrayList<String> customArgs = new ArrayList<String>(); @@ -1212,10 +1353,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener ArrayList<String> list = new ArrayList<String>(); list.add(AdtPlugin.getOsAbsoluteEmulator()); - if (config.mSkin != null) { - list.add(FLAG_SKIN); - list.add(config.mSkin); - } + list.add(FLAG_VM); + list.add(vmToLaunch.getName()); if (config.mNetworkSpeed != null) { list.add(FLAG_NETSPEED); @@ -1329,7 +1468,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * @param debugPort The port to connect the debugger to * @param androidLaunch The associated AndroidLaunch object. * @param monitor A Progress monitor - * @see connectRemoveDebugger() + * @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor) */ public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch, final IProgressMonitor monitor) { @@ -1352,7 +1491,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * This is sent from a non UI thread. * @param bridge the new {@link AndroidDebugBridge} object. * - * @see IDebugBridgeChangeListener#serverChanged(AndroidDebugBridge) + * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge) */ public void bridgeChanged(AndroidDebugBridge bridge) { // The adb server has changed. We cancel any pending launches. @@ -1447,7 +1586,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * @param device the device that was updated. * @param changeMask the mask indicating what changed. * - * @see IDeviceChangeListener#deviceChanged(Device) + * @see IDeviceChangeListener#deviceChanged(Device, int) */ public void deviceChanged(Device device, int changeMask) { // We could check if any starting device we care about is now ready, but we can wait for @@ -1622,7 +1761,6 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * Get the stderr/stdout outputs of a process and return when the process is done. * Both <b>must</b> be read or the process will block on windows. * @param process The process to get the ouput from - * @throws InterruptedException */ private void grabEmulatorOutput(final Process process) { // read the lines as they come. if null is returned, it's diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java index 2cb11c3..19ec9a7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.debug.launching; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.Client; import com.android.ddmlib.Device; +import com.android.ddmlib.IDevice; import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; import com.android.ddmlib.Device.DeviceState; import com.android.ddmuilib.IImageLoader; @@ -27,7 +28,10 @@ import com.android.ddmuilib.TableHelper; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration; import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.DelayedLaunchInfo; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.vm.VmManager.VmInfo; import org.eclipse.core.resources.IProject; import org.eclipse.jface.preference.IPreferenceStore; @@ -67,19 +71,26 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$ private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$ - private final static String PREFS_COL_BUILD = "deviceChooser.build"; //$NON-NLS-1$ + private final static String PREFS_COL_VM = "deviceChooser.vm"; //$NON-NLS-1$ + private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$ + private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$ private Table mDeviceTable; private TableViewer mViewer; private Image mDeviceImage; private Image mEmulatorImage; + private Image mMatchImage; + private Image mNoMatchImage; + private Image mWarningImage; private Button mOkButton; private Button mCreateButton; private DeviceChooserResponse mResponse; private DelayedLaunchInfo mLaunchInfo; + private IAndroidTarget mProjectTarget; + private Sdk mSdk; /** * Basic Content Provider for a table full of {@link Device} objects. The input is @@ -111,13 +122,44 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener private class LabelProvider implements ITableLabelProvider { public Image getColumnImage(Object element, int columnIndex) { - if (columnIndex == 0 && element instanceof Device) { - if (((Device)element).isEmulator()) { - return mEmulatorImage; + if (element instanceof Device) { + Device device = (Device)element; + switch (columnIndex) { + case 0: + return device.isEmulator() ? mEmulatorImage : mDeviceImage; + + case 2: + // check for compatibility. + if (device.isEmulator() == false) { // physical device + // get the api level of the device + try { + String apiValue = device.getProperty( + IDevice.PROP_BUILD_VERSION_NUMBER); + int api = Integer.parseInt(apiValue); + if (api >= mProjectTarget.getApiVersionNumber()) { + // if the project is compiling against an add-on, the optional + // API may be missing from the device. + return mProjectTarget.isPlatform() ? + mMatchImage : mWarningImage; + } else { + return mNoMatchImage; + } + } catch (NumberFormatException e) { + // lets consider the device non compatible + return mNoMatchImage; + } + } else { + // get the VmInfo + VmInfo info = mSdk.getVmManager().getVm(device.getVmName()); + if (info == null) { + return mWarningImage; + } + return mProjectTarget.isCompatibleBaseFor(info.getTarget()) ? + mMatchImage : mNoMatchImage; + } } - - return mDeviceImage; } + return null; } @@ -128,15 +170,30 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener case 0: return device.getSerialNumber(); case 1: - return getStateString(device); + if (device.isEmulator()) { + return device.getVmName(); + } else { + return "N/A"; // devices don't have VM names. + } case 2: - String debuggable = device.getProperty(Device.PROP_DEBUGGABLE); - String version = device.getProperty(Device.PROP_BUILD_VERSION); + if (device.isEmulator()) { + VmInfo info = mSdk.getVmManager().getVm(device.getVmName()); + if (info == null) { + return "?"; + } + return info.getTarget().getFullName(); + } else { + return device.getProperty(IDevice.PROP_BUILD_VERSION); + } + case 3: + String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE); if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ - return String.format("%1$s (debug)", version); //$NON-NLS-1$ + return "Yes"; } else { - return String.format("%1$s", version); //$NON-NLS-1$ + return ""; } + case 4: + return getStateString(device); } } @@ -164,6 +221,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener public static class DeviceChooserResponse { public boolean mustContinue; public boolean mustLaunchEmulator; + public VmInfo vmToLaunch; public Device deviceToUse; } @@ -175,14 +233,18 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener * Prepare and display the dialog. * @param response * @param project + * @param projectTarget * @param launch * @param launchInfo * @param config */ public void open(DeviceChooserResponse response, IProject project, - AndroidLaunch launch, DelayedLaunchInfo launchInfo, AndroidLaunchConfiguration config) { + IAndroidTarget projectTarget, AndroidLaunch launch, DelayedLaunchInfo launchInfo, + AndroidLaunchConfiguration config) { mResponse = response; + mProjectTarget = projectTarget; mLaunchInfo = launchInfo; + mSdk = Sdk.getCurrent(); Shell parent = getParent(); Shell shell = new Shell(parent, getStyle()); @@ -218,6 +280,9 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener mEmulatorImage.dispose(); mDeviceImage.dispose(); + mMatchImage.dispose(); + mNoMatchImage.dispose(); + mWarningImage.dispose(); AndroidLaunchController.getInstance().continueLaunch(response, project, launch, launchInfo, config); @@ -249,14 +314,22 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$ PREFS_COL_SERIAL, store); + TableHelper.createTableColumn(mDeviceTable, "VM Name", + SWT.LEFT, "engineering", //$NON-NLS-1$ + PREFS_COL_VM, store); + + TableHelper.createTableColumn(mDeviceTable, "Target", + SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$ + PREFS_COL_TARGET, store); + + TableHelper.createTableColumn(mDeviceTable, "Debug", + SWT.LEFT, "Debug", //$NON-NLS-1$ + PREFS_COL_DEBUG, store); + TableHelper.createTableColumn(mDeviceTable, "State", SWT.LEFT, "bootloader", //$NON-NLS-1$ PREFS_COL_STATE, store); - TableHelper.createTableColumn(mDeviceTable, "Build Info", - SWT.LEFT, "engineering", //$NON-NLS-1$ - PREFS_COL_BUILD, store); - // create the viewer for it mViewer = new TableViewer(mDeviceTable); mViewer.setContentProvider(new ContentProvider()); @@ -357,20 +430,42 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener } private void loadImages() { - IImageLoader loader = DdmsPlugin.getImageLoader(); + IImageLoader ddmsLoader = DdmsPlugin.getImageLoader(); Display display = DdmsPlugin.getDisplay(); + IImageLoader adtLoader = AdtPlugin.getImageLoader(); if (mDeviceImage == null) { - mDeviceImage = ImageHelper.loadImage(loader, display, + mDeviceImage = ImageHelper.loadImage(ddmsLoader, display, "device.png", //$NON-NLS-1$ ICON_WIDTH, ICON_WIDTH, display.getSystemColor(SWT.COLOR_RED)); } if (mEmulatorImage == null) { - mEmulatorImage = ImageHelper.loadImage(loader, display, + mEmulatorImage = ImageHelper.loadImage(ddmsLoader, display, "emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$ display.getSystemColor(SWT.COLOR_BLUE)); } + + if (mMatchImage == null) { + mMatchImage = ImageHelper.loadImage(adtLoader, display, + "match.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_GREEN)); + } + + if (mNoMatchImage == null) { + mNoMatchImage = ImageHelper.loadImage(adtLoader, display, + "error.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_RED)); + } + + if (mWarningImage == null) { + mWarningImage = ImageHelper.loadImage(adtLoader, display, + "warning.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_YELLOW)); + } } @@ -438,7 +533,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener * @param device the device that was updated. * @param changeMask the mask indicating what changed. * - * @see IDeviceChangeListener#deviceChanged(Device) + * @see IDeviceChangeListener#deviceChanged(Device, int) */ public void deviceChanged(final Device device, int changeMask) { if ((changeMask & (Device.CHANGE_STATE | Device.CHANGE_BUILD_INFO)) != 0) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java index 5d3e349..68deec3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java @@ -80,9 +80,8 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { */ public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ - /** Skin to be used to launch the emulator */ - public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$ - + public static final String ATTR_VM_NAME = AdtPlugin.PLUGIN_ID + ".vm"; //$NON-NLS-1$ + public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ /** @@ -317,6 +316,10 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { 1 /* code, unused */, "Can't find the project!", null /* exception */)); } + /** + * {@inheritDoc} + * @throws CoreException + */ @Override public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException { @@ -406,8 +409,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { /** * Returns the name of the activity. - * @param configuration - * @return */ private String getActivityName(ILaunchConfiguration configuration) { String empty = ""; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java index c7b340c..f4f5281 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java @@ -22,7 +22,9 @@ import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkConstants; +import com.android.sdklib.vm.VmManager; +import com.android.sdklib.vm.VmManager.VmInfo; +import com.android.sdkuilib.VmSelector; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; @@ -70,6 +72,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { { "UMTS", "umts" }, //$NON-NLS-2$ }; + private Button mAutoTargetButton; + private Button mManualTargetButton; + + private VmSelector mPreferredVmSelector; + private Combo mSpeedCombo; private Combo mDelayCombo; @@ -78,18 +85,10 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { private Text mEmulatorCLOptions; - private Combo mSkinCombo; - - private Button mAutoTargetButton; - - private Button mManualTargetButton; - private Button mWipeDataButton; private Button mNoBootAnimButton; - private IAndroidTarget mTarget; - /** * Returns the emulator ready speed option value. * @param value The index of the combo selection. @@ -147,6 +146,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { targetModeGroup.setLayout(layout); targetModeGroup.setFont(font); + mManualTargetButton = new Button(targetModeGroup, SWT.RADIO); + mManualTargetButton.setText("Manual"); + // Since there are only 2 radio buttons, we can put a listener on only one (they + // are both called on select and unselect event. + // add the radio button mAutoTargetButton = new Button(targetModeGroup, SWT.RADIO); mAutoTargetButton.setText("Automatic"); @@ -159,11 +163,16 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { } }); - mManualTargetButton = new Button(targetModeGroup, SWT.RADIO); - mManualTargetButton.setText("Manual"); - // Since there are only 2 radio buttons, we can put a listener on only - // one (they - // are both called on select and unselect event. + new Label(targetModeGroup, SWT.NONE).setText("Preferred VM"); + VmInfo[] vms = new VmInfo[0]; + mPreferredVmSelector = new VmSelector(targetModeGroup, vms, + false /*allowMultipleSelection*/); + mPreferredVmSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); // emulator size mEmulatorOptionsGroup = new Group(topComp, SWT.NONE); @@ -174,17 +183,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { mEmulatorOptionsGroup.setLayout(layout); mEmulatorOptionsGroup.setFont(font); - new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:"); - - mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); - mSkinCombo.addSelectionListener(new SelectionAdapter() { - // called when selection changes - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - // network options new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:"); @@ -279,8 +277,9 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration) */ public void initializeFrom(ILaunchConfiguration configuration) { - boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == - // automatic + VmManager vmManager = Sdk.getCurrent().getVmManager(); + + boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic try { value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value); } catch (CoreException e) { @@ -290,11 +289,12 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { mManualTargetButton.setSelection(!value); // look for the project name to get its target. - String projectName = ""; + String stringValue = ""; try { - projectName = configuration.getAttribute( - IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, projectName); + stringValue = configuration.getAttribute( + IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, stringValue); } catch (CoreException ce) { + // let's not do anything here, we'll use the default value } IProject project = null; @@ -304,25 +304,41 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { if (projects != null) { // look for the project whose name we read from the configuration. for (IJavaProject p : projects) { - if (p.getElementName().equals(projectName)) { + if (p.getElementName().equals(stringValue)) { project = p.getProject(); break; } } } - mSkinCombo.removeAll(); + // update the VM list + VmInfo[] vms = null; + if (vmManager != null) { + vms = vmManager.getVms(); + } + + IAndroidTarget projectTarget = null; if (project != null) { - mTarget = Sdk.getCurrent().getTarget(project); - if (mTarget != null) { - String[] skins = mTarget.getSkins(); - if (skins != null) { - for (String skin : skins) { - mSkinCombo.add(skin); - } - mSkinCombo.pack(); - } - } + projectTarget = Sdk.getCurrent().getTarget(project); + } else { + vms = null; // no project? we don't want to display any "compatible" VMs. + } + + mPreferredVmSelector.setVms(vms, projectTarget); + + stringValue = ""; + try { + stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME, + stringValue); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + + if (stringValue != null && stringValue.length() > 0 && vmManager != null) { + VmInfo targetVm = vmManager.getVm(stringValue); + mPreferredVmSelector.setSelection(targetVm); + } else { + mPreferredVmSelector.setSelection(null); } value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; @@ -342,23 +358,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { mNoBootAnimButton.setSelection(value); int index = -1; - try { - String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null); - if (skin == null) { - skin = SdkConstants.SKIN_DEFAULT; - } - - index = getSkinIndex(skin); - } catch (CoreException e) { - index = getSkinIndex(SdkConstants.SKIN_DEFAULT); - } - - if (index == -1) { - mSkinCombo.select(0); - updateLaunchConfigurationDialog(); - } else { - mSkinCombo.select(index); - } index = LaunchConfigDelegate.DEFAULT_SPEED; try { @@ -405,8 +404,12 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { public void performApply(ILaunchConfigurationWorkingCopy configuration) { configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, mAutoTargetButton.getSelection()); - configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN, - getSkinNameByIndex(mSkinCombo.getSelectionIndex())); + VmInfo vm = mPreferredVmSelector.getFirstSelected(); + if (vm != null) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, vm.getName()); + } else { + configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null); + } configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, mSpeedCombo.getSelectionIndex()); configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, @@ -425,8 +428,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, LaunchConfigDelegate.DEFAULT_TARGET_MODE); - configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN, - SdkConstants.SKIN_DEFAULT); configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, LaunchConfigDelegate.DEFAULT_SPEED); configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, @@ -440,33 +441,4 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS); configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions); } - - private String getSkinNameByIndex(int index) { - if (mTarget != null && index > 0) { - String[] skins = mTarget.getSkins(); - if (skins != null && index < skins.length) { - return skins[index]; - } - } - - return null; - } - - private int getSkinIndex(String name) { - if (mTarget != null) { - String[] skins = mTarget.getSkins(); - if (skins != null) { - int index = 0; - for (String skin : skins) { - if (skin.equalsIgnoreCase(name)) { - return index; - } - index++; - } - } - } - - return -1; - } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java index af7f2bb..6a40ed0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java @@ -398,11 +398,13 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { config.setMappedResources(resources); } - /** Loads the ui with the activities of the specified project, and store the - * activities in <code>mActivities</code> + /** + * Loads the ui with the activities of the specified project, and stores the + * activities in <code>mActivities</code>. + * <p/> * First activity is selected by default if present. - * @param project The project to load the activities from - * @return The array of activities or null if none could be found. + * + * @param project The project to load the activities from. */ private void loadActivities(IProject project) { if (project != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java index 434269c..e64c2f4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java @@ -199,7 +199,7 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements */ private void handleException(Throwable t) { String msg = t.getMessage(); - if (t == null) { + if (msg == null) { Throwable cause = t.getCause(); if (cause != null) { handleException(cause); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java index 1ca89cd..7fc3318 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java @@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.project; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.common.AndroidConstants; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; @@ -53,13 +54,13 @@ public class FolderDecorator implements ILightweightLabelDecorator { // check the folder is directly under the project. if (folder.getParent().getType() == IResource.PROJECT) { String name = folder.getName(); - if (name.equals(AndroidConstants.FD_ASSETS)) { + if (name.equals(SdkConstants.FD_ASSETS)) { decorate(decoration, " [Android assets]"); decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); - } else if (name.equals(AndroidConstants.FD_RESOURCES)) { + } else if (name.equals(SdkConstants.FD_RESOURCES)) { decorate(decoration, " [Android resources]"); decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); - } else if (name.equals(AndroidConstants.FD_NATIVE_LIBS)) { + } else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) { decorate(decoration, " [Native Libraries]"); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java index de4b339..399eac9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java @@ -493,7 +493,7 @@ public final class ExportWizard extends Wizard implements IExportWizard { } // no more cause and still no message. display the first exception. - return cause.getClass().getCanonicalName(); + return t.getClass().getCanonicalName(); } return message; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java index 3614be3..e161e18 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java @@ -256,7 +256,6 @@ final class ProjectCheckPage extends ExportWizardPage { /** * Checks the parameters for correctness, and update the error message and buttons. - * @return the current IProject of this launch config. */ private void handleProjectNameChange() { setPageComplete(false); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java index 2cafa01..4da216c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java @@ -28,8 +28,12 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.ClasspathContainerInitializer; import org.eclipse.jdt.core.IAccessRule; import org.eclipse.jdt.core.IClasspathAttribute; @@ -125,7 +129,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit */ private static IClasspathContainer allocateAndroidContainer(String containerId, IJavaProject javaProject) { - IProject iProject = javaProject.getProject(); + final IProject iProject = javaProject.getProject(); // remove potential MARKER_TARGETs. try { @@ -139,7 +143,9 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit } - // first we check if the SDK has been loaded + // First we check if the SDK has been loaded. + // By passing the javaProject to getSdkLoadStatus(), we ensure that, should the SDK + // not be loaded yet, the classpath container will be resolved again once the SDK is loaded. boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) == LoadStatus.LOADED; @@ -172,8 +178,14 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit String message = null; boolean outputToConsole = true; if (hashString == null || hashString.length() == 0) { - message = String.format( - "Project has no target set. Edit the project properties to set one."); + // if there is no hash string we only show this if the SDK is loaded. + // For a project opened at start-up with no target, this would be displayed twice, + // once when the project is opened, and once after the SDK has finished loading. + // By testing the sdk is loaded, we only show this once in the console. + if (sdkIsLoaded) { + message = String.format( + "Project has no target set. Edit the project properties to set one."); + } } else if (sdkIsLoaded) { message = String.format( "Unable to resolve target '%s'", hashString); @@ -187,23 +199,41 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // and it's expected. (we do keep the error marker though). outputToConsole = false; } - - // log the error and put the marker on the project - if (outputToConsole) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message); - } - IMarker marker = BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message, - IMarker.SEVERITY_ERROR); - // add a marker priority as this is an more important error than the error that will - // spring from the lack of library - try { - marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); - } catch (CoreException e) { - // just log the error - AdtPlugin.log(e, "Error changing target marker priority."); + if (message != null) { + // log the error and put the marker on the project if we can. + if (outputToConsole) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message); + } + + try { + BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message, -1, + IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); + } catch (CoreException e) { + // In some cases, the workspace may be locked for modification when we pass here. + // We schedule a new job to put the marker after. + final String fmessage = message; + Job markerJob = new Job("Android SDK: Resolving error markers") { + @SuppressWarnings("unchecked") + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, + fmessage, -1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); + } catch (CoreException e2) { + return e2.getStatus(); + } + + return Status.OK_STATUS; + } + }; + + // build jobs are run after other interactive jobs + markerJob.setPriority(Job.BUILD); + markerJob.schedule(); + } } - + // return a dummy container to replace the one we may have had before. return new IClasspathContainer() { public IClasspathEntry[] getClasspathEntries() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java index fad4f19..1f6ebf1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java @@ -37,7 +37,8 @@ import javax.management.InvalidAttributeValueException; public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader { /** - * Wrapper around a {@link Class} to provide the methods of {@link IClassDescriptor}. + * Wrapper around a {@link Class} to provide the methods of + * {@link IAndroidClassLoader.IClassDescriptor}. */ public final static class ClassWrapper implements IClassDescriptor { private Class<?> mClass; @@ -416,7 +417,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader } /** - * Returns a {@link IClass} by its fully-qualified name. + * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name. * @param className the fully-qualified name of the class to return. * @throws ClassNotFoundException */ 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 232b9e8..dfe876f 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 @@ -82,11 +82,8 @@ public final class AndroidTargetParser { /** * Parses the framework, collects all interesting information and stores them in the - * {@link FrameworkResourceManager} given to the constructor. + * {@link IAndroidTarget} given to the constructor. * - * @param osSdkPath the OS path of the SDK directory. - * @param resourceManager the {@link FrameworkResourceManager} that will store the parsed - * resources. * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. * @return True if the SDK path was valid and parsing has been attempted. */ @@ -400,7 +397,6 @@ public final class AndroidTargetParser { * Loads and collects the action and category default values from the framework. * The values are added to the <code>actions</code> and <code>categories</code> lists. * - * @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator. * @param activityActions the list which will receive the activity action values. * @param broadcastActions the list which will receive the broadcast action values. * @param serviceActions the list which will receive the service action values. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java index 50d319e..35057d1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java @@ -64,7 +64,7 @@ public interface IAndroidClassLoader { throws IOException, InvalidAttributeValueException, ClassFormatError; /** - * Returns a {@link IClass} by its fully-qualified name. + * Returns a {@link IClassDescriptor} by its fully-qualified name. * @param className the fully-qualified name of the class to return. * @throws ClassNotFoundException */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java index 3b9d10e..19f8f45 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java @@ -18,17 +18,17 @@ package com.android.ide.eclipse.adt.sdk; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; +import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; import com.android.sdklib.project.ProjectProperties; +import com.android.sdklib.project.ProjectProperties.PropertyType; +import com.android.sdklib.vm.VmManager; import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -46,21 +46,20 @@ import java.util.HashMap; * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of * the Sdk object. * - * To get the list of platforms present in the SDK, call {@link #getPlatforms()}. - * To get the list of add-ons present in the SDK, call {@link #getAddons()}. - * + * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}. */ public class Sdk { - private final static String PROPERTY_PROJECT_TARGET = "androidTarget"; //$NON-NLS-1$ - private static Sdk sCurrentSdk = null; private final SdkManager mManager; + private final VmManager mVmManager; + private final HashMap<IProject, IAndroidTarget> mProjectMap = new HashMap<IProject, IAndroidTarget>(); private final HashMap<IAndroidTarget, AndroidTargetData> mTargetMap = new HashMap<IAndroidTarget, AndroidTargetData>(); private final String mDocBaseUrl; + /** * Loads an SDK and returns an {@link Sdk} object if success. @@ -74,18 +73,33 @@ public class Sdk { final ArrayList<String> logMessages = new ArrayList<String>(); ISdkLog log = new ISdkLog() { - public void error(String errorFormat, Object... arg) { - logMessages.add(String.format(errorFormat, arg)); + public void error(Throwable throwable, String errorFormat, Object... arg) { + if (errorFormat != null) { + logMessages.add(String.format(errorFormat, arg)); + } + + if (throwable != null) { + logMessages.add(throwable.getMessage()); + } } public void warning(String warningFormat, Object... arg) { logMessages.add(String.format(warningFormat, arg)); } + public void printf(String msgFormat, Object... arg) { + logMessages.add(String.format(msgFormat, arg)); + } }; // get an SdkManager object for the location SdkManager manager = SdkManager.createManager(sdkLocation, log); if (manager != null) { - sCurrentSdk = new Sdk(manager); + VmManager vmManager = null; + try { + vmManager = new VmManager(manager, log); + } catch (AndroidLocationException e) { + log.error(e, "Error parsing the VMs"); + } + sCurrentSdk = new Sdk(manager, vmManager); return sCurrentSdk; } else { StringBuilder sb = new StringBuilder("Error Loading the SDK:\n"); @@ -106,6 +120,13 @@ public class Sdk { } /** + * Returns the location (OS path) of the current SDK. + */ + public String getSdkLocation() { + return mManager.getLocation(); + } + + /** * Returns the URL to the local documentation. * Can return null if no documentation is found in the current SDK. * @@ -181,7 +202,8 @@ public class Sdk { */ public static String getProjectTargetHashString(IProject project) { // load the default.properties from the project folder. - ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString()); + ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(), + PropertyType.DEFAULT); if (properties == null) { AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'", project.getName()); @@ -200,10 +222,12 @@ public class Sdk { public static void setProjectTargetHashString(IProject project, String targetHashString) { // because we don't want to erase other properties from default.properties, we first load // them - ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString()); + ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(), + PropertyType.DEFAULT); if (properties == null) { // doesn't exist yet? we create it. - properties = ProjectProperties.create(project.getLocation().toOSString()); + properties = ProjectProperties.create(project.getLocation().toOSString(), + PropertyType.DEFAULT); } // add/change the target hash string. @@ -218,7 +242,7 @@ public class Sdk { } } /** - * Return the {@link PlatformData} for a given {@link IAndroidTarget}. + * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}. */ public AndroidTargetData getTargetData(IAndroidTarget target) { synchronized (mTargetMap) { @@ -226,8 +250,17 @@ public class Sdk { } } - private Sdk(SdkManager manager) { + /** + * Returns the {@link VmManager}. If the VmManager failed to parse the VM folder, this could + * be <code>null</code>. + */ + public VmManager getVmManager() { + return mVmManager; + } + + private Sdk(SdkManager manager, VmManager vmManager) { mManager = manager; + mVmManager = vmManager; // pre-compute some paths mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java index 8db09f2..0e60f8a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java @@ -323,7 +323,7 @@ public final class WidgetClassLoader implements IAndroidClassLoader { } /** - * Returns a {@link IClass} by its fully-qualified name. + * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name. * @param className the fully-qualified name of the class to return. * @throws ClassNotFoundException */ 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 8044bcb..7fc94ef 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 @@ -26,6 +26,7 @@ import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestHelper; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; import com.android.sdkuilib.SdkTargetSelector; import org.eclipse.core.filesystem.URIUtil; @@ -64,9 +65,11 @@ 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 - * <li> Location of the SDK * </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. @@ -93,13 +96,14 @@ public class NewProjectCreationPage extends WizardPage { 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 static boolean sAutoComputeCustomLocation = true; private final int MSG_NONE = 0; private final int MSG_WARNING = 1; private final int MSG_ERROR = 2; private String mUserPackageName = ""; //$NON-NLS-1$ - private String mUserActivityName = ""; //$NON-NLS-1$ + private String mUserActivityName = ""; //$NON-NLS-1$ private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY; private String mSourceFolder = ""; //$NON-NLS-1$ @@ -114,6 +118,8 @@ public class NewProjectCreationPage extends WizardPage { private Text mLocationPathField; private Button mBrowseButton; private Button mCreateActivityCheck; + private Text mMinSdkVersionField; + private SdkTargetSelector mSdkTargetSelector; private boolean mInternalLocationPathUpdate; protected boolean mInternalProjectNameUpdate; @@ -122,7 +128,7 @@ public class NewProjectCreationPage extends WizardPage { private boolean mInternalActivityNameUpdate; protected boolean mProjectNameModifiedByUser; protected boolean mApplicationNameModifiedByUser; - private SdkTargetSelector mSdkTargetSelector; + private boolean mInternalMinSdkVersionUpdate; /** @@ -133,13 +139,6 @@ public class NewProjectCreationPage extends WizardPage { public NewProjectCreationPage(String pageName) { super(pageName); setPageComplete(false); - if (sCustomLocationOsPath == null || - sCustomLocationOsPath.length() == 0 || - !new File(sCustomLocationOsPath).isDirectory()) { - // FIXME location of samples is pretty much impossible here. - //sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder(); - sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath(); - } } // --- Getters used by NewProjectWizard --- @@ -170,6 +169,11 @@ public class NewProjectCreationPage extends WizardPage { 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(); + } + /** Returns the value of the application name field with spaces trimmed. */ public String getApplicationName() { // Return the name of the activity as default application name. @@ -200,7 +204,7 @@ public class NewProjectCreationPage extends WizardPage { * "src" constant. */ public String getSourceFolder() { if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) { - return AndroidConstants.FD_SOURCES; + return SdkConstants.FD_SOURCES; } else { return mSourceFolder; } @@ -389,9 +393,16 @@ public class NewProjectCreationPage extends WizardPage { } mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/); + + // If there's only one target, select it + if (targets != null && targets.length == 1) { + mSdkTargetSelector.setSelection(targets[0]); + } + mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { + updateLocationPathField(null); setPageComplete(validatePage()); } }); @@ -506,6 +517,25 @@ public class NewProjectCreationPage extends WizardPage { onActivityNameFieldModified(); } }); + + // 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(); + setPageComplete(validatePage()); + } + }); } @@ -588,7 +618,29 @@ public class NewProjectCreationPage extends WizardPage { mInternalLocationPathUpdate = true; if (custom_location) { if (abs_dir != null) { + // We get here if the user selected a directory with the "Browse" button. + // Disable auto-compute of the custom location unless the user selected + // the exact same path. + sAutoComputeCustomLocation = sAutoComputeCustomLocation && + abs_dir.equals(sCustomLocationOsPath); sCustomLocationOsPath = TextProcessor.process(abs_dir); + } else if (sAutoComputeCustomLocation || + !new File(sCustomLocationOsPath).isDirectory()) { + // By default select the samples directory of the current target + IAndroidTarget target = getSdkTarget(); + if (target != null) { + sCustomLocationOsPath = target.getPath(IAndroidTarget.SAMPLES); + } + + // If we don't have a target, select the base directory of the + // "universal sdk". If we don't even have that, use a root drive. + if (sCustomLocationOsPath == null || sCustomLocationOsPath.length() == 0) { + if (Sdk.getCurrent() != null) { + sCustomLocationOsPath = Sdk.getCurrent().getSdkLocation(); + } else { + sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath(); + } + } } if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) { mLocationPathField.setText(sCustomLocationOsPath); @@ -615,8 +667,13 @@ public class NewProjectCreationPage extends WizardPage { 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. - sCustomLocationOsPath = getLocationPathFieldValue(); + // editing the field manually, in which case we want to save the value internally + // and we disable auto-compute of the custom location (to avoid overriding the user + // value) + String newPath = getLocationPathFieldValue(); + sAutoComputeCustomLocation = sAutoComputeCustomLocation && + newPath.equals(sCustomLocationOsPath); + sCustomLocationOsPath = newPath; extractNamesFromAndroidManifest(); setPageComplete(validatePage()); } @@ -665,6 +722,31 @@ public class NewProjectCreationPage extends WizardPage { } /** + * 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) { + return; + } + + try { + int version = Integer.parseInt(getMinSdkVersion()); + + for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { + if (target.getApiVersionNumber() == version) { + mSdkTargetSelector.setSelection(target); + break; + } + } + } catch (NumberFormatException e) { + // ignore + } + } + + /** * Called when the radio buttons are changed between the "create new project" and the * "use existing source" mode. This reverts the fields to whatever the user manually * entered before. @@ -697,89 +779,132 @@ public class NewProjectCreationPage extends WizardPage { * can actually be found in the custom user directory. */ private void extractNamesFromAndroidManifest() { - if (!isNewProject()) { - File f = new File(getProjectLocation()); - if (f.isDirectory()) { - Path path = new Path(f.getPath()); - String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); - if (manifest.exists()) { - String packageName = null; - String activityName = null; - try { - packageName = manifest.getPackageName(); - activityName = manifest.getActivityName(1); - } catch (Exception e) { - // pass - } + if (isNewProject()) { + return; + } + String projectLocation = getProjectLocation(); + File f = new File(projectLocation); + if (!f.isDirectory()) { + return; + } - if (packageName != null && packageName.length() > 0) { - mPackageNameField.setText(packageName); - } + Path path = new Path(f.getPath()); + String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); + AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); + if (!manifest.exists()) { + return; + } + + String packageName = null; + String activityName = null; + String minSdkVersion = null; + try { + packageName = manifest.getPackageName(); + activityName = manifest.getActivityName(1); + minSdkVersion = manifest.getMinSdkVersion(); + } catch (Exception e) { + // ignore exceptions + } - if (activityName != null && activityName.length() > 0) { - mInternalActivityNameUpdate = true; - mInternalCreateActivityUpdate = true; - mActivityNameField.setText(activityName); - mCreateActivityCheck.setSelection(true); - mInternalCreateActivityUpdate = false; - mInternalActivityNameUpdate = false; - - // If project name and application names are empty, use the activity - // name as a default. If the activity name has dots, it's a part of a - // package specification and only the last identifier must be used. - if (activityName.indexOf('.') != -1) { - String[] ids = activityName.split(AndroidConstants.RE_DOT); - activityName = ids[ids.length - 1]; - } - if (mProjectNameField.getText().length() == 0 || - !mProjectNameModifiedByUser) { - mInternalProjectNameUpdate = true; - mProjectNameField.setText(activityName); - mInternalProjectNameUpdate = false; - } - if (mApplicationNameField.getText().length() == 0 || - !mApplicationNameModifiedByUser) { - mInternalApplicationNameUpdate = true; - mApplicationNameField.setText(activityName); - mInternalApplicationNameUpdate = false; - } - } else { - mInternalActivityNameUpdate = true; - mInternalCreateActivityUpdate = true; - mActivityNameField.setText(""); - 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) { - // Package name is a java identifier, so it's most suitable for - // an application name. - - if (mApplicationNameField.getText().length() == 0 || - !mApplicationNameModifiedByUser) { - mInternalApplicationNameUpdate = true; - mApplicationNameField.setText(packageName); - mInternalApplicationNameUpdate = false; - } - - // For the project name, remove any dots - packageName = packageName.replace('.', '_'); - if (mProjectNameField.getText().length() == 0 || - !mProjectNameModifiedByUser) { - mInternalProjectNameUpdate = true; - mProjectNameField.setText(packageName); - mInternalProjectNameUpdate = false; - } - - } + + if (packageName != null && packageName.length() > 0) { + mPackageNameField.setText(packageName); + } + + if (activityName != null && activityName.length() > 0) { + mInternalActivityNameUpdate = true; + mInternalCreateActivityUpdate = true; + mActivityNameField.setText(activityName); + mCreateActivityCheck.setSelection(true); + mInternalCreateActivityUpdate = false; + mInternalActivityNameUpdate = false; + + // If project name and application names are empty, use the activity + // name as a default. If the activity name has dots, it's a part of a + // package specification and only the last identifier must be used. + if (activityName.indexOf('.') != -1) { + String[] ids = activityName.split(AndroidConstants.RE_DOT); + activityName = ids[ids.length - 1]; + } + if (mProjectNameField.getText().length() == 0 || + !mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(activityName); + mInternalProjectNameUpdate = false; + } + if (mApplicationNameField.getText().length() == 0 || + !mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(activityName); + mInternalApplicationNameUpdate = false; + } + } else { + mInternalActivityNameUpdate = true; + mInternalCreateActivityUpdate = true; + mActivityNameField.setText(""); //$NON-NLS-1$ + 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) { + // Package name is a java identifier, so it's most suitable for + // an application name. + + if (mApplicationNameField.getText().length() == 0 || + !mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(packageName); + mInternalApplicationNameUpdate = false; + } + + // For the project name, remove any dots + packageName = packageName.replace('.', '_'); + if (mProjectNameField.getText().length() == 0 || + !mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(packageName); + mInternalProjectNameUpdate = false; + } + + } + } + + // Select the target matching the manifest's sdk, if any + boolean foundTarget = false; + if (minSdkVersion != null) { + try { + int sdkVersion = Integer.parseInt(minSdkVersion); + + for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { + if (target.getApiVersionNumber() == sdkVersion) { + mSdkTargetSelector.setSelection(target); + foundTarget = true; + break; } } + } catch(NumberFormatException e) { + // ignore + } + } + + if (!foundTarget) { + for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { + if (projectLocation.startsWith(target.getLocation())) { + mSdkTargetSelector.setSelection(target); + foundTarget = true; + break; + } } } + + if (!foundTarget) { + mInternalMinSdkVersionUpdate = true; + mMinSdkVersionField.setText(minSdkVersion == null ? "" : minSdkVersion); //$NON-NLS-1$ + mInternalMinSdkVersionUpdate = false; + } } /** @@ -805,6 +930,9 @@ public class NewProjectCreationPage extends WizardPage { status |= validateActivityField(); } if ((status & MSG_ERROR) == 0) { + status |= validateMinSdkVersionField(); + } + if ((status & MSG_ERROR) == 0) { status |= validateSourceFolder(); } if (status == MSG_NONE) { @@ -950,6 +1078,38 @@ 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) { + return MSG_NONE; + } + + int version = -1; + try { + // If not empty, it must be a valid integer > 0 + version = Integer.parseInt(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) { + return setStatus("The API level for the selected SDK target does not match the Min SDK version.", + MSG_WARNING); + } + + return MSG_NONE; + } + + /** * Validates the activity name field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. 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 a582217..607159a 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 @@ -22,6 +22,7 @@ import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -85,32 +86,36 @@ public class NewProjectWizard extends Wizard implements INewWizard { 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$ private static final String BIN_DIRECTORY = - AndroidConstants.FD_BINARIES + AndroidConstants.WS_SEP; + SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP; private static final String RES_DIRECTORY = - AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; private static final String ASSETS_DIRECTORY = - AndroidConstants.FD_ASSETS + AndroidConstants.WS_SEP; + SdkConstants.FD_ASSETS + AndroidConstants.WS_SEP; private static final String DRAWABLE_DIRECTORY = - AndroidConstants.FD_DRAWABLE + AndroidConstants.WS_SEP; + SdkConstants.FD_DRAWABLE + AndroidConstants.WS_SEP; private static final String LAYOUT_DIRECTORY = - AndroidConstants.FD_LAYOUT + AndroidConstants.WS_SEP; + SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP; private static final String VALUES_DIRECTORY = - AndroidConstants.FD_VALUES + AndroidConstants.WS_SEP; + SdkConstants.FD_VALUES + AndroidConstants.WS_SEP; private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$ private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY + "AndroidManifest.template"; //$NON-NLS-1$ private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY + "activity.template"; //$NON-NLS-1$ + private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY + + "uses-sdk.template"; //$NON-NLS-1$ private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY + "launcher_intent_filter.template"; //$NON-NLS-1$ - + private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY + "strings.template"; //$NON-NLS-1$ private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY @@ -235,6 +240,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject()); parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder()); parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget()); + parameters.put(PARAM_MIN_SDK_VERSION, mMainPage.getMinSdkVersion()); if (mMainPage.isCreateActivity()) { // An activity name can be of the form ".package.Class" or ".Class". @@ -449,6 +455,15 @@ public class NewProjectWizard extends Wizard implements INewWizard { // remove the activity(ies) from the manifest manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); } + + String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION); + if (minSdkVersion != null && minSdkVersion.length() > 0) { + String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK); + String usesSdk = replaceParameters(usesSdkTemplate, parameters); + manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk); + } else { + manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, ""); + } // Save in the project as UTF-8 InputStream stream = new ByteArrayInputStream( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java index f3f7b79..0e780a9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java @@ -130,48 +130,14 @@ public class AndroidConstants { public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? "traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$ - /** Folder Names for Android Projects . */ - - /* Resources folder name, i.e. "res". */ - public final static String FD_RESOURCES = "res"; //$NON-NLS-1$ - /** Assets folder name, i.e. "assets" */ - public final static String FD_ASSETS = "assets"; //$NON-NLS-1$ - /** Default source folder name, i.e. "src" */ - public final static String FD_SOURCES = "src"; //$NON-NLS-1$ - /** Default native library folder name inside the project, i.e. "libs" - * While the folder inside the .apk is "lib", we call that one libs because - * that's what we use in ant for both .jar and .so and we need to make the 2 development ways - * compatible. */ - public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$ - /** Native lib folder inside the APK: "lib" */ - public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$ - /** Default bin folder name, i.e. "bin" */ - public final static String FD_BINARIES = "bin"; //$NON-NLS-1$ - /** Default anim resource folder name, i.e. "anim" */ - public final static String FD_ANIM = "anim"; //$NON-NLS-1$ - /** Default color resource folder name, i.e. "color" */ - public final static String FD_COLOR = "color"; //$NON-NLS-1$ - /** Default drawable resource folder name, i.e. "drawable" */ - public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$ - /** Default layout resource folder name, i.e. "layout" */ - public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "menu" */ - public final static String FD_MENU = "menu"; //$NON-NLS-1$ - /** Default values resource folder name, i.e. "values" */ - public final static String FD_VALUES = "values"; //$NON-NLS-1$ - /** Default xml resource folder name, i.e. "xml" */ - public final static String FD_XML = "xml"; //$NON-NLS-1$ - /** Default raw resource folder name, i.e. "raw" */ - public final static String FD_RAW = "raw"; //$NON-NLS-1$ - /** Absolute path of the workspace root, i.e. "/" */ public final static String WS_ROOT = WS_SEP; /** Absolute path of the resource folder, eg "/res".<br> This is a workspace path. */ - public final static String WS_RESOURCES = WS_SEP + FD_RESOURCES; + public final static String WS_RESOURCES = WS_SEP + SdkConstants.FD_RESOURCES; /** Absolute path of the resource folder, eg "/assets".<br> This is a workspace path. */ - public final static String WS_ASSETS = WS_SEP + FD_ASSETS; + public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS; /** Leaf of the javaDoc folder. Does not start with a separator. */ public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/reference"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java index 2db9e9b..cd238d2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java @@ -97,7 +97,7 @@ public class AndroidManifestHelper { */ public String getPackageName() { try { - return getPackageNameInternal(mXPath, getSource()); + return mXPath.evaluate("/manifest/@package", getSource()); //$NON-NLS-1$ } catch (XPathExpressionException e1) { // If the XPath failed to evaluate, we'll return null. } catch (Exception e) { @@ -111,17 +111,39 @@ public class AndroidManifestHelper { } /** + * Returns the minSdkVersion defined in the manifest file. + * + * @return A String object with the package or null if any error happened. + */ + public String getMinSdkVersion() { + try { + return mXPath.evaluate("/manifest/uses-sdk/@" //$NON-NLS-1$ + + AndroidXPathFactory.DEFAULT_NS_PREFIX + + ":minSdkVersion", getSource()); //$NON-NLS-1$ + } catch (XPathExpressionException e1) { + // If the XPath failed to evaluate, we'll return null. + } catch (Exception e) { + // if this happens this is due to the resource being out of sync. + // so we must refresh it and do it again + + // for any other kind of exception we must return null as well; + } + + return null; + } + /** * Returns the i-th activity defined in the manifest file. * - * @param manifest The manifest's IFile object. * @param index The 1-based index of the activity to return. - * @param xpath An optional xpath object. If null is provided a new one will - * be created. * @return A String object with the activity or null if any error happened. */ public String getActivityName(int index) { try { - return getActivityNameInternal(index, mXPath, getSource()); + return mXPath.evaluate("/manifest/application/activity[" //$NON-NLS-1$ + + index + + "]/@" //$NON-NLS-1$ + + AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$ + getSource()); } catch (XPathExpressionException e1) { // If the XPath failed to evaluate, we'll return null. } catch (Exception e) { @@ -216,26 +238,4 @@ public class AndroidManifestHelper { return null; } - /** - * Performs the actual XPath evaluation to get the package name. - * Extracted so that we can share it with AndroidManifestFromProject. - */ - private static String getPackageNameInternal(XPath xpath, InputSource source) - throws XPathExpressionException { - return xpath.evaluate("/manifest/@package", source); //$NON-NLS-1$ - } - - /** - * Performs the actual XPath evaluation to get the activity name. - * Extracted so that we can share it with AndroidManifestFromProject. - */ - private static String getActivityNameInternal(int index, XPath xpath, InputSource source) - throws XPathExpressionException { - return xpath.evaluate("/manifest/application/activity[" //$NON-NLS-1$ - + index - + "]/@" //$NON-NLS-1$ - + AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$ - source); - } - } 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 cb98525..2866ce2 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 @@ -319,7 +319,7 @@ public class AndroidManifestParser { * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) */ @Override - public void error(SAXParseException e) throws SAXException { + public void error(SAXParseException e) { if (mMarkErrors) { handleError(e, e.getLineNumber()); } @@ -329,7 +329,7 @@ public class AndroidManifestParser { * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) */ @Override - public void fatalError(SAXParseException e) throws SAXException { + public void fatalError(SAXParseException e) { if (mMarkErrors) { handleError(e, e.getLineNumber()); } @@ -348,7 +348,6 @@ public class AndroidManifestParser { /** * Processes the activity node. * @param attributes the attributes for the activity node. - * @throws CoreException */ private void processActivityNode(Attributes attributes) { // lets get the activity name, and add it to the list @@ -381,7 +380,6 @@ public class AndroidManifestParser { * @param attributes the attributes for the activity node. * @param superClassName the fully qualified name of the super class that this * node is representing - * @throws CoreException */ private void processNode(Attributes attributes, String superClassName) { // lets get the class name, and check it if required. @@ -567,12 +565,11 @@ public class AndroidManifestParser { /** * Parses the manifest file, collects data, and checks for errors. * @param javaProject The java project. Required. - * @param manifestFile + * @param manifestFile The manifest file to parse. * @param errorListener the {@link XmlErrorListener} object being notified of the presence * of errors. Optional. * @return an {@link AndroidManifestParser} or null if the parsing failed. * @throws CoreException - * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} */ public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile, XmlErrorListener errorListener) throws CoreException { @@ -581,12 +578,9 @@ public class AndroidManifestParser { /** * Parses the manifest file, and collects data. - * @param manifestFile - * @param errorListener the {@link XmlErrorListener} object being notified of the presence - * of errors. Optional. + * @param manifestFile The manifest file to parse. * @return an {@link AndroidManifestParser} or null if the parsing failed. * @throws CoreException - * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} */ public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException { return parse(null /* javaProject */, manifestFile, null /* errorListener */, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java index 530c89e..8544b25 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java @@ -39,7 +39,7 @@ public class AndroidXPathFactory { /** * Construct the context with the prefix associated with the android namespace. - * @param prefix the Prefix + * @param androidPrefix the Prefix */ public AndroidNamespaceContext(String androidPrefix) { mAndroidPrefix = androidPrefix; @@ -71,7 +71,7 @@ public class AndroidXPathFactory { /** * Creates a new XPath object, specifying which prefix in the query is used for the * android namespace. - * @param prefix The namespace prefix. + * @param androidPrefix The namespace prefix. */ public static XPath newXPath(String androidPrefix) { XPath xpath = sFactory.newXPath(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java index c69e875..bd8b444 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java @@ -82,11 +82,13 @@ public final class BaseProjectHelper { } /** - * Adds a marker to a file on a specific line + * Adds a marker to a file on a specific line. This methods catches thrown + * {@link CoreException}, and returns null instead. * @param file the file to be marked * @param markerId The id of the marker to add. * @param message the message associated with the mark - * @param lineNumber the line number where to put the mark + * @param lineNumber the line number where to put the mark. If line is < 1, it puts the marker + * on line 1. * @param severity the severity of the marker. * @return the IMarker that was added or null if it failed to add one. */ @@ -96,7 +98,7 @@ public final class BaseProjectHelper { IMarker marker = file.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); - if (lineNumber == -1) { + if (lineNumber < 1) { lineNumber = 1; } marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); @@ -108,7 +110,7 @@ public final class BaseProjectHelper { return marker; } catch (CoreException e) { - AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", + AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", //$NON-NLS-1$ markerId, file.getFullPath()); } @@ -116,7 +118,8 @@ public final class BaseProjectHelper { } /** - * Adds a marker to a resource. + * Adds a marker to a resource. This methods catches thrown {@link CoreException}, + * and returns null instead. * @param resource the file to be marked * @param markerId The id of the marker to add. * @param message the message associated with the mark @@ -129,7 +132,7 @@ public final class BaseProjectHelper { IMarker marker = resource.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); - + // on Windows, when adding a marker to a project, it takes a refresh for the marker // to show. In order to fix this we're forcing a refresh of elements receiving // markers (and only the element, not its children), to force the marker display. @@ -137,13 +140,43 @@ public final class BaseProjectHelper { return marker; } catch (CoreException e) { - AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", + AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", //$NON-NLS-1$ markerId, resource.getFullPath()); } return null; } - + + /** + * Adds a marker to a resource. This method does not catch {@link CoreException} and instead + * throw them. + * @param resource the file to be marked + * @param markerId The id of the marker to add. + * @param message the message associated with the mark + * @param lineNumber the line number where to put the mark if != -1. + * @param severity the severity of the marker. + * @param priority the priority of the marker + * @return the IMarker that was added. + * @throws CoreException + */ + public final static IMarker addMarker(IResource resource, String markerId, + String message, int lineNumber, int severity, int priority) throws CoreException { + IMarker marker = resource.createMarker(markerId); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.SEVERITY, severity); + if (lineNumber != -1) { + marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); + } + marker.setAttribute(IMarker.PRIORITY, priority); + + // on Windows, when adding a marker to a project, it takes a refresh for the marker + // to show. In order to fix this we're forcing a refresh of elements receiving + // markers (and only the element, not its children), to force the marker display. + resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + + return marker; + } + /** * Tests that a class name is valid for usage in the manifest. * <p/> 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 26fbf42..fda55c4 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 @@ -62,6 +62,7 @@ public class XmlErrorHandler extends DefaultHandler { /** * Xml Error call back * @param exception the parsing exception + * @throws SAXException */ @Override public void error(SAXParseException exception) throws SAXException { @@ -71,6 +72,7 @@ public class XmlErrorHandler extends DefaultHandler { /** * Xml Fatal Error call back * @param exception the parsing exception + * @throws SAXException */ @Override public void fatalError(SAXParseException exception) throws SAXException { @@ -80,6 +82,7 @@ public class XmlErrorHandler extends DefaultHandler { /** * Xml Warning call back * @param exception the parsing exception + * @throws SAXException */ @Override public void warning(SAXParseException exception) throws SAXException { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java index 3176c8e..3875e81 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java @@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format; import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; import org.eclipse.core.runtime.IStatus; import org.w3c.dom.Document; @@ -228,7 +229,7 @@ public final class AttrsXmlParser { } mStyleMap.put(name, style); if (lastComment != null) { - style.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); + style.setJavaDoc(parseJavadoc(lastComment.getNodeValue())); } } } @@ -263,14 +264,15 @@ public final class AttrsXmlParser { } if (info != null) { if (lastComment != null) { - info.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); + info.setJavaDoc(parseJavadoc(lastComment.getNodeValue())); + info.setDeprecatedDoc(parseDeprecatedDoc(lastComment.getNodeValue())); } } } } return info; } - + /** * Finds all the attributes for a particular style node, * e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout". @@ -431,16 +433,23 @@ public final class AttrsXmlParser { } /** - * Formats the javadoc. + * Parses the javadoc comment. * Only keeps the first sentence. - * Removes and simplifies links and references. + * <p/> + * This does not remove nor simplify links and references. Such a transformation + * is done later at "display" time in {@link DescriptorsUtils#formatTooltip(String)} and co. */ - private String formatJavadoc(String comment) { + private String parseJavadoc(String comment) { if (comment == null) { return null; } + // sanitize & collapse whitespace comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ + + // Explicitly remove any @deprecated tags since they are handled separately. + comment = comment.replaceAll("(?:\\{@deprecated[^}]*\\}|@deprecated[^@}]*)", ""); + // take everything up to the first dot that is followed by a space or the end of the line. // I love regexps :-). For the curious, the regexp is: // - start of line @@ -456,6 +465,41 @@ public final class AttrsXmlParser { // - followed by a space (?= non-capturing zero-width positive look-ahead) // - anything else is ignored comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ + return comment; } + + + /** + * Parses the javadoc and extract the first @deprecated tag, if any. + * Returns null if there's no @deprecated tag. + * The deprecated tag can be of two forms: + * - {+@deprecated ...text till the next bracket } + * Note: there should be no space or + between { and @. I need one in this comment otherwise + * this method will be tagged as deprecated ;-) + * - @deprecated ...text till the next @tag or end of the comment. + * In both cases the comment can be multi-line. + */ + private String parseDeprecatedDoc(String comment) { + // Skip if we can't even find the tag in the comment. + if (comment == null) { + return null; + } + + // sanitize & collapse whitespace + comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ + + int pos = comment.indexOf("{@deprecated"); + if (pos >= 0) { + comment = comment.substring(pos + 12 /* len of {@deprecated */); + comment = comment.replaceFirst("^([^}]*).*", "$1"); + } else if ((pos = comment.indexOf("@deprecated")) >= 0) { + comment = comment.substring(pos + 11 /* len of @deprecated */); + comment = comment.replaceFirst("^(.*?)(?:@.*|$)", "$1"); + } else { + return null; + } + + return comment.trim(); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java index 6cff62c..efa5981 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java @@ -55,8 +55,10 @@ public class DeclareStyleableInfo { private String[] mEnumValues; /** Values for flag. null for other types. */ private String[] mFlagValues; - /** Short javadoc */ + /** Short javadoc (i.e. the first sentence). */ private String mJavaDoc; + /** Documentation for deprecated attributes. Null if not deprecated. */ + private String mDeprecatedDoc; /** * @param name The XML Name of the attribute @@ -74,6 +76,7 @@ public class DeclareStyleableInfo { mEnumValues = info.mEnumValues; mFlagValues = info.mFlagValues; mJavaDoc = info.mJavaDoc; + mDeprecatedDoc = info.mDeprecatedDoc; } /** Returns the XML Name of the attribute */ @@ -93,10 +96,14 @@ public class DeclareStyleableInfo { public String[] getFlagValues() { return mFlagValues; } - /** Returns a short javadoc */ + /** Returns a short javadoc, .i.e. the first sentence. */ public String getJavaDoc() { return mJavaDoc; } + /** Returns the documentation for deprecated attributes. Null if not deprecated. */ + public String getDeprecatedDoc() { + return mDeprecatedDoc; + } /** Sets the values for enums. null for other types. */ public void setEnumValues(String[] values) { @@ -106,10 +113,14 @@ public class DeclareStyleableInfo { public void setFlagValues(String[] values) { mFlagValues = values; } - /** Sets a short javadoc */ + /** Sets a short javadoc, .i.e. the first sentence. */ public void setJavaDoc(String javaDoc) { mJavaDoc = javaDoc; } + /** Sets the documentation for deprecated attributes. Null if not deprecated. */ + public void setDeprecatedDoc(String deprecatedDoc) { + mDeprecatedDoc = deprecatedDoc; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java index d1b4547..50d3d28 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java @@ -91,7 +91,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { /** * Constructor for AndroidContentAssist - * @param rootElementDescriptors The valid root elements of the XML hierarchy + * @param descriptorId An id for {@link AndroidTargetData#getDescriptorProvider(int)}. + * The Id can be one of {@link AndroidTargetData#DESCRIPTOR_MANIFEST}, + * {@link AndroidTargetData#DESCRIPTOR_LAYOUT}, + * {@link AndroidTargetData#DESCRIPTOR_MENU}, + * or {@link AndroidTargetData#DESCRIPTOR_XML}. + * All other values will throw an {@link IllegalArgumentException} later at runtime. */ public AndroidContentAssist(int descriptorId) { mDescriptorId = descriptorId; @@ -723,7 +728,6 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { /** * Computes (if needed) and returns the root descriptor. - * @return */ private ElementDescriptor getRootDescriptor() { if (mRootDescriptor == null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java index 78e0401..dca7db0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java @@ -722,7 +722,7 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang } /** - * Returns the {@link PlatformData} for the edited file. + * Returns the {@link AndroidTargetData} for the edited file. */ public AndroidTargetData getTargetData() { IProject project = getProject(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java index 2c779b2..70d03a1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java @@ -38,6 +38,7 @@ public abstract class AttributeDescriptor { private String mXmlLocalName; private ElementDescriptor mParent; private final String mNsUri; + private boolean mDeprecated; /** * Creates a new {@link AttributeDescriptor} @@ -70,6 +71,14 @@ public abstract class AttributeDescriptor { return mParent; } + public void setDeprecated(boolean isDeprecated) { + mDeprecated = isDeprecated; + } + + public boolean isDeprecated() { + return mDeprecated; + } + /** * Returns an optional icon for the attribute. * <p/> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java index 05ae922..2729565 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.editors.descriptors; +import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.uimodel.UiAbstractTextAttributeNode; import org.eclipse.jface.viewers.ILabelProvider; @@ -35,6 +36,17 @@ public class AttributeDescriptorLabelProvider implements ILabelProvider { } public Image getImage(Object element) { + if (element instanceof UiAbstractTextAttributeNode) { + UiAbstractTextAttributeNode node = (UiAbstractTextAttributeNode) element; + if (node.getDescriptor().isDeprecated()) { + String v = node.getCurrentValue(); + if (v != null && v.length() > 0) { + IconFactory factory = IconFactory.getInstance(); + return factory.getIcon("warning"); //$NON-NLS-1$ + } + } + } + return null; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java index c84bf57..09f1478 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java @@ -92,8 +92,9 @@ public final class DescriptorsUtils { * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. * See {@link AndroidConstants#NS_RESOURCES} for a common value. * @param infos The array of {@link AttributeInfo} to read and append to attributes - * @param requiredAttributes An optional list of attributes to mark as "required" (i.e. append - * a "*" to their UI name as a hint for the user.) + * @param requiredAttributes An optional set of attributes to mark as "required" (i.e. append + * a "*" to their UI name as a hint for the user.) If not null, must contains + * entries in the form "elem-name/attr-name". Elem-name can be "*". * @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator * can either by a Class<? extends TextAttributeDescriptor> or an instance of * {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor. @@ -101,16 +102,15 @@ public final class DescriptorsUtils { public static void appendAttributes(ArrayList<AttributeDescriptor> attributes, String elementXmlName, String nsUri, AttributeInfo[] infos, - String[] requiredAttributes, + Set<String> requiredAttributes, Map<String, Object> overrides) { for (AttributeInfo info : infos) { boolean required = false; if (requiredAttributes != null) { - for(String attr_name : requiredAttributes) { - if (attr_name.equals(info.getName())) { - required = true; - break; - } + String attr_name = info.getName(); + if (requiredAttributes.contains("*/" + attr_name) || + requiredAttributes.contains(elementXmlName + "/" + attr_name)) { + required = true; } } appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides); @@ -144,7 +144,26 @@ public final class DescriptorsUtils { if (required) { uiName += "*"; //$NON-NLS-1$ } - String tooltip = formatTooltip(info.getJavaDoc()); // tooltip + + String tooltip = null; + String rawTooltip = info.getJavaDoc(); + if (rawTooltip == null) { + rawTooltip = ""; + } + + String deprecated = info.getDeprecatedDoc(); + if (deprecated != null) { + if (rawTooltip.length() > 0) { + rawTooltip += "@@"; //$NON-NLS-1$ insert a break + } + rawTooltip += "* Deprecated"; + if (deprecated.length() != 0) { + rawTooltip += ": " + deprecated; //$NON-NLS-1$ + } + if (deprecated.length() == 0 || !deprecated.endsWith(".")) { //$NON-NLS-1$ + rawTooltip += "."; //$NON-NLS-1$ + } + } // Add the known types to the tooltip Format[] formats_list = info.getFormats(); @@ -154,11 +173,14 @@ public final class DescriptorsUtils { HashSet<Format> formats_set = new HashSet<Format>(); StringBuilder sb = new StringBuilder(); - if (tooltip != null) { - sb.append(tooltip); - sb.append(" "); //$NON-NLS-1$ + if (rawTooltip != null && rawTooltip.length() > 0) { + sb.append(rawTooltip); + sb.append(" "); //$NON-NLS-1$ } - sb.append("["); //$NON-NLS-1$ + if (sb.length() > 0) { + sb.append("@@"); //$NON-NLS-1$ @@ inserts a break before the types + } + sb.append("["); //$NON-NLS-1$ for (int i = 0; i < flen; i++) { Format f = formats_list[i]; formats_set.add(f); @@ -172,19 +194,21 @@ public final class DescriptorsUtils { sb.append("]"); //$NON-NLS-1$ if (required) { - sb.append(". Required."); + sb.append(".@@* "); //$NON-NLS-1$ @@ inserts a break. + sb.append("Required."); } // The extra space at the end makes the tooltip more readable on Windows. sb.append(" "); //$NON-NLS-1$ - tooltip = sb.toString(); + rawTooltip = sb.toString(); + tooltip = formatTooltip(rawTooltip); // Create a specialized attribute if we can if (overrides != null) { for (Entry<String, Object> entry: overrides.entrySet()) { String key = entry.getKey(); - String elements[] = key.split("/"); + String elements[] = key.split("/"); //$NON-NLS-1$ String overrideAttrLocalName = null; if (elements.length < 1) { continue; @@ -193,7 +217,7 @@ public final class DescriptorsUtils { elements = null; } else { overrideAttrLocalName = elements[elements.length - 1]; - elements = elements[0].split(","); + elements = elements[0].split(","); //$NON-NLS-1$ } if (overrideAttrLocalName == null || @@ -204,7 +228,8 @@ public final class DescriptorsUtils { boolean ok_element = elements.length < 1; if (!ok_element) { for (String element : elements) { - if (element.equals("*") || element.equals(elementXmlName)) { + if (element.equals("*") //$NON-NLS-1$ + || element.equals(elementXmlName)) { ok_element = true; break; } @@ -271,8 +296,12 @@ public final class DescriptorsUtils { // By default a simple text field is used if (attr == null) { + if (tooltip == null) { + tooltip = formatTooltip(rawTooltip); + } attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); } + attr.setDeprecated(info.getDeprecatedDoc() != null); attributes.add(attr); } @@ -582,6 +611,8 @@ public final class DescriptorsUtils { Pattern p_code = Pattern.compile("<code>(.+?)</code>(.*)"); //$NON-NLS-1$ // Detects @blah@, used in hard-coded tooltip descriptors Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$ + // Detects a buffer that starts by @@ (request for a break) + Pattern p_break = Pattern.compile("@@(.*)"); //$NON-NLS-1$ // Detects a buffer that starts by @ < or { (one that was not matched above) Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$ // Detects everything till the next potential separator, i.e. @ < or { @@ -616,6 +647,10 @@ public final class DescriptorsUtils { if (text != null) { currentLength += text.length() - 2; } + } else if ((m = p_break.matcher(javadoc)).matches()) { + spans.add(BREAK); + currentLength = 0; + javadoc = m.group(1); } else if ((m = p_open.matcher(javadoc)).matches()) { s = m.group(1); javadoc = m.group(2); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java index 632471d..a9d2b2e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java @@ -95,6 +95,10 @@ public class TextAttributeDescriptor extends AttributeDescriptor implements IPro } public String getCategory() { + if (isDeprecated()) { + return "Deprecated"; + } + ElementDescriptor parent = getParent(); if (parent != null) { return parent.getUiName(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java index c512625..381539b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java @@ -20,7 +20,6 @@ import com.android.layoutlib.api.IXmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -178,7 +177,7 @@ public abstract class BasePullParser implements IXmlPullParser { return mParsingState; } - public int nextTag() throws XmlPullParserException, IOException { + public int nextTag() throws XmlPullParserException { int eventType = next(); if (eventType != START_TAG && eventType != END_TAG) { throw new XmlPullParserException("expected start or end tag", this, null); @@ -186,7 +185,7 @@ public abstract class BasePullParser implements IXmlPullParser { return eventType; } - public String nextText() throws XmlPullParserException, IOException { + public String nextText() throws XmlPullParserException { if (getEventType() != START_TAG) { throw new XmlPullParserException("parser must be on START_TAG to read next text", this, null); @@ -208,7 +207,7 @@ public abstract class BasePullParser implements IXmlPullParser { } } - public int nextToken() throws XmlPullParserException, IOException { + public int nextToken() throws XmlPullParserException { return next(); } 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 87a14ad..a0b30ec 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 @@ -33,8 +33,10 @@ import org.eclipse.core.runtime.IStatus; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.TreeSet; import java.util.Map.Entry; @@ -166,7 +168,13 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { "android", //$NON-NLS-1$ AndroidConstants.NS_RESOURCES); + // -- setup the required attributes overrides -- + + Set<String> required = new HashSet<String>(); + required.add("provider/authorities"); //$NON-NLS-1$ + // -- setup the various attribute format overrides -- + // The key for each override is "element1,element2,.../attr-xml-local-name" or // "*/attr-xml-local-name" to match the attribute in any element. @@ -181,7 +189,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { tooltip); } }); - + overrides.put("*/theme", ThemeAttributeDescriptor.class); //$NON-NLS-1$ overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$ overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$ @@ -212,8 +220,12 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { // -- - inflateElement(manifestMap, overrides, elementDescs, - MANIFEST_ELEMENT, "AndroidManifest"); //$NON-NLS-1$ + inflateElement(manifestMap, + overrides, + required, + elementDescs, + MANIFEST_ELEMENT, + "AndroidManifest"); //$NON-NLS-1$ insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC); sanityCheck(manifestMap, MANIFEST_ELEMENT); @@ -312,16 +324,17 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration. * <p/> * This first creates all the attributes for the given ElementDescriptor. - * It then find all children of the descriptor, inflate them recursively and set them + * It then finds all children of the descriptor, inflate them recursively and set them * as child to this ElementDescriptor. * - * @param styleMap The input styleable map for manifest elements & attributes + * @param styleMap The input styleable map for manifest elements & attributes. * @param overrides A list of attribute overrides (to customize the type of the attribute - * descriptors) + * descriptors). + * @param requiredAttributes Set of attributes to be marked as required. * @param existingElementDescs A map of already created element descriptors, keyed by * XML local name. This is used to use the static elements created initially by this * class, which are referenced directly by editors (so that reloading an SDK won't - * break these references) + * break these references). * @param elemDesc The current {@link ElementDescriptor} to inflate. * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name * will be guessed automatically from the style name. @@ -329,6 +342,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { private void inflateElement( Map<String, DeclareStyleableInfo> styleMap, Map<String, Object> overrides, + Set<String> requiredAttributes, HashMap<String, ElementDescriptor> existingElementDescs, ElementDescriptor elemDesc, String styleName) { @@ -342,7 +356,9 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { DescriptorsUtils.appendAttributes(attrDescs, elemDesc.getXmlLocalName(), AndroidConstants.NS_RESOURCES, - style.getAttributes(), null, overrides); + style.getAttributes(), + requiredAttributes, + overrides); elemDesc.setTooltip(style.getJavaDoc()); elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()])); } @@ -373,7 +389,12 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { } children.add(child); - inflateElement(styleMap, overrides, existingElementDescs, child, childStyleName); + inflateElement(styleMap, + overrides, + requiredAttributes, + existingElementDescs, + child, + childStyleName); } } elemDesc.setChildren(children.toArray(new ElementDescriptor[children.size()])); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java index 1d01260..9a61d17 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java @@ -66,7 +66,7 @@ public final class CountryCodeQualifier extends ResourceQualifier { /** * Returns the folder name segment for the given value. This is equivalent to calling * {@link #toString()} on a {@link CountryCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. + * @param code the value of the qualifier, as returned by {@link #getCode()}. */ public static String getFolderSegment(int code) { if (code != DEFAULT_CODE && code >= 100 && code <=999) { // code is 3 digit.) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java index ce527a4..7e30901 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java @@ -66,7 +66,7 @@ public final class NetworkCodeQualifier extends ResourceQualifier { /** * Returns the folder name segment for the given value. This is equivalent to calling * {@link #toString()} on a {@link NetworkCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. + * @param code the value of the qualifier, as returned by {@link #getCode()}. */ public static String getFolderSegment(int code) { if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java index 0fd05bf..c47bb83 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java @@ -39,7 +39,7 @@ public final class PixelDensityQualifier extends ResourceQualifier { /** * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, * <code>null</code> is returned. - * @param segment the folder segment from which to create a qualifier. + * @param folderSegment the folder segment from which to create a qualifier. * @return a new {@link CountryCodeQualifier} object or <code>null</code> */ public static PixelDensityQualifier getQualifier(String folderSegment) { @@ -66,7 +66,7 @@ public final class PixelDensityQualifier extends ResourceQualifier { /** * Returns the folder name segment for the given value. This is equivalent to calling * {@link #toString()} on a {@link NetworkCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. + * @param value the value of the qualifier, as returned by {@link #getValue()}. */ public static String getFolderSegment(int value) { if (value != DEFAULT_DENSITY) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java index 72438a6..3812791 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java @@ -132,7 +132,7 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou /** * Adds a resource item to the list * @param resType The type of the resource - * @param name The name of the resource. + * @param value The value of the resource. */ public void addResourceValue(String resType, ResourceValue value) { ResourceType type = ResourceType.getEnum(resType); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java index 183af27..8b6c3c1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java @@ -107,7 +107,6 @@ public final class ProjectClassLoader extends ClassLoader { * @param parent the root of the file. * @param segments the segments containing the path of the file * @param index the offset at which to start looking into segments. - * @return * @throws FileNotFoundException */ private File getFile(File parent, String[] segments, int index) @@ -168,8 +167,6 @@ public final class ProjectClassLoader extends ClassLoader { /** * Loads a class from the 3rd party jar present in the project - * @param name - * @return * @throws ClassNotFoundException */ private Class<?> loadClassFromJar(String name) throws ClassNotFoundException { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java index b0881fa..40e4e3b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java @@ -393,8 +393,6 @@ public class ProjectResources implements IResourceRepository { /** * Resolves a compiled resource id of type int[] into the resource name. - * @param id - * @return */ public String resolveResourceValue(int[] id) { if (mStyleableValueToNameMap != null) { @@ -407,9 +405,6 @@ public class ProjectResources implements IResourceRepository { /** * Returns the value of a resource by its type and name. - * @param type - * @param name - * @return */ public Integer getResourceValue(String type, String name) { if (mResourceValueMap != null) { @@ -444,8 +439,7 @@ public class ProjectResources implements IResourceRepository { /** * Returns the list of regions used in the resources with the given language. - * @param currentLanguage the current language the region must be associated with - * @return + * @param currentLanguage the current language the region must be associated with. */ public Set<String> getRegions(String currentLanguage) { Set<String> set = new HashSet<String>(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java index 6db0d94..98f5b39 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java @@ -185,7 +185,7 @@ public final class ResourceFolder extends Resource { /** * Returns the {@link ResourceFile} matching a given name. - * @param file The name of the file to return. + * @param filename The name of the file to return. * @return the {@link ResourceFile} or <code>null</code> if no match was found. */ public ResourceFile getFile(String filename) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java index bd93301..5fc7393 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java @@ -16,21 +16,21 @@ package com.android.ide.eclipse.editors.resources.manager; -import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.sdklib.SdkConstants; /** * Enum representing a type of resource folder. */ public enum ResourceFolderType { - ANIM(AndroidConstants.FD_ANIM), - COLOR(AndroidConstants.FD_COLOR), - DRAWABLE(AndroidConstants.FD_DRAWABLE), - LAYOUT(AndroidConstants.FD_LAYOUT), - MENU(AndroidConstants.FD_MENU), - RAW(AndroidConstants.FD_RAW), - VALUES(AndroidConstants.FD_VALUES), - XML(AndroidConstants.FD_XML); + ANIM(SdkConstants.FD_ANIM), + COLOR(SdkConstants.FD_COLOR), + DRAWABLE(SdkConstants.FD_DRAWABLE), + LAYOUT(SdkConstants.FD_LAYOUT), + MENU(SdkConstants.FD_MENU), + RAW(SdkConstants.FD_RAW), + VALUES(SdkConstants.FD_VALUES), + XML(SdkConstants.FD_XML); private final String mName; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java index 9c5f0fc..6099008 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java @@ -30,6 +30,7 @@ import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; import com.android.ide.eclipse.editors.resources.manager.files.IFileWrapper; import com.android.ide.eclipse.editors.resources.manager.files.IFolderWrapper; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -269,8 +270,7 @@ public final class ResourceManager implements IProjectListener, IFolderListener, /** * Loads and returns the resources for a given {@link IAndroidTarget} - * @param osFilePath the path to the folder containing all the versions of the framework - * resources + * @param androidTarget the target from which to load the framework resources */ public ProjectResources loadFrameworkResources(IAndroidTarget androidTarget) { String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES); @@ -329,7 +329,7 @@ public final class ResourceManager implements IProjectListener, IFolderListener, return; } - IFolder resourceFolder = project.getFolder(AndroidConstants.FD_RESOURCES); + IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES); ProjectResources projectResources = mMap.get(project); if (projectResources == null) { @@ -478,7 +478,7 @@ public final class ResourceManager implements IProjectListener, IFolderListener, * @return true if the path is under /project res/ */ private boolean isInResFolder(IPath path) { - return AndroidConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1)); + return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1)); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java index 1211236..32b1107 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java @@ -115,8 +115,6 @@ public class SingleResourceFile extends ResourceFile { /** * Returns the name of the resources. - * @param type - * @return */ private String getResourceName(ResourceType type) { // get the name from the filename. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java index 0a14214..d99cb13 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.editors.resources.manager.files; import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; import java.io.File; import java.io.FileInputStream; @@ -45,7 +44,7 @@ public class FileWrapper implements IAbstractFile { mFile = file; } - public InputStream getContents() throws CoreException { + public InputStream getContents() { try { return new FileInputStream(mFile); } catch (FileNotFoundException e) { @@ -74,7 +73,7 @@ public class FileWrapper implements IAbstractFile { } if (obj instanceof File) { - return mFile.equals((File)obj); + return mFile.equals(obj); } return super.equals(obj); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java index 8afea33..9ad7460 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java @@ -59,7 +59,7 @@ public class FolderWrapper implements IAbstractFolder { } if (obj instanceof File) { - return mFolder.equals((File)obj); + return mFolder.equals(obj); } return super.equals(obj); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java index 441c65b..f0f5f2d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java @@ -55,7 +55,7 @@ public class IFileWrapper implements IAbstractFile { } if (obj instanceof IFile) { - return mFile.equals((IFile)obj); + return mFile.equals(obj); } return super.equals(obj); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java index 92b5c07..b1fa3ef 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java @@ -61,7 +61,7 @@ public class IFolderWrapper implements IAbstractFolder { } if (obj instanceof IFolder) { - return mFolder.equals((IFolder)obj); + return mFolder.equals(obj); } return super.equals(obj); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java index 772fb52..0729881 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.editors.ui.tree; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.editors.uimodel.UiElementNode; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java index b7dffdd..4a05b1e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java @@ -79,8 +79,7 @@ import java.util.HashMap; * To use this, instantiate somewhere in the UI and then: * <ul> * <li>Use {@link #setConfiguration(String)} or {@link #setConfiguration(FolderConfiguration)}. - * <li>Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)} and - * test it using {@link FolderConfiguration#isValid()}. + * <li>Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)}. * </ul> */ public class ConfigurationSelector extends Composite { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java index b27dd4f..cc643be 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java @@ -30,6 +30,7 @@ import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptor import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -243,7 +244,7 @@ class NewXmlFileCreationPage extends WizardPage { /** Absolute destination folder root, e.g. "/res/" */ private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; /** Relative destination folder root, e.g. "res/" */ - private static String sResFolderRel = AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + private static String sResFolderRel = SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; private IProject mProject; private Text mProjectTextField; @@ -629,7 +630,7 @@ class NewXmlFileCreationPage extends WizardPage { // Disregard this folder selection if it doesn't point to /res/something if (wsFolderPath != null && wsFolderPath.segmentCount() > 1 && - AndroidConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) { + SdkConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) { score += 2; } else { wsFolderPath = null; @@ -1002,7 +1003,7 @@ class NewXmlFileCreationPage extends WizardPage { String fileName = getFileName(); if (fileName == null || fileName.length() == 0) { error = "A destination file name is required."; - } else if (fileName != null && !fileName.endsWith(AndroidConstants.DOT_XML)) { + } else if (!fileName.endsWith(AndroidConstants.DOT_XML)) { error = String.format("The filename must end with %1$s.", AndroidConstants.DOT_XML); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java index d3ff334..6913ce0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.editors.wizards; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.ResourceItem; import com.android.ide.eclipse.common.resources.ResourceType; 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 b4faae6..b43e75f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/AndroidManifest.template @@ -6,4 +6,5 @@ <application android:icon="@drawable/icon" android:label="APPLICATION_NAME"> ACTIVITIES </application> +USES-SDK </manifest> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/uses-sdk.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/uses-sdk.template new file mode 100644 index 0000000..8adae71 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/uses-sdk.template @@ -0,0 +1 @@ + <uses-sdk android:minSdkVersion="MIN_SDK_VERSION" /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/jar/example/Class1.java b/eclipse/plugins/com.android.ide.eclipse.tests/not_source_folder/jar/example/Class1.java index 3cf1027..3cf1027 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/jar/example/Class1.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/not_source_folder/jar/example/Class1.java diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/jar/example/Class2.java b/eclipse/plugins/com.android.ide.eclipse.tests/not_source_folder/jar/example/Class2.java index 4d15c47..4d15c47 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/jar/example/Class2.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/not_source_folder/jar/example/Class2.java diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java index 4700821..5a89d01 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java @@ -60,8 +60,9 @@ public class ProjectHelperTest extends TestCase { ProjectHelper.fixProjectClasspathEntries(javaProject); IClasspathEntry[] fixedEntries = javaProject.getRawClasspath(); - assertEquals(2, fixedEntries.length); + assertEquals(3, fixedEntries.length); assertEquals("Project/src", fixedEntries[0].getPath().toString()); - assertEquals(CONTAINER_ID, fixedEntries[1].getPath().toString()); + assertEquals(OLD_CONTAINER_ID, fixedEntries[1].getPath().toString()); + assertEquals(CONTAINER_ID, fixedEntries[2].getPath().toString()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java index f3d9b79..872938b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java @@ -45,6 +45,8 @@ public class AndroidJarLoaderTest extends TestCase { @Override public void tearDown() throws Exception { + mFrameworkClassLoader = null; + System.gc(); } /** Preloads classes. They should load just fine. */ @@ -74,8 +76,9 @@ public class AndroidJarLoaderTest extends TestCase { Class<?> c = _findClass(mFrameworkClassLoader, "jar.example.Class2"); //$NON-NLS-1$ assertEquals("jar.example.Class2", c.getName()); //$NON-NLS-1$ HashMap<String, Class<?>> map = getPrivateClassCache(); + assertTrue(map.containsKey("jar.example.Class1")); //$NON-NLS-1$ assertTrue(map.containsKey("jar.example.Class2")); //$NON-NLS-1$ - assertEquals(1, map.size()); + assertEquals(2, map.size()); } /** call the protected method findClass */ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/resources/AttrsXmlParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/resources/AttrsXmlParserTest.java index 76ebfc3..8338453 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/resources/AttrsXmlParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/resources/AttrsXmlParserTest.java @@ -94,6 +94,33 @@ public class AttrsXmlParserTest extends TestCase { assertEquals(Integer.valueOf(0), valueMap.get("horizontal")); assertEquals(Integer.valueOf(1), valueMap.get("vertical")); } + + public final void testDeprecated() throws Exception { + mParser.preload(); + + DeclareStyleableInfo dep = mParser.getDeclareStyleableList().get("DeprecatedTest"); + assertNotNull(dep); + + AttributeInfo[] attrs = dep.getAttributes(); + assertEquals(4, attrs.length); + + assertEquals("deprecated-inline", attrs[0].getName()); + assertEquals("In-line deprecated.", attrs[0].getDeprecatedDoc()); + assertEquals("Deprecated comments using delimiters.", attrs[0].getJavaDoc()); + + assertEquals("deprecated-multiline", attrs[1].getName()); + assertEquals("Multi-line version of deprecated that works till the next tag.", + attrs[1].getDeprecatedDoc()); + assertEquals("Deprecated comments on their own line.", attrs[1].getJavaDoc()); + + assertEquals("deprecated-not", attrs[2].getName()); + assertEquals(null, attrs[2].getDeprecatedDoc()); + assertEquals("This attribute is not deprecated.", attrs[2].getJavaDoc()); + + assertEquals("deprecated-no-javadoc", attrs[3].getName()); + assertEquals("There is no other javadoc here.", attrs[3].getDeprecatedDoc()); + assertEquals("", attrs[3].getJavaDoc()); + } //---- access to private methods diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/descriptors/DescriptorsUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/descriptors/DescriptorsUtilsTest.java index 5506459..69c3ed8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/descriptors/DescriptorsUtilsTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/descriptors/DescriptorsUtilsTest.java @@ -99,28 +99,26 @@ public class DescriptorsUtilsTest extends TestCase { ElementDescriptor desc = new ElementDescriptor("application"); desc.setSdkUrl(DescriptorsUtils.MANIFEST_SDK_URL + "TagApplication"); String docBaseUrl = "http://base"; - assertEquals("<form><p></p></form>", DescriptorsUtils.formatFormText("", desc, docBaseUrl)); + assertEquals("<form><li style=\"image\" value=\"image\"></li></form>", DescriptorsUtils.formatFormText("", desc, docBaseUrl)); - assertEquals("<form><p><a href=\"http://base/reference/android/R.styleable.html#TagApplication\">application</a></p></form>", + assertEquals("<form><li style=\"image\" value=\"image\"><a href=\"http://base/reference/android/R.styleable.html#TagApplication\">application</a></li></form>", DescriptorsUtils.formatFormText( "<code>application</code>", desc, docBaseUrl)); - assertEquals("<form><p><b>android.content.Intent</b></p></form>", + assertEquals("<form><li style=\"image\" value=\"image\"><b>android.content.Intent</b></li></form>", DescriptorsUtils.formatFormText( "{@link android.content.Intent}", desc, docBaseUrl)); - assertEquals("<form><p><a href=\"http://base/reference/android/R.styleable.html#AndroidManifestPermission\">AndroidManifestPermission</a></p></form>", + assertEquals("<form><li style=\"image\" value=\"image\"><a href=\"http://base/reference/android/R.styleable.html#AndroidManifestPermission\">AndroidManifestPermission</a></li></form>", DescriptorsUtils.formatFormText( "{@link #AndroidManifestPermission}", desc, docBaseUrl)); - assertEquals("<form><p><a href=\"http://base/reference/android/R.styleable.html#AndroidManifestPermission\">\"permission\"</a></p></form>", + assertEquals("<form><li style=\"image\" value=\"image\"><a href=\"http://base/reference/android/R.styleable.html#AndroidManifestPermission\">\"permission\"</a></li></form>", DescriptorsUtils.formatFormText( "{@link #AndroidManifestPermission <permission>}", desc, docBaseUrl)); - } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifierTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifierTest.java index 9e01081..28f7871 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifierTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifierTest.java @@ -54,11 +54,11 @@ public class TextInputMethodQualifierTest extends TestCase { } public void testNoKey() { - assertEquals(true, timq.checkAndSet("nokey", config)); //$NON-NLS-1$ + assertEquals(true, timq.checkAndSet("nokeys", config)); //$NON-NLS-1$ assertTrue(config.getTextInputMethodQualifier() != null); assertEquals(TextInputMethodQualifier.TextInputMethod.NOKEY, config.getTextInputMethodQualifier().getValue()); - assertEquals("nokey", config.getTextInputMethodQualifier().toString()); //$NON-NLS-1$ + assertEquals("nokeys", config.getTextInputMethodQualifier().toString()); //$NON-NLS-1$ } public void testFailures() { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java index 0c1a508..25a86c3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java @@ -41,7 +41,7 @@ public class ConfigMatchTest extends TestCase { private static final String MISC2_FILENAME = "bar.xml"; //$NON-NLS-1$ private ProjectResources mResources; - private ArrayList<ResourceQualifier> mQualifierList; + private ResourceQualifier[] mQualifierList; private FolderConfiguration config4; private FolderConfiguration config3; private FolderConfiguration config2; @@ -60,7 +60,7 @@ public class ConfigMatchTest extends TestCase { qualifierListField.setAccessible(true); // get the actual list. - mQualifierList = (ArrayList<ResourceQualifier>)qualifierListField.get(manager); + mQualifierList = (ResourceQualifier[])qualifierListField.get(manager); // create the project resources. mResources = new ProjectResources(false /* isFrameworkRepository */); @@ -191,10 +191,10 @@ public class ConfigMatchTest extends TestCase { FolderConfiguration config = new FolderConfiguration(); // those must be of the same length - assertEquals(qualifierValues.length, mQualifierList.size()); + assertEquals(qualifierValues.length, mQualifierList.length); int index = 0; - + for (ResourceQualifier qualifier : mQualifierList) { String value = qualifierValues[index++]; if (value != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/mock_attrs.xml b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/mock_attrs.xml index e51604c..aa9a1f7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/mock_attrs.xml +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/mock_attrs.xml @@ -18,7 +18,7 @@ --> <resources> <!-- WARNING !!! THIS IS A MOCK FILE. DO NOT USE FOR DOCUMENTATION PURPOSES. - This file has been trimmed down to only extract a number of interest cases + This file has been trimmed down to only extract a number of interesting cases for unit tests. What this contains: @@ -314,5 +314,27 @@ <attr name="collapseColumns" format="string" /> </declare-styleable> + <!-- Test for deprecated attributes. --> + <declare-styleable name="DeprecatedTest"> + <!-- Deprecated comments using delimiters. + Ignored. {@deprecated In-line deprecated.} {@ignore Ignored}. + --> + <attr name="deprecated-inline" /> + + <!-- Deprecated comments on their own line. + @deprecated Multi-line version of deprecated + that works till the next tag. + @ignore This tag must be ignored + --> + <attr name="deprecated-multiline" /> + + <!-- This attribute is not deprecated. --> + <attr name="deprecated-not" /> + + <!-- {@deprecated There is no other javadoc here. } --> + <attr name="deprecated-no-javadoc" format="boolean" /> + + </declare-styleable> + </resources> diff --git a/eclipse/scripts/create_test_symlinks.sh b/eclipse/scripts/create_test_symlinks.sh index 1479e04..931dce8 100755 --- a/eclipse/scripts/create_test_symlinks.sh +++ b/eclipse/scripts/create_test_symlinks.sh @@ -11,32 +11,38 @@ function back() { echo $1 | sed 's@[^/]*@..@g' } +HOST=`uname` +if [ "${HOST:0:6}" == "CYGWIN" ]; then + # We can't use symlinks under Cygwin + function cpdir() { # $1=dest $2=source + rsync -avW --delete-after $2 $1 + } + +else + # For all other systems which support symlinks + function cpdir() { # $1=dest $2=source + ln -svf `back $1`/$2 $1 + } +fi + BASE="development/tools/eclipse/plugins/com.android.ide.eclipse.tests" DEST=$BASE BACK=`back $DEST` - HOST=`uname` if [ "$HOST" == "Linux" ]; then - DIR="ln -svf" ln -svf $BACK/out/host/linux-x86/framework/kxml2-2.3.0.jar "$DEST/" elif [ "$HOST" == "Darwin" ]; then - DIR="ln -svf" ln -svf $BACK/out/host/darwin-x86/framework/kxml2-2.3.0.jar "$DEST/" elif [ "${HOST:0:6}" == "CYGWIN" ]; then - DIR="rsync -avW --delete-after" - JAR="kxml2-2.3.0.jar" - if [ ! -f "$DEST/$JAR" ]; then - # Get the jar from ADT if we can, otherwise download it. - if [ -f "$DEST/../com.android.ide.eclipse.adt/$JAR" ]; then - cp "$DEST/../com.android.ide.eclipse.adt/$JAR" "$DEST/$JAR" - else - wget -O "$DEST/$JAR" "http://internap.dl.sourceforge.net/sourceforge/kxml/$JAR" - fi - chmod a+rx "$DEST/$JAR" + + if [ ! -f "$DEST/kxml2-2.3.0.jar" ]; then + cp -v "prebuilt/common/kxml2/kxml2-2.3.0.jar" "$DEST/" + chmod -v a+rx "$DEST"/*.jar fi + else echo "Unsupported platform ($HOST). Nothing done." fi @@ -44,5 +50,5 @@ fi # create link to ddmlib tests DEST=$BASE/unittests/com/android BACK=`back $DEST` -$DIR $BACK/development/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib $DEST/ +cpdir $DEST development/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib diff --git a/emulator/qtools/dmtrace.cpp b/emulator/qtools/dmtrace.cpp index a27193a..6d9250a 100644 --- a/emulator/qtools/dmtrace.cpp +++ b/emulator/qtools/dmtrace.cpp @@ -2,6 +2,7 @@ #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #include <inttypes.h> #include <string.h> #include "dmtrace.h" diff --git a/scripts/android_rules.xml b/scripts/android_rules.xml index bed5f24..1331696 100644 --- a/scripts/android_rules.xml +++ b/scripts/android_rules.xml @@ -1,112 +1,73 @@ <?xml version="1.0" ?> <project name="android_rules" default="debug"> - <!-- No user servicable parts below. --> - - <property name="outdir-main" value="../${outdir}" /> - - <property name="android-tools" value="${sdk-folder}/tools" /> - <property name="android-platform" value="${sdk-folder}/platforms/${target-folder}" /> - <property name="android-framework" value="${android-platform}/framework.aidl" /> - <property name="android-jar" value="${android-platform}/android.jar" /> + <property name="android-tools" value="${sdk-location}/tools" /> <!-- Input directories --> - <property name="resource-dir" value="res" /> - <property name="asset-dir" value="assets" /> - <property name="srcdir" value="src" /> - <condition property="srcdir-ospath" - value="${basedir}\${srcdir}" - else="${basedir}/${srcdir}" > - <os family="windows"/> - </condition> + <property name="source-folder" value="src" /> + <property name="resource-folder" value="res" /> + <property name="asset-folder" value="assets" /> + <property name="source-location" value="${basedir}/${source-folder}" /> + <available file="${basedir}/${asset-folder}" property="has.asset.folder"/> <!-- folder for the 3rd party java libraries --> <property name="external-libs" value="libs" /> - <condition property="external-libs-ospath" - value="${basedir}\${external-libs}" - else="${basedir}/${external-libs}" > - <os family="windows"/> - </condition> + <property name="external-libs-location" value="${basedir}/${external-libs}"/> <!-- folder for the native libraries --> <property name="native-libs" value="libs" /> - <condition property="native-libs-ospath" - value="${basedir}\${native-libs}" - else="${basedir}/${native-libs}" > - <os family="windows"/> - </condition> + <property name="native-libs-location" value="${basedir}/${native-libs}"/> <!-- Output directories --> - <property name="outdir-classes" value="${outdir}/classes" /> - <condition property="outdir-classes-ospath" - value="${basedir}\${outdir-classes}" - else="${basedir}/${outdir-classes}" > - <os family="windows"/> - </condition> - <condition property="outdir-main-classes" - value="${outdir-main}/classes"> - <available file="${outdir-main}/classes" - type="dir"/> - </condition> + <property name="out-folder" value="bin" /> + <property name="out-classes" value="${out-folder}/classes" /> + <property name="out-classes-location" value="${basedir}/${out-classes}"/> + <!-- out folders for a parent project if this project is an instrumentation project --> + <property name="main-out-folder" value="../${out-folder}" /> + <property name="main-out-classes" value="${main-out-folder}/classes"/> <!-- Create R.java in the source directory --> - <property name="outdir-r" value="src" /> + <property name="r-folder" value="${source-folder}" /> <!-- Intermediate files --> <property name="dex-file" value="classes.dex" /> - <property name="intermediate-dex" value="${outdir}/${dex-file}" /> - <condition property="intermediate-dex-ospath" + <property name="intermediate-dex" value="${out-folder}/${dex-file}" /> + <!-- dx does not properly support incorrect / or \ based on the platform + and Ant cannot convert them because the parameter is not a valid path. + Because of this we have to compute different paths depending on the platform. --> + <condition property="intermediate-dex-location" value="${basedir}\${intermediate-dex}" else="${basedir}/${intermediate-dex}" > <os family="windows"/> </condition> <!-- The final package file to generate --> - <property name="resources-package" value="${outdir}/${ant.project.name}.ap_" /> - <condition property="resources-package-ospath" - value="${basedir}\${resources-package}" - else="${basedir}/${resources-package}" > - <os family="windows"/> - </condition> + <property name="resources-package" value="${out-folder}/${ant.project.name}.ap_"/> + <property name="resources-package-location" value="${basedir}/${resources-package}"/> - <property name="out-debug-package" value="${outdir}/${ant.project.name}-debug.apk" /> - <condition property="out-debug-package-ospath" - value="${basedir}\${out-debug-package}" - else="${basedir}/${out-debug-package}" > - <os family="windows"/> - </condition> + <property name="out-debug-package" value="${out-folder}/${ant.project.name}-debug.apk"/> + <property name="out-debug-package-location" value="${basedir}/${out-debug-package}"/> - <property name="out-unsigned-package" value="${outdir}/${ant.project.name}-unsigned.apk" /> - <condition property="out-unsigned-package-ospath" - value="${basedir}\${out-unsigned-package}" - else="${basedir}/${out-unsigned-package}" > - <os family="windows"/> - </condition> + <property name="out-unsigned-package" value="${out-folder}/${ant.project.name}-unsigned.apk"/> + <property name="out-unsigned-package-location" value="${basedir}/${out-unsigned-package}"/> <!-- Tools --> - <condition property="aapt" value="${android-tools}/aapt.exe" else="${android-tools}/aapt" > - <os family="windows"/> - </condition> - <condition property="aidl" value="${android-tools}/aidl.exe" else="${android-tools}/aidl" > - <os family="windows"/> - </condition> - <condition property="adb" value="${android-tools}/adb.exe" else="${android-tools}/adb" > - <os family="windows"/> - </condition> - <condition property="dx" value="${android-tools}/dx.bat" else="${android-tools}/dx" > - <os family="windows"/> - </condition> - <condition property="apk-builder" value="${android-tools}/apkbuilder.bat" else="${android-tools}/apkbuilder" > - <os family="windows"/> - </condition> + <condition property="exe" value="exe" else=""><os family="windows"/></condition> + <condition property="bat" value="bat" else=""><os family="windows"/></condition> - <!-- Rules --> + <property name="aapt" value="${android-tools}/aapt${exe}"/> + <property name="aidl" value="${android-tools}/aidl${exe}"/> + <property name="adb" value="${android-tools}/adb${exe}"/> + <property name="dx" value="${android-tools}/dx${bat}"/> + <property name="apk-builder" value="${android-tools}/apkbuilder${bat}"/> + + <!-- rules --> <!-- Create the output directories if they don't exist yet. --> <target name="dirs"> <echo>Creating output directories if needed...</echo> - <mkdir dir="${outdir}" /> - <mkdir dir="${outdir-classes}" /> + <mkdir dir="${out-folder}" /> + <mkdir dir="${out-classes}" /> </target> <!-- Generate the R.java file for this project's resources. --> @@ -116,13 +77,13 @@ <arg value="package" /> <arg value="-m" /> <arg value="-J" /> - <arg value="${outdir-r}" /> + <arg path="${r-folder}" /> <arg value="-M" /> - <arg value="AndroidManifest.xml" /> + <arg path="AndroidManifest.xml" /> <arg value="-S" /> - <arg value="${resource-dir}" /> + <arg path="${resource-folder}" /> <arg value="-I" /> - <arg value="${android-jar}" /> + <arg path="${android-jar}" /> </exec> </target> @@ -130,9 +91,9 @@ <target name="aidl" depends="dirs"> <echo>Compiling aidl files into Java classes...</echo> <apply executable="${aidl}" failonerror="true"> - <arg value="-p${android-framework}" /> - <arg value="-I${srcdir}" /> - <fileset dir="${srcdir}"> + <arg value="-p${android-aidl}" /> + <arg value="-I${source-folder}" /> + <fileset dir="${source-folder}"> <include name="**/*.aidl"/> </fileset> </apply> @@ -141,113 +102,102 @@ <!-- Compile this project's .java files into .class files. --> <target name="compile" depends="dirs, resource-src, aidl"> <javac encoding="ascii" target="1.5" debug="true" extdirs="" - srcdir="${srcdir}" - destdir="${outdir-classes}" + srcdir="${source-folder}" + destdir="${out-classes}" bootclasspath="${android-jar}"> <classpath> <fileset dir="${external-libs}" includes="*.jar"/> - <pathelement path="${outdir-main-classes}"/> + <pathelement path="${main-out-classes}"/> </classpath> </javac> </target> <!-- Convert this project's .class files into .dex files. --> <target name="dex" depends="compile"> - <echo>Converting compiled files and external libraries into ${outdir}/${dex-file}...</echo> + <echo>Converting compiled files and external libraries into ${out-folder}/${dex-file}...</echo> <apply executable="${dx}" failonerror="true" parallel="true"> <arg value="--dex" /> - <arg value="--output=${intermediate-dex-ospath}" /> - <arg path="${outdir-classes-ospath}" /> + <arg value="--output=${intermediate-dex-location}" /> + <arg path="${out-classes-location}" /> <fileset dir="${external-libs}" includes="*.jar"/> </apply> </target> <!-- Put the project's resources into the output package file. --> - <target name="package-res-and-assets"> + <target name="package-res-and-assets" if="has.asset.folder"> <echo>Packaging resources and assets...</echo> <exec executable="${aapt}" failonerror="true"> <arg value="package" /> <arg value="-f" /> <arg value="-M" /> - <arg value="AndroidManifest.xml" /> + <arg path="AndroidManifest.xml" /> <arg value="-S" /> - <arg value="${resource-dir}" /> + <arg path="${resource-folder}" /> <arg value="-A" /> - <arg value="${asset-dir}" /> + <arg path="${asset-folder}" /> <arg value="-I" /> - <arg value="${android-jar}" /> + <arg path="${android-jar}" /> <arg value="-F" /> <arg value="${resources-package}" /> </exec> </target> - <!-- Same as package-res-and-assets, but without "-A ${asset-dir}" --> - <target name="package-res-no-assets"> + <!-- Same as package-res-and-assets, but without "-A ${asset-folder}" --> + <target name="package-res-no-assets" unless="has.asset.folder"> <echo>Packaging resources...</echo> <exec executable="${aapt}" failonerror="true"> <arg value="package" /> <arg value="-f" /> <arg value="-M" /> - <arg value="AndroidManifest.xml" /> + <arg path="AndroidManifest.xml" /> <arg value="-S" /> - <arg value="${resource-dir}" /> + <arg path="${resource-folder}" /> <!-- No assets directory --> <arg value="-I" /> - <arg value="${android-jar}" /> + <arg path="${android-jar}" /> <arg value="-F" /> - <arg value="${resources-package}" /> + <arg path="${resources-package}" /> </exec> </target> - <!-- Invoke the proper target depending on whether or not - an assets directory is present. --> - <!-- TODO: find a nicer way to include the "-A ${asset-dir}" argument - only when the assets dir exists. --> - <target name="package-res"> - <available file="${asset-dir}" type="dir" - property="res-target" value="and-assets" /> - <property name="res-target" value="no-assets" /> - <antcall target="package-res-${res-target}" /> - </target> - <!-- Package the application and sign it with a debug key. This is the default target when building. It is used for debug. --> - <target name="debug" depends="dex, package-res"> + <target name="debug" depends="dex, package-res-and-assets, package-res-no-assets"> <echo>Packaging ${out-debug-package}, and signing it with a debug key...</echo> <exec executable="${apk-builder}" failonerror="true"> - <arg value="${out-debug-package-ospath}" /> + <arg value="${out-debug-package-location}" /> <arg value="-z" /> - <arg value="${resources-package-ospath}" /> + <arg path="${resources-package-location}" /> <arg value="-f" /> - <arg value="${intermediate-dex-ospath}" /> + <arg path="${intermediate-dex-location}" /> <arg value="-rf" /> - <arg value="${srcdir-ospath}" /> + <arg path="${source-location}" /> <arg value="-rj" /> - <arg value="${external-libs-ospath}" /> + <arg path="${external-libs-location}" /> <arg value="-nf" /> - <arg value="${native-libs-ospath}" /> + <arg path="${native-libs-location}" /> </exec> </target> <!-- Package the application without signing it. This allows for the application to be signed later with an official publishing key. --> - <target name="release" depends="dex, package-res"> + <target name="release" depends="dex, package-res-and-assets, package-res-no-assets"> <echo>Packaging ${out-unsigned-package} for release...</echo> <exec executable="${apk-builder}" failonerror="true"> - <arg value="${out-unsigned-package-ospath}" /> + <arg value="${out-unsigned-package-location}" /> <arg value="-u" /> <arg value="-z" /> - <arg value="${resources-package-ospath}" /> + <arg path="${resources-package-location}" /> <arg value="-f" /> - <arg value="${intermediate-dex-ospath}" /> + <arg path="${intermediate-dex-location}" /> <arg value="-rf" /> - <arg value="${srcdir-ospath}" /> + <arg path="${source-location}" /> <arg value="-rj" /> - <arg value="${external-libs-ospath}" /> + <arg path="${external-libs-location}" /> <arg value="-nf" /> - <arg value="${native-libs-ospath}" /> + <arg path="${native-libs-location}" /> </exec> - <echo>It will need to be signed with jarsigner before being published.</echo> + <echo>It will need to be signed with jarsigner before it is published.</echo> </target> <!-- Install the package on the default emulator --> @@ -255,7 +205,7 @@ <echo>Installing ${out-debug-package} onto default emulator...</echo> <exec executable="${adb}" failonerror="true"> <arg value="install" /> - <arg value="${out-debug-package}" /> + <arg path="${out-debug-package}" /> </exec> </target> @@ -264,7 +214,7 @@ <exec executable="${adb}" failonerror="true"> <arg value="install" /> <arg value="-r" /> - <arg value="${out-debug-package}" /> + <arg path="${out-debug-package}" /> </exec> </target> @@ -273,8 +223,25 @@ <echo>Uninstalling ${application-package} from the default emulator...</echo> <exec executable="${adb}" failonerror="true"> <arg value="uninstall" /> - <arg value="${application-package}" /> + <arg path="${application-package}" /> </exec> </target> - + + <target name="help"> + <!-- displays starts at col 13 + |13 80| --> + <echo>Android Ant Build. Available targets:</echo> + <echo> help: Displays this help.</echo> + <echo> debug: Builds the application and sign it with a debug key.</echo> + <echo> release: Builds the application. The generated apk file must be</echo> + <echo> signed before it is published.</echo> + <echo> install: Installs the debug package onto a running emulator or</echo> + <echo> device. This can only be used if the application has </echo> + <echo> not yet been installed.</echo> + <echo> reinstall: Installs the debug package on a running emulator or</echo> + <echo> device that already has the application.</echo> + <echo> The signatures must match.</echo> + <echo> uninstall: uninstall the application from a running emulator or</echo> + <echo> device.</echo> + </target> </project> diff --git a/scripts/build.template b/scripts/build.template index f04f1d8..c1afef8 100644 --- a/scripts/build.template +++ b/scripts/build.template @@ -1,30 +1,51 @@ <?xml version="1.0" ?> -<project name="ACTIVITY_NAME" default="debug"> +<project name="ACTIVITY_NAME" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contain the path to the SDK. It should *NOT* be checked in in Version + Control Systems. --> + <property file="local.properties"/> <!-- The build.properties file can be created by you and is never touched - by activitycreator. If you want to manually set properties, this is - the best place to set them. --> - <property file="build.properties"/> + by the 'android' tool. This is the place to change some of the default property values + used by the Ant rules. + Here are some properties you may want to change/update: + + application-package + the name of your application package as defined in the manifest. Used by the + 'uninstall' rule. + source-folder + the name of the source folder. Default is 'src'. + out-folder + the name of the output folder. Default is 'bin'. - <!-- The default.properties file is created and updated by activitycreator. - It will set any properties not already defined by build.properties. --> - <property file="default.properties"/> + Properties related to the SDK location or the project target should be updated + using the 'android' tool with the 'update' action. - <!-- ************************************************************************************* --> - <!-- These settings were written by activitycreator. - Do not change them unless you really know what you are doing. --> + This file is an integral part of the build system for your application and + should be checked in in Version Control Systems. - <!-- Application Package Name --> - <property name="application-package" value="PACKAGE" /> + --> + <property file="build.properties"/> - <!-- The intermediates directory, Eclipse uses "bin" - for its own output, so we do the same. --> - <property name="outdir" value="bin" /> - - <!-- ************************************************************************************* --> - <!-- Import the default Android build rules. - This requires ant 1.6.0 or above. --> + <!-- The default.properties file is created and updated by the 'android' tool, as well + as ADT. + This file is an integral part of the build system for your application and + should be checked in in Version Control Systems. --> + <property file="default.properties"/> - <import file="${sdk-folder}/tools/lib/android_rules.xml" /> + <!-- Custom Android task to deal with the project target, and import the proper rules. + This requires ant 1.6.0 or above. --> + <path id="android.antlibs"> + <pathelement path="${sdk-location}/tools/lib/anttasks.jar" /> + <pathelement path="${sdk-location}/tools/lib/sdklib.jar" /> + <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" /> + </path> + + <taskdef name="androidinit" classname="com.android.ant.AndroidInitTask" + classpathref="android.antlibs"/> + <!-- Class the Android Init task that will import the proper rule file containing + all the Ant targets --> + <androidinit /> </project> diff --git a/scripts/default.properties.template b/scripts/default.properties.template deleted file mode 100644 index 63df494..0000000 --- a/scripts/default.properties.template +++ /dev/null @@ -1,18 +0,0 @@ -# This file is automatically generated by activitycreator. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# Instead customize values in a "build.properties" file. - -# location of the SDK -sdk-folder=ANDROID_SDK_FOLDER - -# target mode. Value can be "platform" or "add-on" -target-mode=TARGET_MODE - -# target API level. -target-api=TARGET_API - -# target name, if target-mode=add-on -target-name=TARGET_NAME - -# target platform. This is either the target itself or the platform the add-on is based on. -target-folder=TARGET_FOLDER diff --git a/sdkmanager/app/.classpath b/sdkmanager/app/.classpath index 45c59d3..cbd9d37 100644 --- a/sdkmanager/app/.classpath +++ b/sdkmanager/app/.classpath @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java new file mode 100644 index 0000000..ef3d0ee --- /dev/null +++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdkmanager; + +import java.util.HashMap; +import java.util.Map.Entry; + +/** + * Parses the command-line and stores flags needed or requested. + * <p/> + * This is a base class. To be useful you want to: + * <ul> + * <li>override it. + * <li>pass an action array to the constructor. + * <li>define flags for your actions. + * </ul> + * <p/> + * To use, call {@link #parseArgs(String[])} and then call {@link #getValue(String, String)}. + */ +public class CommandLineProcessor { + + /** Internal action name for all global flags. */ + public final static String GLOBAL_FLAG = "global"; + /** Internal action name for internally hidden flags. + * This is currently used to store the requested action name. */ + public final static String INTERNAL_FLAG = "internal"; + + /** The global help flag. */ + public static final String KEY_HELP = "help"; + /** The global verbose flag. */ + public static final String KEY_VERBOSE = "verbose"; + /** The internal action flag. */ + public static final String KEY_ACTION = "action"; + + /** List of available actions. + * <p/> + * Each entry must be a 2-string array with first the action name and then + * a description. + */ + private final String[][] mActions; + /** The hash of all defined arguments. + * <p/> + * The key is a string "action/longName". + */ + private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>(); + + public CommandLineProcessor(String[][] actions) { + mActions = actions; + + define(MODE.STRING, false, INTERNAL_FLAG, null, KEY_ACTION, "Selected Action", null); + + define(MODE.BOOLEAN, false, GLOBAL_FLAG, "v", KEY_VERBOSE, "Verbose mode", false); + define(MODE.BOOLEAN, false, GLOBAL_FLAG, "h", KEY_HELP, "This help", false); + } + + //------------------ + // Helpers to get flags values + + /** Helper that returns true if --verbose was requested. */ + public boolean isVerbose() { + return ((Boolean) getValue(GLOBAL_FLAG, KEY_VERBOSE)).booleanValue(); + } + + /** Helper that returns true if --help was requested. */ + public boolean isHelpRequested() { + return ((Boolean) getValue(GLOBAL_FLAG, KEY_HELP)).booleanValue(); + } + + /** Helper that returns the requested action name. */ + public String getActionRequested() { + return (String) getValue(INTERNAL_FLAG, KEY_ACTION); + } + + //------------------ + + /** + * Raw access to parsed parameter values. + * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG} + * @param longFlagName The long flag name for the given action. + * @return The current value object stored in the parameter, which depends on the argument mode. + */ + public Object getValue(String action, String longFlagName) { + String key = action + "/" + longFlagName; + Arg arg = mArguments.get(key); + return arg.getCurrentValue(); + } + + /** + * Internal setter for raw parameter value. + * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG} + * @param longFlagName The long flag name for the given action. + * @param value The new current value object stored in the parameter, which depends on the + * argument mode. + */ + protected void setValue(String action, String longFlagName, Object value) { + String key = action + "/" + longFlagName; + Arg arg = mArguments.get(key); + arg.setCurrentValue(value); + } + + /** + * Parses the command-line arguments. + * <p/> + * This method will exit and not return if a parsing error arise. + * + * @param args The arguments typically received by a main method. + */ + public void parseArgs(String[] args) { + String needsHelp = null; + String action = null; + + int n = args.length; + for (int i = 0; i < n; i++) { + Arg arg = null; + String a = args[i]; + if (a.startsWith("--")) { + arg = findLongArg(action, a.substring(2)); + } else if (a.startsWith("-")) { + arg = findShortArg(action, a.substring(1)); + } + + // Not a keyword and we don't have an action yet, this should be an action + if (arg == null && action == null) { + + if (a.startsWith("-")) { + // Got a keyword but not valid for global flags + needsHelp = String.format( + "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the action name?", + a, action); + break; + } + + for (String[] actionDesc : mActions) { + if (actionDesc[0].equals(a)) { + action = a; + break; + } + } + + if (action == null) { + needsHelp = String.format( + "Expected action name after global parameters but found %1$s instead.", + a); + break; + } + } else if (arg == null && action != null) { + // Got a keyword but not valid for the current action + needsHelp = String.format( + "Flag '%1$s' is not valid for action '%2$s'.", + a, action); + break; + + } else if (arg != null) { + // Process keyword + String error = null; + if (arg.getMode().needsExtra()) { + if (++i >= n) { + needsHelp = String.format("Missing argument for flag %1$s.", a); + break; + } + + error = arg.getMode().process(arg, args[i]); + } else { + error = arg.getMode().process(arg, null); + + // If we just toggled help, we want to exit now without printing any error. + // We do this test here only when a Boolean flag is toggled since booleans + // are the only flags that don't take parameters and help is a boolean. + if (isHelpRequested()) { + printHelpAndExit(null); + // The call above should terminate however in unit tests we override + // it so we still need to return here. + return; + } + } + + if (error != null) { + needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error); + break; + } + } + } + + if (needsHelp == null) { + if (action == null) { + needsHelp = "Missing action name."; + } else { + // Validate that all mandatory arguments are non-null for this action + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getAction().equals(action)) { + if (arg.isMandatory() && arg.getCurrentValue() == null) { + needsHelp = String.format("The parameter --%1$s must be defined for action '%2$s'", + arg.getLongArg(), + action); + break; + } + } + } + + setValue(INTERNAL_FLAG, KEY_ACTION, action); + } + } + + if (needsHelp != null) { + printHelpAndExitForAction(action, needsHelp); + } + } + + /** + * Finds an {@link Arg} given an action name and a long flag name. + * @return The {@link Arg} found or null. + */ + protected Arg findLongArg(String action, String longName) { + if (action == null) { + action = GLOBAL_FLAG; + } + String key = action + "/" + longName; + return mArguments.get(key); + } + + /** + * Finds an {@link Arg} given an action name and a short flag name. + * @return The {@link Arg} found or null. + */ + protected Arg findShortArg(String action, String shortName) { + if (action == null) { + action = GLOBAL_FLAG; + } + + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getAction().equals(action)) { + if (shortName.equals(arg.getShortArg())) { + return arg; + } + } + } + + return null; + } + + /** + * Prints the help/usage and exits. + * + * @param errorFormat Optional error message to print prior to usage using String.format + * @param args Arguments for String.format + */ + public void printHelpAndExit(String errorFormat, Object... args) { + printHelpAndExitForAction(null /*actionFilter*/, errorFormat, args); + } + + /** + * Prints the help/usage and exits. + * + * @param actionFilter If null, displays help for all actions. If not null, display help only + * for that specific action. In all cases also display general usage and action list. + * @param errorFormat Optional error message to print prior to usage using String.format + * @param args Arguments for String.format + */ + public void printHelpAndExitForAction(String actionFilter, String errorFormat, Object... args) { + if (errorFormat != null) { + stderr(errorFormat, args); + } + + /* + * usage should fit in 80 columns + * 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + */ + stdout("\n" + + "Usage:\n" + + " android [global options] action [action options]\n" + + "\n" + + "Global options:"); + listOptions(GLOBAL_FLAG); + + stdout("\nValid actions:"); + for (String[] action : mActions) { + String filler = ""; + int len = action[0].length(); + if (len < 10) { + filler = " ".substring(len); + } + + stdout("- %1$s:%2$s %3$s", action[0], filler, action[1]); + } + + for (String[] action : mActions) { + if (actionFilter == null || actionFilter.equals(action[0])) { + stdout("\nAction \"%1$s\":", action[0]); + stdout(" %1$s", action[1]); + stdout("Options:"); + listOptions(action[0]); + } + } + + exit(); + } + + /** + * Internal helper to print all the option flags for a given action name. + */ + protected void listOptions(String action) { + int numOptions = 0; + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getAction().equals(action)) { + + String value = null; + if (arg.getDefaultValue() instanceof String[]) { + value = ""; + for (String v : (String[]) arg.getDefaultValue()) { + if (value.length() > 0) { + value += "|"; + } + value += v; + } + } else if (arg.getDefaultValue() != null) { + value = arg.getDefaultValue().toString(); + } + + stdout(" -%1$s %2$-10s %3$s%4$s", + arg.getShortArg(), + "--" + arg.getLongArg(), + arg.getDescription(), + value == null ? "" : " (" + value + ")"); + numOptions++; + } + } + + if (numOptions == 0) { + stdout(" No options"); + } + } + + //---- + + /** + * The mode of an argument specifies the type of variable it represents, + * whether an extra parameter is required after the flag and how to parse it. + */ + static enum MODE { + /** Argument value is a Boolean. Default value is a Boolean. */ + BOOLEAN { + @Override + public boolean needsExtra() { + return false; + } + @Override + public String process(Arg arg, String extra) { + // Toggle the current value + arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue()); + return null; + } + }, + + /** Argument value is an Integer. Default value is an Integer. */ + INTEGER { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String process(Arg arg, String extra) { + try { + arg.setCurrentValue(Integer.parseInt(extra)); + return null; + } catch (NumberFormatException e) { + return String.format("Failed to parse '%1$s' as an integer: %2%s", + extra, e.getMessage()); + } + } + }, + + /** Argument value is a String. Default value is a String[]. */ + ENUM { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String process(Arg arg, String extra) { + StringBuilder desc = new StringBuilder(); + String[] values = (String[]) arg.getDefaultValue(); + for (String value : values) { + if (value.equals(extra)) { + arg.setCurrentValue(extra); + return null; + } + + if (desc.length() != 0) { + desc.append(", "); + } + desc.append(value); + } + + return String.format("'%1$s' is not one of %2$s", extra, desc.toString()); + } + }, + + /** Argument value is a String. Default value is a null. */ + STRING { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String process(Arg arg, String extra) { + arg.setCurrentValue(extra); + return null; + } + }; + + /** + * Returns true if this mode requires an extra parameter. + */ + public abstract boolean needsExtra(); + + /** + * Processes the flag for this argument. + * + * @param arg The argument being processed. + * @param extra The extra parameter. Null if {@link #needsExtra()} returned false. + * @return An error string or null if there's no error. + */ + public abstract String process(Arg arg, String extra); + } + + /** + * An argument accepted by the command-line, also called "a flag". + * Arguments must have a short version (one letter), a long version name and a description. + * They can have a default value, or it can be null. + * Depending on the {@link MODE}, the default value can be a Boolean, an Integer, a String + * or a String array (in which case the first item is the current by default.) + */ + static class Arg { + private final String mAction; + private final String mShortName; + private final String mLongName; + private final String mDescription; + private final Object mDefaultValue; + private Object mCurrentValue; + private final MODE mMode; + private final boolean mMandatory; + + /** + * Creates a new argument flag description. + * + * @param mode The {@link MODE} for the argument. + * @param mandatory True if this argument is mandatory for this action. + * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG. + * @param shortName The one-letter short argument name. Cannot be empty nor null. + * @param longName The long argument name. Cannot be empty nor null. + * @param description The description. Cannot be null. + * @param defaultValue The default value (or values), which depends on the selected {@link MODE}. + */ + public Arg(MODE mode, + boolean mandatory, + String action, + String shortName, + String longName, + String description, + Object defaultValue) { + mMode = mode; + mMandatory = mandatory; + mAction = action; + mShortName = shortName; + mLongName = longName; + mDescription = description; + mDefaultValue = defaultValue; + if (defaultValue instanceof String[]) { + mCurrentValue = ((String[])defaultValue)[0]; + } else { + mCurrentValue = mDefaultValue; + } + } + + public boolean isMandatory() { + return mMandatory; + } + + public String getShortArg() { + return mShortName; + } + + public String getLongArg() { + return mLongName; + } + + public String getDescription() { + return mDescription; + } + + public String getAction() { + return mAction; + } + + public Object getDefaultValue() { + return mDefaultValue; + } + + public Object getCurrentValue() { + return mCurrentValue; + } + + public void setCurrentValue(Object currentValue) { + mCurrentValue = currentValue; + } + + public MODE getMode() { + return mMode; + } + } + + /** + * Internal helper to define a new argument for a give action. + * + * @param mode The {@link MODE} for the argument. + * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG. + * @param shortName The one-letter short argument name. Cannot be empty nor null. + * @param longName The long argument name. Cannot be empty nor null. + * @param description The description. Cannot be null. + * @param defaultValue The default value (or values), which depends on the selected {@link MODE}. + */ + protected void define(MODE mode, + boolean mandatory, + String action, + String shortName, String longName, + String description, Object defaultValue) { + assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory + + String key = action + "/" + longName; + mArguments.put(key, new Arg(mode, mandatory, + action, shortName, longName, description, defaultValue)); + } + + /** + * Exits in case of error. + * This is protected so that it can be overridden in unit tests. + */ + protected void exit() { + System.exit(1); + } + + /** + * Prints a line to stdout. + * This is protected so that it can be overridden in unit tests. + * + * @param format The string to be formatted. Cannot be null. + * @param args Format arguments. + */ + protected void stdout(String format, Object...args) { + System.out.println(String.format(format, args)); + } + + /** + * Prints a line to stderr. + * This is protected so that it can be overridden in unit tests. + * + * @param format The string to be formatted. Cannot be null. + * @param args Format arguments. + */ + protected void stderr(String format, Object...args) { + System.err.println(String.format(format, args)); + } +} diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java index 72bd2aa..3bcb9a3 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/Main.java +++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java @@ -23,6 +23,8 @@ import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; import com.android.sdklib.IAndroidTarget.IOptionalLibrary; +import com.android.sdklib.project.ProjectCreator; +import com.android.sdklib.project.ProjectCreator.OutputLevel; import com.android.sdklib.vm.HardwareProperties; import com.android.sdklib.vm.VmManager; import com.android.sdklib.vm.HardwareProperties.HardwareProperty; @@ -35,31 +37,20 @@ import java.util.List; import java.util.Map; /** - * Main class for the 'android' application - * + * Main class for the 'android' application. */ class Main { private final static String TOOLSDIR = "com.android.sdkmanager.toolsdir"; - private final static String ARG_LIST_TARGET = "target"; - private final static String ARG_LIST_VM = "vm"; - private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" }; private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" }; private String mSdkFolder; + private ISdkLog mSdkLog; private SdkManager mSdkManager; private VmManager mVmManager; - - /* --list parameters */ - private String mListObject; - - /* --create parameters */ - private boolean mCreateVm; - private int mCreateTargetId; - private IAndroidTarget mCreateTarget; - private String mCreateName; + private SdkCommandLine mSdkCommandLine; public static void main(String[] args) { new Main().run(args); @@ -71,7 +62,7 @@ class Main { */ private void run(String[] args) { init(); - parseArgs(args); + mSdkCommandLine.parseArgs(args); parseSdk(); doAction(); } @@ -81,70 +72,41 @@ class Main { * doing basic parsing of the SDK. */ private void init() { + mSdkCommandLine = new SdkCommandLine(); + /* We get passed a property for the tools dir */ String toolsDirProp = System.getProperty(TOOLSDIR); if (toolsDirProp == null) { // for debugging, it's easier to override using the process environment toolsDirProp = System.getenv(TOOLSDIR); } - if (toolsDirProp == null) { - printHelpAndExit("ERROR: The tools directory property is not set, please make sure you are executing android or android.bat"); - } - - // got back a level for the SDK folder - File tools = new File(toolsDirProp); - mSdkFolder = tools.getParent(); - - } - /** - * Parses command-line arguments, or prints help/usage and exits if error. - * @param args arguments passed to the program - */ - private void parseArgs(String[] args) { - final int numArgs = args.length; - - try { - int argPos = 0; - for (; argPos < numArgs; argPos++) { - final String arg = args[argPos]; - if (arg.equals("-l") || arg.equals("--list")) { - mListObject = args[++argPos]; - } else if (arg.equals("-c") || arg.equals("--create")) { - mCreateVm = true; - parseCreateArgs(args, ++argPos); + if (toolsDirProp != null) { + // got back a level for the SDK folder + File tools; + if (toolsDirProp.length() > 0) { + tools = new File(toolsDirProp); + mSdkFolder = tools.getParent(); + } else { + try { + tools = new File(".").getCanonicalFile(); + mSdkFolder = tools.getParent(); + } catch (IOException e) { + // Will print an error below since mSdkFolder is not defined } } - } catch (ArrayIndexOutOfBoundsException e) { - /* Any OOB triggers help */ - printHelpAndExit("ERROR: Not enough arguments."); } - } - private void parseCreateArgs(String[] args, int argPos) { - final int numArgs = args.length; - - try { - for (; argPos < numArgs; argPos++) { - final String arg = args[argPos]; - if (arg.equals("-t") || arg.equals("--target")) { - String targetId = args[++argPos]; - try { - // get the target id - mCreateTargetId = Integer.parseInt(targetId); - } catch (NumberFormatException e) { - printHelpAndExit("ERROR: Target Id is not a number"); - } - } else if (arg.equals("-n") || arg.equals("--name")) { - mCreateName = args[++argPos]; - } else { - printHelpAndExit("ERROR: '%s' unknown argument for --create mode", - args[argPos]); - } + if (mSdkFolder == null) { + String os = System.getProperty("os.name"); + String cmd = "android"; + if (os.startsWith("Windows")) { + cmd += ".bat"; } - } catch (ArrayIndexOutOfBoundsException e) { - /* Any OOB triggers help */ - printHelpAndExit("ERROR: Not enough arguments for --create"); + + mSdkCommandLine.printHelpAndExit( + "ERROR: The tools directory property is not set, please make sure you are executing %1$s", + cmd); } } @@ -152,10 +114,15 @@ class Main { * Does the basic SDK parsing required for all actions */ private void parseSdk() { - mSdkManager = SdkManager.createManager(mSdkFolder, new ISdkLog() { - public void error(String errorFormat, Object... args) { - System.err.printf("Error: " + errorFormat, args); - System.err.println(""); + mSdkLog = new ISdkLog() { + public void error(Throwable t, String errorFormat, Object... args) { + if (errorFormat != null) { + System.err.printf("Error: " + errorFormat, args); + System.err.println(""); + } + if (t != null) { + System.err.print("Error: " + t.getMessage()); + } } public void warning(String warningFormat, Object... args) { @@ -165,10 +132,15 @@ class Main { System.out.println(""); } } - }); + + public void printf(String msgFormat, Object... args) { + System.out.printf(msgFormat, args); + } + }; + mSdkManager = SdkManager.createManager(mSdkFolder, mSdkLog); if (mSdkManager == null) { - printHelpAndExit("ERROR: Unable to parse SDK content."); + mSdkCommandLine.printHelpAndExit("ERROR: Unable to parse SDK content."); } } @@ -176,19 +148,37 @@ class Main { * Actually do an action... */ private void doAction() { - if (mListObject != null) { + String action = mSdkCommandLine.getActionRequested(); + + if (SdkCommandLine.ACTION_LIST.equals(action)) { // list action. - if (ARG_LIST_TARGET.equals(mListObject)) { + if (SdkCommandLine.ARG_TARGET.equals(mSdkCommandLine.getListFilter())) { displayTargetList(); - } else if (ARG_LIST_VM.equals(mListObject)) { + } else if (SdkCommandLine.ARG_VM.equals(mSdkCommandLine.getListFilter())) { displayVmList(); } else { - printHelpAndExit("'%s' is not a valid --list option", mListObject); + displayTargetList(); + displayVmList(); } - } else if (mCreateVm) { + } else if (SdkCommandLine.ACTION_NEW_VM.equals(action)) { createVm(); + } else if (SdkCommandLine.ACTION_NEW_PROJECT.equals(action)) { + // get the target and try to resolve it. + int targetId = mSdkCommandLine.getNewProjectTargetId(); + IAndroidTarget[] targets = mSdkManager.getTargets(); + if (targetId < 1 || targetId > targets.length) { + mSdkCommandLine.printHelpAndExit("ERROR: Wrong target id."); + } + IAndroidTarget target = targets[targetId - 1]; + + ProjectCreator creator = new ProjectCreator(mSdkFolder, + OutputLevel.NORMAL, mSdkLog); + + creator.createProject(mSdkCommandLine.getNewProjectLocation(), + mSdkCommandLine.getNewProjectName(), mSdkCommandLine.getNewProjectPackage(), + mSdkCommandLine.getNewProjectActivity(), target, true); } else { - printHelpAndExit(null); + mSdkCommandLine.printHelpAndExit(null); } } @@ -274,7 +264,7 @@ class Main { index++; } } catch (AndroidLocationException e) { - printHelpAndExit(e.getMessage()); + mSdkCommandLine.printHelpAndExit(e.getMessage()); } } @@ -283,11 +273,14 @@ class Main { */ private void createVm() { // find a matching target - if (mCreateTargetId >= 1 && mCreateTargetId <= mSdkManager.getTargets().length) { - mCreateTarget = mSdkManager.getTargets()[mCreateTargetId-1]; // target it is 1-based + int targetId = mSdkCommandLine.getNewVmTargetId(); + IAndroidTarget target = null; + + if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) { + target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based } else { - printHelpAndExit( - "ERROR: Target Id is not a valid Id. Check android --list target for the list of targets."); + mSdkCommandLine.printHelpAndExit( + "ERROR: Target Id is not a valid Id. Check 'android list target' for the list of targets."); } try { @@ -295,19 +288,24 @@ class Main { String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS; Map<String, String> hardwareConfig = null; - if (mCreateTarget.isPlatform()) { + if (target.isPlatform()) { try { - hardwareConfig = promptForHardware(mCreateTarget); + hardwareConfig = promptForHardware(target); } catch (IOException e) { - printHelpAndExit(e.getMessage()); + mSdkCommandLine.printHelpAndExit(e.getMessage()); } } - VmManager.createVm(vmRoot, mCreateName, mCreateTarget, null /*skinName*/, - null /*sdcardPath*/, 0 /*sdcardSize*/, hardwareConfig, + VmManager.createVm(vmRoot, + mSdkCommandLine.getNewVmName(), + target, + null /*skinName*/, + null /*sdcardPath*/, + 0 /*sdcardSize*/, + hardwareConfig, null /* sdklog */); } catch (AndroidLocationException e) { - printHelpAndExit(e.getMessage()); + mSdkCommandLine.printHelpAndExit(e.getMessage()); } } @@ -325,7 +323,7 @@ class Main { System.out.print(String.format("Do you which to create a custom hardware profile [%s]", defaultAnswer)); - result = readLine(readLineBuffer); + result = readLine(readLineBuffer).trim(); // handle default: if (result.length() == 0) { result = defaultAnswer; @@ -391,8 +389,7 @@ class Main { break; case INTEGER: try { - @SuppressWarnings("unused") - int value = Integer.parseInt(result); + Integer.parseInt(result); map.put(property.getName(), result); i++; // valid reply, move to next property } catch (NumberFormatException e) { @@ -414,9 +411,8 @@ class Main { } /** - * Read the line from the input stream. + * Reads the line from the input stream. * @param buffer - * @return * @throws IOException */ private String readLine(byte[] buffer) throws IOException { @@ -434,7 +430,12 @@ class Main { return new String(buffer, 0, count) + secondHalf; } - return new String(buffer, 0, count - 1); // -1 to not include the carriage return + // ignore end whitespace + while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) { + count--; + } + + return new String(buffer, 0, count); } /** @@ -442,6 +443,7 @@ class Main { * @throws IOException If the value is not a boolean string. */ private boolean getBooleanReply(String reply) throws IOException { + for (String valid : BOOLEAN_YES_REPLIES) { if (valid.equalsIgnoreCase(reply)) { return true; @@ -456,32 +458,4 @@ class Main { throw new IOException(String.format("%s is not a valid reply", reply)); } - - /** - * Prints the help/usage and exits. - * @param errorFormat Optional error message to print prior to usage using String.format - * @param args Arguments for String.format - */ - private void printHelpAndExit(String errorFormat, Object... args) { - if (errorFormat != null) { - System.err.println(String.format(errorFormat, args)); - } - - /* - * usage should fit in 80 columns - * 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - */ - final String usage = "\n" + - "Usage:\n" + - " android --list [target|vm]\n" + - " android --create --target <target id> --name <name>\n" + - "\n" + - "Options:\n" + - " -l [target|vm], --list [target|vm]\n" + - " Outputs the available targets or Virtual Machines and their Ids.\n" + - "\n"; - - System.out.println(usage); - System.exit(1); - } }
\ No newline at end of file diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java new file mode 100644 index 0000000..918c534 --- /dev/null +++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdkmanager; + +import com.android.sdklib.SdkManager; + + +/** + * Specific command-line flags for the {@link SdkManager}. + */ +public class SdkCommandLine extends CommandLineProcessor { + + public static final String ARG_ALIAS = "alias"; + public static final String ARG_ACTIVITY = "activity"; + public static final String ARG_VM = "vm"; + public static final String ARG_TARGET = "target"; + public static final String ARG_ALL = "all"; + + public static final String KEY_IN = "in"; + public static final String KEY_ACTIVITY = ARG_ACTIVITY; + public static final String KEY_PACKAGE = "package"; + public static final String KEY_MODE = "mode"; + public static final String KEY_TARGET_ID = ARG_TARGET; + public static final String KEY_NAME = "name"; + public static final String KEY_OUT = "out"; + public static final String KEY_FILTER = "filter"; + + public final static String ACTION_LIST = "list"; + public final static String ACTION_NEW_VM = ARG_VM; + public final static String ACTION_NEW_PROJECT = "project"; + public final static String ACTION_UPDATE_PROJECT = "update"; + + private final static String[][] ACTIONS = { + { ACTION_LIST, + "Lists existing targets or VMs." }, + { ACTION_NEW_VM, + "Creates a new VM." }, + { ACTION_NEW_PROJECT, + "Creates a new project using a template." }, + { ACTION_UPDATE_PROJECT, + "Updates a new project from existing source (must have an AndroidManifest.xml)." }, + }; + + public SdkCommandLine() { + super(ACTIONS); + + define(MODE.ENUM, false, ACTION_LIST, "f", KEY_FILTER, + "List filter", new String[] { ARG_ALL, ARG_TARGET, ARG_VM }); + + define(MODE.STRING, false, ACTION_NEW_VM, "o", KEY_OUT, + "Location path of new VM", null); + define(MODE.STRING, true, ACTION_NEW_VM, "n", KEY_NAME, + "Name of the new VM", null); + define(MODE.INTEGER, true, ACTION_NEW_VM, "t", KEY_TARGET_ID, + "Target id of the new VM", null); + + define(MODE.ENUM, true, ACTION_NEW_PROJECT, "m", KEY_MODE, + "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS }); + define(MODE.STRING, false, ACTION_NEW_PROJECT, "o", KEY_OUT, + "Location path of new project", null); + define(MODE.STRING, true, ACTION_NEW_PROJECT, "n", KEY_NAME, + "Name of the new project", null); + define(MODE.INTEGER, true, ACTION_NEW_PROJECT, "t", KEY_TARGET_ID, + "Target id of the new project", null); + define(MODE.STRING, true, ACTION_NEW_PROJECT, "p", KEY_PACKAGE, + "Package name", null); + define(MODE.STRING, true, ACTION_NEW_PROJECT, "a", KEY_ACTIVITY, + "Activity name", null); + + define(MODE.STRING, false, ACTION_UPDATE_PROJECT, "i", KEY_IN, + "Directory location of the project", null); + define(MODE.STRING, true, ACTION_UPDATE_PROJECT, "t", KEY_TARGET_ID, + "Target id to set for the project", null); + } + + // -- some helpers for list action flags + + /** Helper to retrieve the --filter for the list action. */ + public String getListFilter() { + return ((String) getValue(ACTION_LIST, KEY_FILTER)); + } + + // -- some helpers for vm action flags + + /** Helper to retrieve the --out location for the new vm action. */ + public String getNewVmLocation() { + return ((String) getValue(ACTION_NEW_VM, KEY_OUT)); + } + + /** Helper to retrieve the --target id for the new vm action. */ + public int getNewVmTargetId() { + return ((Integer) getValue(ACTION_NEW_VM, KEY_TARGET_ID)).intValue(); + } + + /** Helper to retrieve the --name for the new vm action. */ + public String getNewVmName() { + return ((String) getValue(ACTION_NEW_VM, KEY_NAME)); + } + + // -- some helpers for project action flags + + /** Helper to retrieve the --out location for the new project action. */ + public String getNewProjectLocation() { + return ((String) getValue(ACTION_NEW_PROJECT, KEY_OUT)); + } + + /** Helper to retrieve the --target id for the new project action. */ + public int getNewProjectTargetId() { + return ((Integer) getValue(ACTION_NEW_PROJECT, KEY_TARGET_ID)).intValue(); + } + + /** Helper to retrieve the --name for the new project action. */ + public String getNewProjectName() { + return ((String) getValue(ACTION_NEW_PROJECT, KEY_NAME)); + } + + /** Helper to retrieve the --package for the new project action. */ + public String getNewProjectPackage() { + return ((String) getValue(ACTION_NEW_PROJECT, KEY_PACKAGE)); + } + + /** Helper to retrieve the --activity for the new project action. */ + public String getNewProjectActivity() { + return ((String) getValue(ACTION_NEW_PROJECT, KEY_ACTIVITY)); + } + + // -- some helpers for update action flags + + /** Helper to retrieve the --out location for the update project action. */ + public String getUpdateProjectLocation() { + return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_OUT)); + } + + /** Helper to retrieve the --target id for the update project action. */ + public int getUpdateProjectTargetId() { + return ((Integer) getValue(ACTION_UPDATE_PROJECT, KEY_TARGET_ID)).intValue(); + } +} diff --git a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java new file mode 100644 index 0000000..e74cdbd --- /dev/null +++ b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdkmanager; + +import junit.framework.TestCase; + + +public class CommandLineProcessorTest extends TestCase { + + /** + * A mock version of the {@link CommandLineProcessor} class that does not + * exits and captures its stdout/stderr output. + */ + public static class MockCommandLineProcessor extends CommandLineProcessor { + private boolean mExitCalled; + private boolean mHelpCalled; + private String mStdOut = ""; + private String mStdErr = ""; + + public MockCommandLineProcessor() { + super(new String[][] { + { "action1", "Some action" }, + { "action2", "Another action" }, + }); + define(MODE.STRING, false /*mandatory*/, + "action1", "1", "first", "non-mandatory flag", null); + define(MODE.STRING, true /*mandatory*/, + "action1", "2", "second", "mandatory flag", null); + } + + @Override + public void printHelpAndExitForAction(String actionFilter, + String errorFormat, Object... args) { + mHelpCalled = true; + super.printHelpAndExitForAction(actionFilter, errorFormat, args); + } + + @Override + protected void exit() { + mExitCalled = true; + } + + @Override + protected void stdout(String format, Object... args) { + String s = String.format(format, args); + mStdOut += s + "\n"; + // don't call super to avoid printing stuff + } + + @Override + protected void stderr(String format, Object... args) { + String s = String.format(format, args); + mStdErr += s + "\n"; + // don't call super to avoid printing stuff + } + + public boolean wasHelpCalled() { + return mHelpCalled; + } + + public boolean wasExitCalled() { + return mExitCalled; + } + + public String getStdOut() { + return mStdOut; + } + + public String getStdErr() { + return mStdErr; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public final void testPrintHelpAndExit() { + MockCommandLineProcessor c = new MockCommandLineProcessor(); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertTrue(c.getStdOut().equals("")); + assertTrue(c.getStdErr().equals("")); + c.printHelpAndExit(null); + assertTrue(c.getStdOut().indexOf("-v") != -1); + assertTrue(c.getStdOut().indexOf("--verbose") != -1); + assertTrue(c.getStdErr().equals("")); + assertTrue(c.wasExitCalled()); + + c = new MockCommandLineProcessor(); + assertFalse(c.wasExitCalled()); + assertTrue(c.getStdOut().equals("")); + assertTrue(c.getStdErr().indexOf("Missing parameter") == -1); + + c.printHelpAndExit("Missing %s", "parameter"); + assertTrue(c.wasExitCalled()); + assertFalse(c.getStdOut().equals("")); + assertTrue(c.getStdErr().indexOf("Missing parameter") != -1); + } + + public final void testVerbose() { + MockCommandLineProcessor c = new MockCommandLineProcessor(); + + assertFalse(c.isVerbose()); + c.parseArgs(new String[] { "-v" }); + assertTrue(c.isVerbose()); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing action name.") != -1); + + c = new MockCommandLineProcessor(); + c.parseArgs(new String[] { "--verbose" }); + assertTrue(c.isVerbose()); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing action name.") != -1); + } + + public final void testHelp() { + MockCommandLineProcessor c = new MockCommandLineProcessor(); + + c.parseArgs(new String[] { "-h" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing action name.") == -1); + + c = new MockCommandLineProcessor(); + c.parseArgs(new String[] { "--help" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing action name.") == -1); + } + + public final void testMandatory() { + MockCommandLineProcessor c = new MockCommandLineProcessor(); + + c.parseArgs(new String[] { "action1", "-1", "value1", "-2", "value2" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals("value1", c.getValue("action1", "first")); + assertEquals("value2", c.getValue("action1", "second")); + + c = new MockCommandLineProcessor(); + c.parseArgs(new String[] { "action1", "-2", "value2" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals(null, c.getValue("action1", "first")); + assertEquals("value2", c.getValue("action1", "second")); + + c = new MockCommandLineProcessor(); + c.parseArgs(new String[] { "action1" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("must be defined") != -1); + assertEquals(null, c.getValue("action1", "first")); + assertEquals(null, c.getValue("action1", "second")); + } +} diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java new file mode 100644 index 0000000..b943b98 --- /dev/null +++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdkmanager; + +import junit.framework.TestCase; + +public class SdkCommandLineTest extends TestCase { + + /** + * A mock version of the {@link SdkCommandLine} class that does not + * exits and discards its stdout/stderr output. + */ + public static class MockSdkCommandLine extends SdkCommandLine { + private boolean mExitCalled; + private boolean mHelpCalled; + + public MockSdkCommandLine() { + } + + @Override + public void printHelpAndExitForAction(String actionFilter, + String errorFormat, Object... args) { + mHelpCalled = true; + super.printHelpAndExitForAction(actionFilter, errorFormat, args); + } + + @Override + protected void exit() { + mExitCalled = true; + } + + @Override + protected void stdout(String format, Object... args) { + // discard + } + + @Override + protected void stderr(String format, Object... args) { + // discard + } + + public boolean wasExitCalled() { + return mExitCalled; + } + + public boolean wasHelpCalled() { + return mHelpCalled; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** Test list with long name and verbose */ + public final void testList_Long_Verbose() { + MockSdkCommandLine c = new MockSdkCommandLine(); + assertEquals("all", c.getListFilter()); + c.parseArgs(new String[] { "-v", "list", "--filter", "vm" }); + assertFalse(c.wasHelpCalled()); + assertFalse(c.wasExitCalled()); + assertEquals("vm", c.getListFilter()); + assertTrue(c.isVerbose()); + } + + /** Test list with short name and no verbose */ + public final void testList_Short() { + MockSdkCommandLine c = new MockSdkCommandLine(); + assertEquals("all", c.getListFilter()); + c.parseArgs(new String[] { "list", "-f", "vm" }); + assertFalse(c.wasHelpCalled()); + assertFalse(c.wasExitCalled()); + assertEquals("vm", c.getListFilter()); + } + + /** Test list with long name and missing parameter */ + public final void testList_Long_MissingParam() { + MockSdkCommandLine c = new MockSdkCommandLine(); + assertEquals("all", c.getListFilter()); + c.parseArgs(new String[] { "list", "--filter" }); + assertTrue(c.wasHelpCalled()); + assertTrue(c.wasExitCalled()); + assertEquals("all", c.getListFilter()); + } +} diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java index 5759613..2a2efe7 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java @@ -95,6 +95,10 @@ final class AddOnTarget implements IAndroidTarget { } } + public String getLocation() { + return mLocation; + } + public String getName() { return mName; } @@ -103,6 +107,10 @@ final class AddOnTarget implements IAndroidTarget { return mVendor; } + public String getFullName() { + return String.format("%1$s (%2$s)", mName, mVendor); + } + public String getDescription() { return mDescription; } @@ -140,6 +148,28 @@ final class AddOnTarget implements IAndroidTarget { return mLibraries; } + public boolean isCompatibleBaseFor(IAndroidTarget target) { + // basic test + if (target == this) { + return true; + } + + // if the receiver has no optional library, then anything with api version number >= to + // the receiver is compatible. + if (mLibraries.length == 0) { + return target.getApiVersionNumber() >= getApiVersionNumber(); + } + + // Otherwise, target is only compatible if the vendor and name are equals with the api + // number greater or equal (ie target is a newer version of this add-on). + if (target.isPlatform() == false) { + return (mVendor.equals(target.getVendor()) && mName.equals(target.getName()) && + target.getApiVersionNumber() >= getApiVersionNumber()); + } + + return false; + } + public String hashString() { return String.format(ADD_ON_FORMAT, mVendor, mName, mBasePlatform.getApiVersionNumber()); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java index e5d45b2..0e2b109 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java @@ -22,23 +22,41 @@ package com.android.sdklib; */ public interface IAndroidTarget extends Comparable<IAndroidTarget> { + /** OS Path to the "android.jar" file. */ public static int ANDROID_JAR = 1; + /** OS Path to the "framework.aidl" file. */ public static int ANDROID_AIDL = 2; + /** OS Path to "images" folder which contains the emulator system images. */ public static int IMAGES = 3; + /** OS Path to the "samples" folder which contains sample projects. */ public static int SAMPLES = 4; + /** OS Path to the "skins" folder which contains the emulator skins. */ public static int SKINS = 5; + /** OS Path to the "templates" folder which contains the templates for new projects. */ public static int TEMPLATES = 6; + /** OS Path to the "data" folder which contains data & libraries for the SDK tools. */ public static int DATA = 7; + /** OS Path to the "attrs.xml" file. */ public static int ATTRIBUTES = 8; + /** OS Path to the "attrs_manifest.xml" file. */ public static int MANIFEST_ATTRIBUTES = 9; + /** OS Path to the "data/layoutlib.jar" library. */ public static int LAYOUT_LIB = 10; + /** OS Path to the "data/res" folder. */ public static int RESOURCES = 11; + /** OS Path to the "data/fonts" folder. */ public static int FONTS = 12; + /** OS Path to the "data/widgets.txt" file. */ public static int WIDGETS = 13; + /** OS Path to the "data/activity_actions.txt" file. */ public static int ACTIONS_ACTIVITY = 14; + /** OS Path to the "data/broadcast_actions.txt" file. */ public static int ACTIONS_BROADCAST = 15; + /** OS Path to the "data/service_actions.txt" file. */ public static int ACTIONS_SERVICE = 16; + /** OS Path to the "data/categories.txt" file. */ public static int CATEGORIES = 17; + /** OS Path to the "sources" folder. */ public static int SOURCES = 18; public interface IOptionalLibrary { @@ -48,6 +66,11 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { } /** + * Returns the target location. + */ + String getLocation(); + + /** * Returns the name of the vendor of the target. */ String getVendor(); @@ -58,6 +81,12 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { String getName(); /** + * Returns the full name of the target, possibly including vendor name. + * @return + */ + String getFullName(); + + /** * Returns the description of the target. */ String getDescription(); @@ -80,7 +109,7 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { /** * Returns the path of a platform component. * @param pathId the id representing the path to return. Any of the constants defined in the - * {@link ITargetDataProvider} interface can be used. + * {@link IAndroidTarget} interface can be used. */ String getPath(int pathId); @@ -96,6 +125,15 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { IOptionalLibrary[] getOptionalLibraries(); /** + * 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 + * the given target. + * + * @param target the IAndroidTarget to test. + */ + boolean isCompatibleBaseFor(IAndroidTarget target); + + /** * Returns a string able to uniquely identify a target. * Typically the target will encode information such as api level, whether it's a platform * or add-on, and if it's an add-on vendor and add-on name. diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java index 3eda37f..8cbe44a 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java @@ -21,5 +21,6 @@ package com.android.sdklib; */ public interface ISdkLog { void warning(String warningFormat, Object... args); - void error(String errorFormat, Object... args); + void error(Throwable t, String errorFormat, Object... args); + void printf(String msgFormat, Object... args); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java index f5a1f6d..59fa81c 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java @@ -95,6 +95,10 @@ final class PlatformTarget implements IAndroidTarget { public String getName() { return mName; } + + public String getFullName() { + return mName; + } /* * (non-Javadoc) @@ -136,7 +140,17 @@ final class PlatformTarget implements IAndroidTarget { public IOptionalLibrary[] getOptionalLibraries() { return null; } + + public boolean isCompatibleBaseFor(IAndroidTarget target) { + // basic test + if (target == this) { + return true; + } + // target is compatible wit the receiver as long as its api version number is greater or + // equal. + return target.getApiVersionNumber() >= mApiVersionNumber; + } public String hashString() { return String.format(PLATFORM_HASH, mApiVersionNumber); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java index 78d1fda..ede0d86 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java @@ -64,6 +64,40 @@ public final class SdkConstants { /** Skin layout file */ public final static String FN_SKIN_LAYOUT = "layout";//$NON-NLS-1$ + /* Folder Names for Android Projects . */ + + /** Resources folder name, i.e. "res". */ + public final static String FD_RESOURCES = "res"; //$NON-NLS-1$ + /** Assets folder name, i.e. "assets" */ + public final static String FD_ASSETS = "assets"; //$NON-NLS-1$ + /** Default source folder name, i.e. "src" */ + public final static String FD_SOURCES = "src"; //$NON-NLS-1$ + /** Default native library folder name inside the project, i.e. "libs" + * While the folder inside the .apk is "lib", we call that one libs because + * that's what we use in ant for both .jar and .so and we need to make the 2 development ways + * compatible. */ + public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$ + /** Native lib folder inside the APK: "lib" */ + public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$ + /** Default output folder name, i.e. "bin" */ + public final static String FD_OUTPUT = "bin"; //$NON-NLS-1$ + /** Default anim resource folder name, i.e. "anim" */ + public final static String FD_ANIM = "anim"; //$NON-NLS-1$ + /** Default color resource folder name, i.e. "color" */ + public final static String FD_COLOR = "color"; //$NON-NLS-1$ + /** Default drawable resource folder name, i.e. "drawable" */ + public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$ + /** Default layout resource folder name, i.e. "layout" */ + public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$ + /** Default menu resource folder name, i.e. "menu" */ + public final static String FD_MENU = "menu"; //$NON-NLS-1$ + /** Default values resource folder name, i.e. "values" */ + public final static String FD_VALUES = "values"; //$NON-NLS-1$ + /** Default xml resource folder name, i.e. "xml" */ + public final static String FD_XML = "xml"; //$NON-NLS-1$ + /** Default raw resource folder name, i.e. "raw" */ + public final static String FD_RAW = "raw"; //$NON-NLS-1$ + /* Folder Names for the Android SDK */ /** Name of the SDK platforms folder. */ @@ -90,13 +124,12 @@ public final class SdkConstants { public final static String FD_RES = "res"; /** Name of the SDK font folder, i.e. "fonts" */ public final static String FD_FONTS = "fonts"; - /** Default values resource folder name, i.e. "values" */ - public final static String FD_VALUES = "values"; /** Name of the android sources directory */ public static final String FD_ANDROID_SOURCES = "sources"; /** Name of the addon libs folder. */ public final static String FD_ADDON_LIBS = "libs"; + /* 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/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java index 67b8499..b4de51a 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java @@ -36,8 +36,8 @@ import java.util.regex.Pattern; */ public final class SdkManager { - private final static String PROP_VERSION_SDK = "ro.build.version.sdk"; - private final static String PROP_VERSION_RELEASE = "ro.build.version.release"; + public final static String PROP_VERSION_SDK = "ro.build.version.sdk"; + public final static String PROP_VERSION_RELEASE = "ro.build.version.release"; private final static String ADDON_NAME = "name"; private final static String ADDON_VENDOR = "vendor"; @@ -73,7 +73,7 @@ public final class SdkManager { return manager; } catch (IllegalArgumentException e) { if (log != null) { - log.error(e.getMessage()); + log.error(e, "Error parsing the sdk."); } } @@ -188,13 +188,14 @@ public final class SdkManager { // looks like apiNumber does not parse to a number. // Ignore this platform. if (log != null) { - log.error("Ignoring platform '%1$s': %2$s is not a valid number in %3$s.", + log.error(null, + "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.", platform.getName(), PROP_VERSION_SDK, SdkConstants.FN_BUILD_PROP); } } } } else if (log != null) { - log.error("Ignoring platform '%1$s': %2$s is missing.", platform.getName(), + log.error(null, "Ignoring platform '%1$s': %2$s is missing.", platform.getName(), SdkConstants.FN_BUILD_PROP); } @@ -281,7 +282,7 @@ public final class SdkManager { if (baseTarget == null) { if (log != null) { - log.error( + log.error(null, "Ignoring add-on '%1$s': Unable to find base platform with API level %2$d", addon.getName(), apiValue); } @@ -292,7 +293,7 @@ public final class SdkManager { // looks like apiNumber does not parse to a number. // Ignore this add-on. if (log != null) { - log.error( + log.error(null, "Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.", addon.getName(), ADDON_API, SdkConstants.FN_BUILD_PROP); } @@ -331,7 +332,7 @@ public final class SdkManager { return target; } } else if (log != null) { - log.error("Ignoring add-on '%1$s': %2$s is missing.", addon.getName(), + log.error(null, "Ignoring add-on '%1$s': %2$s is missing.", addon.getName(), SdkConstants.FN_MANIFEST_INI); } @@ -340,7 +341,7 @@ public final class SdkManager { private void displayAddonManifestError(ISdkLog log, String addonName, String valueName) { if (log != null) { - log.error("Ignoring add-on '%1$s': '%2$s' is missing from %3$s.", + log.error(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.", addonName, valueName, SdkConstants.FN_MANIFEST_INI); } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java new file mode 100644 index 0000000..1184fc2 --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.sdklib.project; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.project.ProjectProperties.PropertyType; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Creates the basic files needed to get an Android project up and running. Also + * allows creation of IntelliJ project files. + * + * @hide + */ +public class ProjectCreator { + + private final static String PH_JAVA_FOLDER = "PACKAGE_PATH"; + private final static String PH_PACKAGE = "PACKAGE"; + private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME"; + + private final static String FOLDER_TESTS = "tests"; + + public enum OutputLevel { + SILENT, NORMAL, VERBOSE; + } + + private static class ProjectCreateException extends Exception { + /** default UID. This will not be serialized anyway. */ + private static final long serialVersionUID = 1L; + + ProjectCreateException(String message) { + super(message); + } + + ProjectCreateException(Throwable t, String format, Object... args) { + super(format != null ? String.format(format, args) : format, t); + } + + ProjectCreateException(String format, Object... args) { + super(String.format(format, args)); + } + } + + private final OutputLevel mLevel; + + private final ISdkLog mLog; + private final String mSdkFolder; + + public ProjectCreator(String sdkFolder, OutputLevel level, ISdkLog log) { + mSdkFolder = sdkFolder; + mLevel = level; + mLog = log; + } + + /** + * Creates a new project. + * @param folderPath the folder of the project to create. This folder must exist. + * @param projectName the name of the project. + * @param packageName the package of the project. + * @param activityName the activity of the project as it will appear in the manifest. + * @param target the project target. + * @param isTestProject whether the project to create is a test project. + */ + public void createProject(String folderPath, String projectName, + String packageName, String activityName, IAndroidTarget target, + boolean isTestProject) { + + // check project folder exists. + File projectFolder = new File(folderPath); + if (projectFolder.isDirectory() == false) { + mLog.error(null, "Folder '%s' does not exist. Aborting...", folderPath); + return; + } + + try { + // first create the project properties. + + // location of the SDK goes in localProperty + ProjectProperties localProperties = ProjectProperties.create(folderPath, + PropertyType.LOCAL); + localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder); + localProperties.save(); + + // target goes in default properties + ProjectProperties defaultProperties = ProjectProperties.create(folderPath, + PropertyType.DEFAULT); + defaultProperties.setAndroidTarget(target); + defaultProperties.save(); + + // create the map for place-holders of values to replace in the templates + final HashMap<String, String> keywords = new HashMap<String, String>(); + + // create the required folders. + // compute src folder path + final String packagePath = + stripString(packageName.replace(".", File.separator), + File.separatorChar); + + // put this path in the place-holder map for project files that needs to list + // files manually. + keywords.put(PH_JAVA_FOLDER, packagePath); + + keywords.put(PH_PACKAGE, packageName); + if (activityName != null) { + keywords.put(PH_ACTIVITY_NAME, activityName); + } + + // create the source folder and the java package folders. + final String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath; + File sourceFolder = createDirs(projectFolder, srcFolderPath); + String javaTemplate = "java_file.template"; + String activityFileName = activityName + ".java"; + if (isTestProject) { + javaTemplate = "java_tests_file.template"; + activityFileName = activityName + "Test.java"; + } + installTemplate(javaTemplate, new File(sourceFolder, activityFileName), + keywords, target); + + // create other useful folders + File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES); + createDirs(projectFolder, SdkConstants.FD_OUTPUT); + createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS); + + if (isTestProject == false) { + /* Make res files only for non test projects */ + File valueFolder = createDirs(resourceFodler, SdkConstants.FD_VALUES); + installTemplate("strings.template", new File(valueFolder, "strings.xml"), + keywords, target); + + File layoutFolder = createDirs(resourceFodler, SdkConstants.FD_LAYOUT); + installTemplate("layout.template", new File(layoutFolder, "main.xml"), + keywords, target); + } + + /* Make AndroidManifest.xml and build.xml files */ + String manifestTemplate = "AndroidManifest.template"; + if (isTestProject) { + manifestTemplate = "AndroidManifest.tests.template"; + } + + installTemplate(manifestTemplate, new File(projectFolder, "AndroidManifest.xml"), + keywords, target); + + installTemplate("build.template", new File(projectFolder, "build.xml"), keywords); + + // if this is not a test project, then we create one. + if (isTestProject == false) { + // create the test project folder. + createDirs(projectFolder, FOLDER_TESTS); + File testProjectFolder = new File(folderPath, FOLDER_TESTS); + + createProject(testProjectFolder.getAbsolutePath(), projectName, packageName, + activityName, target, true /*isTestProject*/); + } + } catch (ProjectCreateException e) { + mLog.error(e, null); + } catch (IOException e) { + mLog.error(e, null); + } + } + + /** + * Installs a new file that is based on a template file provided by a given target. + * Each match of each key from the place-holder map in the template will be replaced with its + * corresponding value in the created file. + * + * @param templateName the name of to the template file + * @param dest the path to the destination file, relative to the project + * @param placeholderMap a map of (place-holder, value) to create the file from the template. + * @param target the Target of the project that will be providing the template. + * @throws ProjectCreateException + */ + private void installTemplate(String templateName, File destFile, + Map<String, String> placeholderMap, IAndroidTarget target) + throws ProjectCreateException { + // query the target for its template directory + String templateFolder = target.getPath(IAndroidTarget.TEMPLATES); + final String sourcePath = templateFolder + File.separator + templateName; + + installFullPathTemplate(sourcePath, destFile, placeholderMap); + } + + /** + * Installs a new file that is based on a template file provided by the tools folder. + * Each match of each key from the place-holder map in the template will be replaced with its + * corresponding value in the created file. + * + * @param templateName the name of to the template file + * @param dest the path to the destination file, relative to the project + * @param placeholderMap a map of (place-holder, value) to create the file from the template. + * @throws ProjectCreateException + */ + private void installTemplate(String templateName, File destFile, + Map<String, String> placeholderMap) + throws ProjectCreateException { + // query the target for its template directory + String templateFolder = mSdkFolder + File.separator + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER; + final String sourcePath = templateFolder + File.separator + templateName; + + installFullPathTemplate(sourcePath, destFile, placeholderMap); + } + + /** + * Installs a new file that is based on a template. + * Each match of each key from the place-holder map in the template will be replaced with its + * corresponding value in the created file. + * + * @param sourcePath the full path to the source template file + * @param destFile the destination file + * @param placeholderMap a map of (place-holder, value) to create the file from the template. + * @throws ProjectCreateException + */ + private void installFullPathTemplate(String sourcePath, File destFile, + Map<String, String> placeholderMap) throws ProjectCreateException { + try { + BufferedWriter out = new BufferedWriter(new FileWriter(destFile)); + BufferedReader in = new BufferedReader(new FileReader(sourcePath)); + String line; + + while ((line = in.readLine()) != null) { + for (String key : placeholderMap.keySet()) { + line = line.replace(key, placeholderMap.get(key)); + } + + out.write(line); + out.newLine(); + } + + out.close(); + in.close(); + } catch (Exception e) { + throw new ProjectCreateException(e, "Could not access %1$s: %2$s", + destFile, e.getMessage()); + } + + println("Added file %1$s", destFile); + } + + + /** + * Prints a message unless silence is enabled. + * @param format Format for String.format + * @param args Arguments for String.format + */ + private void println(String format, Object... args) { + if (mLevel == OutputLevel.VERBOSE) { + System.out.println(String.format(format, args)); + } + } + + /** + * Creates a new folder, along with any parent folders that do not exists. + * + * @param parent the parent folder + * @param name the name of the directory to create. + * @throws ProjectCreateException + */ + private File createDirs(File parent, String name) throws ProjectCreateException { + final File newFolder = new File(parent, name); + boolean existedBefore = true; + + if (!newFolder.exists()) { + if (!newFolder.mkdirs()) { + throw new ProjectCreateException("Could not create directory: %1$s", newFolder); + } + existedBefore = false; + } + + if (newFolder.isDirectory()) { + if (!newFolder.canWrite()) { + throw new ProjectCreateException("Path is not writable: %1$s", newFolder); + } + } else { + throw new ProjectCreateException("Path is not a directory: %1$s", newFolder); + } + + if (!existedBefore) { + try { + println("Created directory %1$s", newFolder.getCanonicalPath()); + } catch (IOException e) { + throw new ProjectCreateException( + "Could not determine canonical path of created directory", e); + } + } + + return newFolder; + } + + /** + * Strips the string of beginning and trailing characters (multiple + * characters will be stripped, example stripString("..test...", '.') + * results in "test"; + * + * @param s the string to strip + * @param strip the character to strip from beginning and end + * @return the stripped string or the empty string if everything is stripped. + */ + private static String stripString(String s, char strip) { + final int sLen = s.length(); + int newStart = 0, newEnd = sLen - 1; + + while (newStart < sLen && s.charAt(newStart) == strip) { + newStart++; + } + while (newEnd >= 0 && s.charAt(newEnd) == strip) { + newEnd--; + } + + /* + * newEnd contains a char we want, and substring takes end as being + * exclusive + */ + newEnd++; + + if (newStart >= sLen || newEnd < 0) { + return ""; + } + + return s.substring(newStart, newEnd); + } +} diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java index c0c1fe3..473f284 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java @@ -16,6 +16,7 @@ package com.android.sdklib.project; +import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkManager; import java.io.File; @@ -32,38 +33,87 @@ import java.util.Map.Entry; public final class ProjectProperties { /** The property name for the project target */ public final static String PROPERTY_TARGET = "target"; - public final static String PROPERTY_SDK = "sdk-folder"; + public final static String PROPERTY_SDK = "sdk-location"; - private final static String PROPERTIES_FILE = "default.properties"; + public static enum PropertyType { + BUILD("build.properties", BUILD_HEADER), + DEFAULT("default.properties", DEFAULT_HEADER), + LOCAL("local.properties", LOCAL_HEADER); + + private final String mFilename; + private final String mHeader; + + PropertyType(String filename, String header) { + mFilename = filename; + mHeader = header; + } + } - private final static String PROP_HEADER = + private final static String LOCAL_HEADER = +// 1-------10--------20--------30--------40--------50--------60--------70--------80 "# This file is automatically generated by Android Tools.\n" + "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + - "# For customized properties when using Ant, set new values\n" + - "# in a \"build.properties\" file.\n\n"; + "# \n" + + "# This file must *NOT* be checked in Version Control Systems,\n" + + "# as it contains information specific to your local configuration.\n" + + "\n"; + + private final static String DEFAULT_HEADER = +// 1-------10--------20--------30--------40--------50--------60--------70--------80 + "# This file is automatically generated by Android Tools.\n" + + "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + + "# \n" + + "# This file must be checked in Version Control Systems.\n" + + "# \n" + + "# To customize properties used by the Ant build system use,\n" + + "# \"build.properties\", and override values to adapt the script to your" + + "# project structure.\n" + + "\n"; + + private final static String BUILD_HEADER = +// 1-------10--------20--------30--------40--------50--------60--------70--------80 + "# This file is used to override default values used by the Ant build system.\n" + + "# \n" + + "# This file must be checked in Version Control Systems, as it is" + + "# integral to the build system of your project.\n" + + "# \n" + + "# Use this file to change values like:\n" + + "# application-package\n:" + + "# the name of your application package as defined in the manifest.\n" + + "# Used by the 'uninstall' rule.\n"+ + "# source-folder\n:" + + "# the name of the source folder. Default is 'src'.\n" + + "# out-folder\n:" + + "# the name of the output folder. Default is 'bin'\n" + + "\n"; private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>(); static { - COMMENT_MAP.put(PROPERTY_TARGET, "# Project target.\n"); - COMMENT_MAP.put(PROPERTY_SDK, "# location of the SDK. Only used by Ant.\n"); +// 1-------10--------20--------30--------40--------50--------60--------70--------80 + COMMENT_MAP.put(PROPERTY_TARGET, + "# Project target.\n"); + COMMENT_MAP.put(PROPERTY_SDK, "# location of the SDK. This is only used by Ant\n" + + "# For customization when using a Version Control System, please read the\n" + + "# header note.\n"); } private final String mProjectFolderOsPath; private final Map<String, String> mProperties; + private final PropertyType mType; /** * Loads a project properties file and return a {@link ProjectProperties} object * containing the properties * @param projectFolderOsPath the project folder. */ - public static ProjectProperties load(String projectFolderOsPath) { + public static ProjectProperties load(String projectFolderOsPath, PropertyType type) { File projectFolder = new File(projectFolderOsPath); if (projectFolder.isDirectory()) { - File defaultFile = new File(projectFolder, PROPERTIES_FILE); + File defaultFile = new File(projectFolder, type.mFilename); if (defaultFile.isFile()) { Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */); if (map != null) { - return new ProjectProperties(projectFolderOsPath, map); + return new ProjectProperties(projectFolderOsPath, map, type); } } } @@ -71,13 +121,14 @@ public final class ProjectProperties { } /** - * Creates a new project properties file, with no properties. + * Creates a new project properties object, with no properties. * <p/>The file is not created until {@link #save()} is called. * @param projectFolderOsPath the project folder. + * @param type */ - public static ProjectProperties create(String projectFolderOsPath) { + public static ProjectProperties create(String projectFolderOsPath, PropertyType type) { // create and return a ProjectProperties with an empty map. - return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>()); + return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>(), type); } /** @@ -90,6 +141,15 @@ public final class ProjectProperties { } /** + * Sets the target property to the given {@link IAndroidTarget} object. + * @param target the Android target. + */ + public void setAndroidTarget(IAndroidTarget target) { + assert mType == PropertyType.DEFAULT; + mProperties.put(PROPERTY_TARGET, target.hashString()); + } + + /** * Returns the value of a property. * @param name the name of the property. * @return the property value or null if the property is not set. @@ -103,12 +163,12 @@ public final class ProjectProperties { * @throws IOException */ public void save() throws IOException { - File toSave = new File(mProjectFolderOsPath, PROPERTIES_FILE); + File toSave = new File(mProjectFolderOsPath, mType.mFilename); FileWriter writer = new FileWriter(toSave); // write the header - writer.write(PROP_HEADER); + writer.write(mType.mHeader); // write the properties. for (Entry<String, String> entry : mProperties.entrySet()) { @@ -128,9 +188,12 @@ public final class ProjectProperties { * Use {@link #load(String)} or {@link #create(String)} to instantiate. * @param projectFolderOsPath * @param map + * @param type */ - private ProjectProperties(String projectFolderOsPath, Map<String, String> map) { + private ProjectProperties(String projectFolderOsPath, Map<String, String> map, + PropertyType type) { mProjectFolderOsPath = projectFolderOsPath; mProperties = map; + mType = type; } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java index a9f1b17..a28561d 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java @@ -76,9 +76,27 @@ public final class VmManager { buildVmList(sdk); } + /** + * Returns the existing VMs. + * @return a newly allocated arrays containing all the VMs. + */ public VmInfo[] getVms() { return mVmList.toArray(new VmInfo[mVmList.size()]); } + + /** + * Returns the {@link VmInfo} matching the given <var>name</var>. + * @return the matching VmInfo or <code>null</code> if none were found. + */ + public VmInfo getVm(String name) { + for (VmInfo info : mVmList) { + if (info.name.equals(name)) { + return info; + } + } + + return null; + } /** * Creates a new VM. @@ -101,7 +119,7 @@ public final class VmManager { File rootDirectory = new File(parentFolder); if (rootDirectory.isDirectory() == false) { if (log != null) { - log.error("%s does not exists.", parentFolder); + log.error(null, "%s does not exists.", parentFolder); } return; } @@ -109,7 +127,7 @@ public final class VmManager { File vmFolder = new File(parentFolder, name + ".avm"); if (vmFolder.exists()) { if (log != null) { - log.error("%s already exists.", vmFolder.getAbsolutePath()); + log.error(null, "%s already exists.", vmFolder.getAbsolutePath()); } return; } diff --git a/sdkmanager/libs/sdkuilib/.classpath b/sdkmanager/libs/sdkuilib/.classpath new file mode 100644 index 0000000..eb5af7e --- /dev/null +++ b/sdkmanager/libs/sdkuilib/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> + <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/sdkmanager/libs/sdkuilib/.project b/sdkmanager/libs/sdkuilib/.project new file mode 100644 index 0000000..da430c8 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>SdkUiLib</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java index ddc492e..fc951f2 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java @@ -40,6 +40,11 @@ import java.util.ArrayList; /** * The SDK target selector is a table that is added to the given parent composite. + * <p/> + * To use, create it using {@link #SdkTargetSelector(Composite, IAndroidTarget[], boolean)} then + * call {@link #setSelection(IAndroidTarget)}, {@link #setSelectionListener(SelectionListener)} + * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the + * selection. */ public class SdkTargetSelector { @@ -49,6 +54,14 @@ public class SdkTargetSelector { private Table mTable; private Label mDescription; + /** + * Creates a new SDK Target Selector. + * + * @param parent The parent composite where the selector will be added. + * @param targets The list of targets. This is <em>not</em> copied, the caller must not modify. + * @param allowMultipleSelection True if more than one SDK target can be selected at the same + * time. + */ public SdkTargetSelector(Composite parent, IAndroidTarget[] targets, boolean allowMultipleSelection) { mTargets = targets; @@ -81,14 +94,25 @@ public class SdkTargetSelector { column1.setText("Vendor"); final TableColumn column2 = new TableColumn(mTable, SWT.NONE); column2.setText("API Level"); + final TableColumn column3 = new TableColumn(mTable, SWT.NONE); + column3.setText("SDK"); - adjustColumnsWidth(mTable, column0, column1, column2); + adjustColumnsWidth(mTable, column0, column1, column2, column3); setupSelectionListener(mTable); fillTable(mTable); setupTooltip(mTable); } /** + * Returns the list of known targets. + * <p/> + * This is not a copy. Callers must <em>not</em> modify this array. + */ + public IAndroidTarget[] getTargets() { + return mTargets; + } + + /** * Sets a selection listener. Set it to null to remove it. * The listener will be called <em>after</em> this table processed its selection * events so that the caller can see the updated state. @@ -107,20 +131,33 @@ public class SdkTargetSelector { /** * Sets the current target selection. + * <p/> + * 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 * @return true if the target could be selected, false otherwise. */ public boolean setSelection(IAndroidTarget target) { boolean found = false; + boolean modified = false; for (TableItem i : mTable.getItems()) { if ((IAndroidTarget) i.getData() == target) { found = true; - i.setChecked(true); - } else { + if (!i.getChecked()) { + modified = true; + i.setChecked(true); + } + } else if (i.getChecked()) { + modified = true; i.setChecked(false); } } + if (modified && mSelectionListener != null) { + mSelectionListener.widgetSelected(null); + } + return found; } @@ -166,15 +203,17 @@ public class SdkTargetSelector { private void adjustColumnsWidth(final Table table, final TableColumn column0, final TableColumn column1, - final TableColumn column2) { + final TableColumn column2, + final TableColumn column3) { // Add a listener to resize the column to the full width of the table table.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle r = table.getClientArea(); - column0.setWidth(r.width * 3 / 10); // 30% - column1.setWidth(r.width * 5 / 10); // 50% - column2.setWidth(r.width * 2 / 10); // 20% + column0.setWidth(r.width * 30 / 100); // 30% + column1.setWidth(r.width * 45 / 100); // 45% + column2.setWidth(r.width * 15 / 100); // 15% + column3.setWidth(r.width * 10 / 100); // 10% } }); } @@ -238,6 +277,7 @@ public class SdkTargetSelector { * <li>column 0: sdk name * <li>column 1: sdk vendor * <li>column 2: sdk api name + * <li>column 3: sdk version * </ul> */ private void fillTable(final Table table) { @@ -249,6 +289,7 @@ public class SdkTargetSelector { item.setText(0, target.getName()); item.setText(1, target.getVendor()); item.setText(2, target.getApiVersionName()); + item.setText(3, Integer.toString(target.getApiVersionNumber())); } } else { table.setEnabled(false); @@ -257,6 +298,7 @@ public class SdkTargetSelector { item.setText(0, "--"); item.setText(1, "No target available"); item.setText(2, "--"); + item.setText(3, "--"); } } diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java new file mode 100644 index 0000000..dcc0b9e --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java @@ -0,0 +1,379 @@ +/* + * 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.sdkuilib; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.vm.VmManager.VmInfo; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +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.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +import java.util.ArrayList; + + +/** + * The VM selector is a table that is added to the given parent composite. + * <p/> + * To use, create it using {@link #VmSelector(Composite, VmInfo[], boolean)} then + * call {@link #setSelection(VmInfo)}, {@link #setSelectionListener(SelectionListener)} + * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the + * selection. + */ +public final class VmSelector { + + private VmInfo[] mVms; + private final boolean mAllowMultipleSelection; + private SelectionListener mSelectionListener; + private Table mTable; + private Label mDescription; + + /** + * Creates a new SDK Target Selector. + * + * @param parent The parent composite where the selector will be added. + * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify. + * @param allowMultipleSelection True if more than one SDK target can be selected at the same + * time. + */ + public VmSelector(Composite parent, VmInfo[] vms, boolean allowMultipleSelection) { + mVms = vms; + + // Layout has 1 column + Composite group = new Composite(parent, SWT.NONE); + group.setLayout(new GridLayout()); + group.setLayoutData(new GridData(GridData.FILL_BOTH)); + group.setFont(parent.getFont()); + + mAllowMultipleSelection = allowMultipleSelection; + mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER); + 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); + + mDescription = new Label(group, SWT.WRAP); + mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + // create the table columns + final TableColumn column0 = new TableColumn(mTable, SWT.NONE); + column0.setText("VM Name"); + final TableColumn column1 = new TableColumn(mTable, SWT.NONE); + column1.setText("Target Name"); + final TableColumn column2 = new TableColumn(mTable, SWT.NONE); + column2.setText("API Level"); + final TableColumn column3 = new TableColumn(mTable, SWT.NONE); + column3.setText("SDK"); + + adjustColumnsWidth(mTable, column0, column1, column2, column3); + setupSelectionListener(mTable); + fillTable(mTable, null /* target filter */); + setupTooltip(mTable); + } + + /** + * Sets a new set of VM, with an optional filter. + * <p/>This must be called from the UI thread. + * + * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify. + * @param filter An IAndroidTarget. If non-null, only VM whose target are compatible with the + * filter target will displayed an available for selection. + */ + public void setVms(VmInfo[] vms, IAndroidTarget filter) { + mVms = vms; + fillTable(mTable, filter); + } + + /** + * Returns the list of known Vms. + * <p/> + * This is not a copy. Callers must <em>not</em> modify this array. + */ + public VmInfo[] getVms() { + return mVms; + } + + /** + * Sets a selection listener. Set it to null to remove it. + * The listener will be called <em>after</em> this table processed its selection + * events so that the caller can see the updated state. + * <p/> + * 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()} and + * {@link #getAllSelected()} methods instead. + * + * @param selectionListener The new listener or null to remove it. + */ + public void setSelectionListener(SelectionListener selectionListener) { + mSelectionListener = selectionListener; + } + + /** + * Sets the current target selection. + * <p/> + * 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 + * @return true if the target could be selected, false otherwise. + */ + public boolean setSelection(VmInfo target) { + boolean found = false; + boolean modified = false; + for (TableItem i : mTable.getItems()) { + if ((VmInfo) i.getData() == target) { + found = true; + if (!i.getChecked()) { + modified = true; + i.setChecked(true); + } + } else if (i.getChecked()) { + modified = true; + i.setChecked(false); + } + } + + if (modified && mSelectionListener != null) { + mSelectionListener.widgetSelected(null); + } + + return found; + } + + /** + * Returns all selected items. + * This is useful when the table is in multiple-selection mode. + * + * @see #getFirstSelected() + * @return An array of selected items. The list can be empty but not null. + */ + public VmInfo[] getAllSelected() { + ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); + for (TableItem i : mTable.getItems()) { + if (i.getChecked()) { + list.add((IAndroidTarget) i.getData()); + } + } + return list.toArray(new VmInfo[list.size()]); + } + + /** + * Returns the first selected item. + * This is useful when the table is in single-selection mode. + * + * @see #getAllSelected() + * @return The first selected item or null. + */ + public VmInfo getFirstSelected() { + for (TableItem i : mTable.getItems()) { + if (i.getChecked()) { + return (VmInfo) i.getData(); + } + } + return null; + } + + /** + * Adds a listener to adjust the columns width when the parent is resized. + * <p/> + * If we need something more fancy, we might want to use this: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co + */ + private void adjustColumnsWidth(final Table table, + final TableColumn column0, + final TableColumn column1, + final TableColumn column2, + final TableColumn column3) { + // Add a listener to resize the column to the full width of the table + table.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = table.getClientArea(); + column0.setWidth(r.width * 30 / 100); // 30% + column1.setWidth(r.width * 45 / 100); // 45% + column2.setWidth(r.width * 15 / 100); // 15% + column3.setWidth(r.width * 10 / 100); // 10% + } + }); + } + + + /** + * Creates a selection listener that will check or uncheck the whole line when + * double-clicked (aka "the default selection"). + */ + private void setupSelectionListener(final Table table) { + // Add a selection listener that will check/uncheck items when they are double-clicked + table.addSelectionListener(new SelectionListener() { + /** Default selection means double-click on "most" platforms */ + public void widgetDefaultSelected(SelectionEvent e) { + if (e.item instanceof TableItem) { + TableItem i = (TableItem) e.item; + i.setChecked(!i.getChecked()); + enforceSingleSelection(i); + updateDescription(i); + } + + if (mSelectionListener != null) { + mSelectionListener.widgetDefaultSelected(e); + } + } + + public void widgetSelected(SelectionEvent e) { + if (e.item instanceof TableItem) { + TableItem i = (TableItem) e.item; + enforceSingleSelection(i); + updateDescription(i); + } + + if (mSelectionListener != null) { + mSelectionListener.widgetSelected(e); + } + } + + /** + * If we're not in multiple selection mode, uncheck all other + * items when this one is selected. + */ + private void enforceSingleSelection(TableItem item) { + if (!mAllowMultipleSelection && item.getChecked()) { + Table parentTable = item.getParent(); + for (TableItem i2 : parentTable.getItems()) { + if (i2 != item && i2.getChecked()) { + i2.setChecked(false); + } + } + } + } + }); + } + + /** + * Fills the table with all VM. + * The table columns are: + * <ul> + * <li>column 0: sdk name + * <li>column 1: sdk vendor + * <li>column 2: sdk api name + * <li>column 3: sdk version + * </ul> + */ + private void fillTable(final Table table, IAndroidTarget filter) { + table.removeAll(); + if (mVms != null && mVms.length > 0) { + table.setEnabled(true); + for (VmInfo vm : mVms) { + if (filter == null || filter.isCompatibleBaseFor(vm.getTarget())) { + TableItem item = new TableItem(table, SWT.NONE); + item.setData(vm); + item.setText(0, vm.getName()); + IAndroidTarget target = vm.getTarget(); + item.setText(1, target.getFullName()); + item.setText(2, target.getApiVersionName()); + item.setText(3, Integer.toString(target.getApiVersionNumber())); + } + } + } + + if (table.getItemCount() == 0) { + table.setEnabled(false); + TableItem item = new TableItem(table, SWT.NONE); + item.setData(null); + item.setText(0, "--"); + item.setText(1, "No VM available"); + item.setText(2, "--"); + item.setText(3, "--"); + } + } + + /** + * Sets up a tooltip that displays the current item description. + * <p/> + * Displaying a tooltip over the table looks kind of odd here. Instead we actually + * display the description in a label under the table. + */ + private void setupTooltip(final Table table) { + /* + * Reference: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup + */ + + final Listener listener = new Listener() { + public void handleEvent(Event event) { + + switch(event.type) { + case SWT.KeyDown: + case SWT.MouseExit: + case SWT.MouseDown: + return; + + case SWT.MouseHover: + updateDescription(table.getItem(new Point(event.x, event.y))); + break; + + case SWT.Selection: + if (event.item instanceof TableItem) { + updateDescription((TableItem) event.item); + } + break; + + default: + return; + } + + } + }; + + table.addListener(SWT.Dispose, listener); + table.addListener(SWT.KeyDown, listener); + table.addListener(SWT.MouseMove, listener); + table.addListener(SWT.MouseHover, listener); + } + + /** + * Updates the description label with the path of the item's VM, if any. + */ + private void updateDescription(TableItem item) { + if (item != null) { + Object data = item.getData(); + if (data instanceof VmInfo) { + String newTooltip = ((VmInfo) data).getPath(); + mDescription.setText(newTooltip == null ? "" : newTooltip); //$NON-NLS-1$ + } + } + } +} |