auto import from //depot/cupcake/@135843
diff --git a/tools/sdkmanager/libs/Android.mk b/tools/sdkmanager/libs/Android.mk
new file mode 100644
index 0000000..a934aa7
--- /dev/null
+++ b/tools/sdkmanager/libs/Android.mk
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+SDKLIBS_LOCAL_DIR := $(call my-dir)
+include $(SDKLIBS_LOCAL_DIR)/sdklib/Android.mk
+include $(SDKLIBS_LOCAL_DIR)/sdkuilib/Android.mk
diff --git a/tools/sdkmanager/libs/sdklib/.classpath b/tools/sdkmanager/libs/sdklib/.classpath
new file mode 100644
index 0000000..fc17a43
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/.classpath
@@ -0,0 +1,7 @@
+<?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="/AndroidPrefs"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/sdkmanager/libs/sdklib/.project b/tools/sdkmanager/libs/sdklib/.project
new file mode 100644
index 0000000..97a8578
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>SdkLib</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/tools/sdkmanager/libs/sdklib/Android.mk b/tools/sdkmanager/libs/sdklib/Android.mk
new file mode 100644
index 0000000..509c573
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/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.
+#
+SDKLIB_LOCAL_DIR := $(call my-dir)
+include $(SDKLIB_LOCAL_DIR)/src/Android.mk
diff --git a/tools/sdkmanager/libs/sdklib/src/Android.mk b/tools/sdkmanager/libs/sdklib/src/Android.mk
new file mode 100644
index 0000000..a059a46
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/Android.mk
@@ -0,0 +1,27 @@
+#
+# 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 := \
+        androidprefs
+
+LOCAL_MODULE := sdklib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
new file mode 100644
index 0000000..0a59107
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
@@ -0,0 +1,258 @@
+/*
+ * 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.sdklib;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Represents an add-on target in the SDK.
+ * An add-on extends a standard {@link PlatformTarget}.
+ */
+final class AddOnTarget implements IAndroidTarget {
+    /**
+     * String to compute hash for add-on targets.
+     * Format is vendor:name:apiVersion
+     * */
+    private final static String ADD_ON_FORMAT = "%s:%s:%d"; //$NON-NLS-1$
+    
+    private final static class OptionalLibrary implements IOptionalLibrary {
+        private final String mJarName;
+        private final String mJarPath;
+        private final String mName;
+        private final String mDescription;
+
+        OptionalLibrary(String jarName, String jarPath, String name, String description) {
+            mJarName = jarName;
+            mJarPath = jarPath;
+            mName = name;
+            mDescription = description;
+        }
+
+        public String getJarName() {
+            return mJarName;
+        }
+
+        public String getJarPath() {
+            return mJarPath;
+        }
+
+        public String getName() {
+            return mName;
+        }
+        
+        public String getDescription() {
+            return mDescription;
+        }
+    }
+    
+    private final String mLocation;
+    private final PlatformTarget mBasePlatform;
+    private final String mName;
+    private final String mVendor;
+    private final String mDescription;
+    private String[] mSkins;
+    private String mDefaultSkin;
+    private IOptionalLibrary[] mLibraries;
+
+    /**
+     * Creates a new add-on
+     * @param location the OS path location of the add-on
+     * @param name the name of the add-on
+     * @param vendor the vendor name of the add-on
+     * @param description the add-on description
+     * @param libMap A map containing the optional libraries. The map key is the fully-qualified
+     * library name. The value is a 2 string array with the .jar filename, and the description.
+     * @param basePlatform the platform the add-on is extending.
+     */
+    AddOnTarget(String location, String name, String vendor, String description,
+            Map<String, String[]> libMap, PlatformTarget basePlatform) {
+        if (location.endsWith(File.separator) == false) {
+            location = location + File.separator;
+        }
+
+        mLocation = location;
+        mName = name;
+        mVendor = vendor;
+        mDescription = description;
+        mBasePlatform = basePlatform;
+        
+        // handle the optional libraries.
+        if (libMap != null) {
+            mLibraries = new IOptionalLibrary[libMap.size()];
+            int index = 0;
+            for (Entry<String, String[]> entry : libMap.entrySet()) {
+                String jarFile = entry.getValue()[0];
+                String desc = entry.getValue()[1];
+                mLibraries[index++] = new OptionalLibrary(jarFile,
+                        mLocation + SdkConstants.OS_ADDON_LIBS_FOLDER + jarFile,
+                        entry.getKey(), desc);
+            }
+        }
+    }
+    
+    public String getLocation() {
+        return mLocation;
+    }
+    
+    public String getName() {
+        return mName;
+    }
+    
+    public String getVendor() {
+        return mVendor;
+    }
+    
+    public String getFullName() {
+        return String.format("%1$s (%2$s)", mName, mVendor);
+    }
+    
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public String getApiVersionName() {
+        // this is always defined by the base platform
+        return mBasePlatform.getApiVersionName();
+    }
+
+    public int getApiVersionNumber() {
+        // this is always defined by the base platform
+        return mBasePlatform.getApiVersionNumber();
+    }
+    
+    public boolean isPlatform() {
+        return false;
+    }
+    
+    public IAndroidTarget getParent() {
+        return mBasePlatform;
+    }
+    
+    public String getPath(int pathId) {
+        switch (pathId) {
+            case IMAGES:
+                return mLocation + SdkConstants.OS_IMAGES_FOLDER;
+            case SKINS:
+                return mLocation + SdkConstants.OS_SKINS_FOLDER;
+            case DOCS:
+                return mLocation + SdkConstants.FD_DOCS + File.separator;
+            default :
+                return mBasePlatform.getPath(pathId);
+        }
+    }
+
+    public String[] getSkins() {
+        return mSkins;
+    }
+    
+    public String getDefaultSkin() {
+        return mDefaultSkin;
+    }
+
+    public IOptionalLibrary[] getOptionalLibraries() {
+        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());
+    }
+    
+    @Override
+    public int hashCode() {
+        return hashString().hashCode();
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof AddOnTarget) {
+            AddOnTarget addon = (AddOnTarget)obj;
+            
+            return mVendor.equals(addon.mVendor) && mName.equals(addon.mName) &&
+                mBasePlatform.getApiVersionNumber() == addon.mBasePlatform.getApiVersionNumber();
+        }
+
+        return super.equals(obj);
+    }
+    
+    /*
+     * Always return +1 if the object we compare to is a platform.
+     * Otherwise, do vendor then name then api version comparison.
+     * (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(IAndroidTarget target) {
+        if (target.isPlatform()) {
+            return +1;
+        }
+        
+        // vendor
+        int value = mVendor.compareTo(target.getVendor());
+
+        // name
+        if (value == 0) {
+            value = mName.compareTo(target.getName());
+        }
+        
+        // api version
+        if (value == 0) {
+            value = getApiVersionNumber() - target.getApiVersionNumber();
+        }
+        
+        return value;
+    }
+
+    
+    // ---- local methods.
+
+
+    public void setSkins(String[] skins, String defaultSkin) {
+        mDefaultSkin = defaultSkin;
+
+        // we mix the add-on and base platform skins
+        HashSet<String> skinSet = new HashSet<String>();
+        skinSet.addAll(Arrays.asList(skins));
+        skinSet.addAll(Arrays.asList(mBasePlatform.getSkins()));
+        
+        mSkins = skinSet.toArray(new String[skinSet.size()]);
+    }
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
new file mode 100644
index 0000000..fa462bd
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
@@ -0,0 +1,163 @@
+/*
+ * 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.sdklib;
+
+
+/**
+ * A version of Android that application can target when building. 
+ */
+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;
+    /** OS Path to the target specific docs */
+    public static int DOCS                = 19;
+    /** OS Path to the target's version of the aapt tool. */
+    public static int AAPT                = 20;
+    /** OS Path to the target's version of the aidl tool. */
+    public static int AIDL                = 21;
+    /** OS Path to the target's version of the dx too. */
+    public static int DX                  = 22;
+    /** OS Path to the target's version of the dx.jar file. */
+    public static int DX_JAR              = 23;
+    
+    public interface IOptionalLibrary {
+        String getName();
+        String getJarName();
+        String getJarPath();
+        String getDescription();
+    }
+
+    /**
+     * Returns the target location.
+     */
+    String getLocation();
+
+    /**
+     * Returns the name of the vendor of the target.
+     */
+    String getVendor();
+
+    /**
+     * Returns the name of the target.
+     */
+    String getName();
+    
+    /**
+     * Returns the full name of the target, possibly including vendor name.
+     */
+    String getFullName();
+    
+    /**
+     * Returns the description of the target.
+     */
+    String getDescription();
+    
+    /**
+     * Returns the api version as an integer.
+     */
+    int getApiVersionNumber();
+
+    /**
+     * Returns the platform version as a readable string.
+     */
+    String getApiVersionName();
+    
+    /**
+     * Returns true if the target is a standard Android platform.
+     */
+    boolean isPlatform();
+    
+    /**
+     * Returns the parent target. This is likely to only be non <code>null</code> if
+     * {@link #isPlatform()} returns <code>false</code>
+     */
+    IAndroidTarget getParent();
+    
+    /**
+     * Returns the path of a platform component.
+     * @param pathId the id representing the path to return. Any of the constants defined in the
+     * {@link IAndroidTarget} interface can be used.
+     */
+    String getPath(int pathId);
+    
+    /**
+     * Returns the available skins for this target.
+     */
+    String[] getSkins();
+    
+    /**
+     * Returns the default skin for this target.
+     */
+    String getDefaultSkin();
+    
+    /**
+     * Returns the available optional libraries for this target.
+     * @return an array of optional libraries or <code>null</code> if there is none.
+     */
+    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.
+     */
+    String hashString();
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
new file mode 100644
index 0000000..4894517
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
@@ -0,0 +1,63 @@
+/*
+ * 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.sdklib;
+
+import java.util.Formatter;
+
+/**
+ * Interface used to display warnings/errors while parsing the SDK content.
+ */
+public interface ISdkLog {
+    
+    /**
+     * Prints a warning message on stdout.
+     * <p/>
+     * Implementations should only display warnings in verbose mode.
+     * The message should be prefixed with "Warning:".
+     * 
+     * @param warningFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for warningFormat.
+     */
+    void warning(String warningFormat, Object... args);
+    
+    /**
+     * Prints an error message on stderr.
+     * <p/>
+     * Implementation should always display errors, independent of verbose mode.
+     * The message should be prefixed with "Error:".
+     * 
+     * @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's
+     *          message will be printed out.
+     * @param errorFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for errorFormat.
+     */
+    void error(Throwable t, String errorFormat, Object... args);
+    
+    /**
+     * Prints a message as-is on stdout.
+     * <p/>
+     * Implementation should always display errors, independent of verbose mode.
+     * No prefix is used, the message is printed as-is after formatting.
+     * 
+     * @param msgFormat is an optional error format. If non-null, it will be printed
+     *          using a {@link Formatter} with the provided arguments.
+     * @param args provides the arguments for msgFormat.
+     */
+    void printf(String msgFormat, Object... args);
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
new file mode 100644
index 0000000..a3da70e
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -0,0 +1,214 @@
+/*
+ * 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.sdklib;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a platform target in the SDK. 
+ */
+final class PlatformTarget implements IAndroidTarget {
+    /** String used to get a hash to the platform target */
+    private final static String PLATFORM_HASH = "android-%d";
+    
+    private final static String PLATFORM_VENDOR = "Android";
+    private final static String PLATFORM_NAME = "Android %s";
+
+    private final String mLocation;
+    private final String mName;
+    private final int mApiVersionNumber;
+    private final String mApiVersionName;
+    private final Map<String, String> mProperties;
+    private final Map<Integer, String> mPaths = new HashMap<Integer, String>();
+    private String[] mSkins;
+    
+    PlatformTarget(String location, Map<String, String> properties,
+            int apiNumber, String apiName) {
+        mName = String.format(PLATFORM_NAME, apiName);
+        if (location.endsWith(File.separator) == false) {
+            location = location + File.separator;
+        }
+        mLocation = location;
+        mProperties = Collections.unmodifiableMap(properties);
+        mApiVersionNumber = apiNumber;
+        mApiVersionName = apiName;
+        
+        // pre-build the path to the platform components
+        mPaths.put(ANDROID_JAR, mLocation + SdkConstants.FN_FRAMEWORK_LIBRARY);
+        mPaths.put(SOURCES, mLocation + SdkConstants.FD_ANDROID_SOURCES);
+        mPaths.put(ANDROID_AIDL, mLocation + SdkConstants.FN_FRAMEWORK_AIDL);
+        mPaths.put(IMAGES, mLocation + SdkConstants.OS_IMAGES_FOLDER);
+        mPaths.put(SAMPLES, mLocation + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER);
+        mPaths.put(SKINS, mLocation + SdkConstants.OS_SKINS_FOLDER);
+        mPaths.put(TEMPLATES, mLocation + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER);
+        mPaths.put(DATA, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER);
+        mPaths.put(ATTRIBUTES, mLocation + SdkConstants.OS_PLATFORM_ATTRS_XML);
+        mPaths.put(MANIFEST_ATTRIBUTES, mLocation + SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML);
+        mPaths.put(RESOURCES, mLocation + SdkConstants.OS_PLATFORM_RESOURCES_FOLDER);
+        mPaths.put(FONTS, mLocation + SdkConstants.OS_PLATFORM_FONTS_FOLDER);
+        mPaths.put(LAYOUT_LIB, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_LAYOUTLIB_JAR);
+        mPaths.put(WIDGETS, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_WIDGETS);
+        mPaths.put(ACTIONS_ACTIVITY, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_INTENT_ACTIONS_ACTIVITY);
+        mPaths.put(ACTIONS_BROADCAST, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_INTENT_ACTIONS_BROADCAST);
+        mPaths.put(ACTIONS_SERVICE, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_INTENT_ACTIONS_SERVICE);
+        mPaths.put(CATEGORIES, mLocation + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+                SdkConstants.FN_INTENT_CATEGORIES);
+        mPaths.put(AAPT, mLocation + SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AAPT);
+        mPaths.put(AIDL, mLocation + SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AIDL);
+        mPaths.put(DX, mLocation + SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_DX);
+        mPaths.put(DX_JAR, mLocation + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER +
+                SdkConstants.FN_DX_JAR);
+    }
+    
+    public String getLocation() {
+        return mLocation;
+    }
+    
+    /*
+     * (non-Javadoc)
+     * 
+     * For Platform, the vendor name is always "Android".
+     * 
+     * @see com.android.sdklib.IAndroidTarget#getVendor()
+     */
+    public String getVendor() {
+        return PLATFORM_VENDOR;
+    }
+
+    public String getName() {
+        return mName;
+    }
+    
+    public String getFullName() {
+        return mName;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * Description for the Android platform is dynamically generated.
+     * 
+     * @see com.android.sdklib.IAndroidTarget#getDescription()
+     */
+    public String getDescription() {
+        return String.format("Standard Android platform %s", mApiVersionName);
+    }
+    
+    public int getApiVersionNumber(){
+        return mApiVersionNumber;
+    }
+    
+    public String getApiVersionName() {
+        return mApiVersionName;
+    }
+    
+    public boolean isPlatform() {
+        return true;
+    }
+    
+    public IAndroidTarget getParent() {
+        return null;
+    }
+    
+    public String getPath(int pathId) {
+        return mPaths.get(pathId);
+    }
+    
+    public String[] getSkins() {
+        return mSkins;
+    }
+    
+    public String getDefaultSkin() {
+        // at this time, this is the default skin for all the platform.
+        return "HVGA";
+    }
+
+    /*
+     * Always returns null, as a standard platforms have no optional libraries.
+     * 
+     * (non-Javadoc)
+     * @see com.android.sdklib.IAndroidTarget#getOptionalLibraries()
+     */
+    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);
+    }
+
+    @Override
+    public int hashCode() {
+        return hashString().hashCode();
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof PlatformTarget) {
+            return mApiVersionNumber == ((PlatformTarget)obj).mApiVersionNumber;
+        }
+        
+        return super.equals(obj);
+    }
+
+    /*
+     * Always return -1 if the object we compare to is an addon.
+     * Otherwise, compare api level.
+     * (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(IAndroidTarget target) {
+        if (target.isPlatform() == false) {
+            return -1;
+        }
+
+        return mApiVersionNumber - target.getApiVersionNumber();
+    }
+
+    // ---- platform only methods.
+    
+    public String getProperty(String name) {
+        return mProperties.get(name);
+    }
+    
+    public Map<String, String> getProperties() {
+        return mProperties; // mProperties is unmodifiable.
+    }
+
+    void setSkins(String[] skins) {
+        mSkins = skins;
+    }
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
new file mode 100644
index 0000000..00594d1
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+
+import java.io.File;
+
+/**
+ * Constant definition class.<br>
+ * <br>
+ * Most constants have a prefix defining the content.
+ * <ul>
+ * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
+ * <li><code>FN_</code> File name constant.</li>
+ * <li><code>FD_</code> Folder name constant.</li>
+ * </ul>
+ *
+ */
+public final class SdkConstants {
+    public final static int PLATFORM_UNKNOWN = 0;
+    public final static int PLATFORM_LINUX = 1;
+    public final static int PLATFORM_WINDOWS = 2;
+    public final static int PLATFORM_DARWIN = 3;
+
+    /**
+     * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
+     * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
+     */
+    public final static int CURRENT_PLATFORM = currentPlatform();
+
+    
+    /** An SDK Project's AndroidManifest.xml file */
+    public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml";
+    /** An SDK Project's build.xml file */
+    public final static String FN_BUILD_XML = "build.xml";
+
+    /** Name of the framework library, i.e. "android.jar" */
+    public static final String FN_FRAMEWORK_LIBRARY = "android.jar";
+    /** Name of the layout attributes, i.e. "attrs.xml" */
+    public static final String FN_ATTRS_XML = "attrs.xml";
+    /** Name of the layout attributes, i.e. "attrs_manifest.xml" */
+    public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml";
+    /** framework aidl import file */
+    public static final String FN_FRAMEWORK_AIDL = "framework.aidl";
+    /** layoutlib.jar file */
+    public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar";
+    /** widget list file */
+    public static final String FN_WIDGETS = "widgets.txt";
+    /** Intent activity actions list file */
+    public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt";
+    /** Intent broadcast actions list file */
+    public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt";
+    /** Intent service actions list file */
+    public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt";
+    /** Intent category list file */
+    public static final String FN_INTENT_CATEGORIES = "categories.txt";
+
+    /** platform build property file */
+    public final static String FN_BUILD_PROP = "build.prop";
+    /** plugin properties file */
+    public final static String FN_PLUGIN_PROP = "plugin.prop";
+    /** add-on manifest file */
+    public final static String FN_MANIFEST_INI = "manifest.ini";
+    /** hardware properties definition file */
+    public final static String FN_HARDWARE_INI = "hardware-properties.ini";
+
+    /** Skin layout file */
+    public final static String FN_SKIN_LAYOUT = "layout";//$NON-NLS-1$
+
+    /** dex.jar file */
+    public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
+
+    /** dx executable */
+    public final static String FN_DX = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
+            "dx.bat" : "dx"; //$NON-NLS-1$ //$NON-NLS-2$
+
+    /** aapt executable */
+    public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
+            "aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$
+
+    /** aidl executable */
+    public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
+            "aidl.exe" : "aidl"; //$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 generated source folder name, i.e. "gen" */
+    public final static String FD_GEN_SOURCES = "gen"; //$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. */
+    public final static String FD_PLATFORMS = "platforms";
+    /** Name of the SDK addons folder. */
+    public final static String FD_ADDONS = "add-ons";
+    /** Name of the SDK tools folder. */
+    public final static String FD_TOOLS = "tools";
+    /** Name of the SDK tools/lib folder. */
+    public final static String FD_LIB = "lib";
+    /** Name of the SDK docs folder. */
+    public final static String FD_DOCS = "docs";
+    /** Name of the SDK images folder. */
+    public final static String FD_IMAGES = "images";
+    /** Name of the SDK skins folder. */
+    public final static String FD_SKINS = "skins";
+    /** Name of the SDK samples folder. */
+    public final static String FD_SAMPLES = "samples";
+    /** Name of the SDK templates folder, i.e. "templates" */
+    public final static String FD_TEMPLATES = "templates";
+    /** Name of the SDK data folder, i.e. "data" */
+    public final static String FD_DATA = "data";
+    /** Name of the SDK resources folder, i.e. "res" */
+    public final static String FD_RES = "res";
+    /** Name of the SDK font folder, i.e. "fonts" */
+    public final static String FD_FONTS = "fonts";
+    /** 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";
+
+    /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
+    public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
+
+    /* 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. */
+    public final static String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator;
+
+    /** Path of the tools directory relative to the sdk folder, or to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator;
+
+    /** Path of the lib directory relative to the sdk folder, or to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_SDK_TOOLS_LIB_FOLDER =
+            OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator;
+
+    /* Folder paths relative to a platform or add-on folder */
+    
+    /** Path of the images directory relative to a platform or addon folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_IMAGES_FOLDER = FD_IMAGES + File.separator;
+
+    /** Path of the skin directory relative to a platform or addon folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_SKINS_FOLDER = FD_SKINS + File.separator;
+
+    /* Folder paths relative to a Platform folder */
+
+    /** Path of the data directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator;
+
+    /** Path of the samples directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator;
+
+    /** Path of the resources directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_RESOURCES_FOLDER =
+            OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator;
+
+    /** Path of the fonts directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_FONTS_FOLDER =
+            OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator;
+
+    /** Path of the android source directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator;
+
+    /** Path of the android templates directory relative to a platform folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator;
+
+    /** Path of the attrs.xml file relative to a platform folder. */
+    public final static String OS_PLATFORM_ATTRS_XML =
+            OS_PLATFORM_RESOURCES_FOLDER + FD_VALUES + File.separator + FN_ATTRS_XML;
+
+    /** Path of the attrs_manifest.xml file relative to a platform folder. */
+    public final static String OS_PLATFORM_ATTRS_MANIFEST_XML =
+            OS_PLATFORM_RESOURCES_FOLDER + FD_VALUES + File.separator + FN_ATTRS_MANIFEST_XML;
+
+    /** Path of the layoutlib.jar file relative to a platform folder. */
+    public final static String OS_PLATFORM_LAYOUTLIB_JAR =
+            OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR;
+    
+    /* Folder paths relative to a addon folder */
+
+    /** Path of the images directory relative to a folder folder.
+     *  This is an OS path, ending with a separator. */
+    public final static String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
+    
+    
+    /** Skin default **/
+    public final static String SKIN_DEFAULT = "default";
+
+    /** Returns the appropriate name for the 'android' command, which is 'android.bat' for
+     * Windows and 'android' for all other platforms. */
+    public static String androidCmdName() {
+        String os = System.getProperty("os.name");
+        String cmd = "android";
+        if (os.startsWith("Windows")) {
+            cmd += ".bat";
+        }
+        return cmd;
+    }
+
+    /** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for
+     * Windows and 'mkdsdcard' for all other platforms. */
+    public static String mkSdCardCmdName() {
+        String os = System.getProperty("os.name");
+        String cmd = "mksdcard";
+        if (os.startsWith("Windows")) {
+            cmd += ".exe";
+        }
+        return cmd;
+    }
+
+    /**
+     * Returns current platform
+     * 
+     * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
+     * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
+     */
+    private static int currentPlatform() {
+        String os = System.getProperty("os.name");          //$NON-NLS-1$
+        if (os.startsWith("Mac OS")) {                      //$NON-NLS-1$
+            return PLATFORM_DARWIN;
+        } else if (os.startsWith("Windows")) {              //$NON-NLS-1$
+            return PLATFORM_WINDOWS;
+        } else if (os.startsWith("Linux")) {                //$NON-NLS-1$
+            return PLATFORM_LINUX;
+        }
+
+        return PLATFORM_UNKNOWN;
+    }
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
new file mode 100644
index 0000000..28227c6
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -0,0 +1,511 @@
+/*
+ * 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.sdklib;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The SDK manager parses the SDK folder and gives access to the content.
+ * @see PlatformTarget
+ * @see AddOnTarget 
+ */
+public final class SdkManager {
+    
+    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";
+    private final static String ADDON_API = "api";
+    private final static String ADDON_DESCRIPTION = "description";
+    private final static String ADDON_LIBRARIES = "libraries";
+    private final static String ADDON_DEFAULT_SKIN = "skin";
+    
+    private final static Pattern PATTERN_PROP = Pattern.compile(
+            "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
+    
+    private final static Pattern PATTERN_LIB_DATA = Pattern.compile(
+            "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE);
+    
+    /** List of items in the platform to check when parsing it. These paths are relative to the
+     * platform root folder. */
+    private final static String[] sPlatformContentList = new String[] {
+        SdkConstants.FN_FRAMEWORK_LIBRARY,
+        SdkConstants.FN_FRAMEWORK_AIDL,
+        SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AAPT,
+        SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AIDL,
+        SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_DX,
+        SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR,
+    };
+
+    /** the location of the SDK */
+    private final String mSdkLocation;
+    private IAndroidTarget[] mTargets;
+
+    /**
+     * Creates an {@link SdkManager} for a given sdk location.
+     * @param sdkLocation the location of the SDK.
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     * @return the created {@link SdkManager} or null if the location is not valid.
+     */
+    public static SdkManager createManager(String sdkLocation, ISdkLog log) {
+        try {
+            SdkManager manager = new SdkManager(sdkLocation);
+            ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+            manager.loadPlatforms(list, log);
+            manager.loadAddOns(list, log);
+            
+            // sort the targets/add-ons
+            Collections.sort(list);
+            
+            manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
+            
+            return manager;
+        } catch (IllegalArgumentException e) {
+            if (log != null) {
+                log.error(e, "Error parsing the sdk.");
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Returns the location of the SDK.
+     */
+    public String getLocation() {
+        return mSdkLocation;
+    }
+    
+    /**
+     * Returns the targets that are available in the SDK.
+     */
+    public IAndroidTarget[] getTargets() {
+        return mTargets;
+    }
+    
+    /**
+     * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
+     * 
+     * @param hash the {@link IAndroidTarget} hash string.
+     * @return The matching {@link IAndroidTarget} or null.
+     */
+    public IAndroidTarget getTargetFromHashString(String hash) {
+        if (hash != null) {
+            for (IAndroidTarget target : mTargets) {
+                if (hash.equals(target.hashString())) {
+                    return target;
+                }
+            }
+        }
+
+        return null;
+    }
+
+
+    private SdkManager(String sdkLocation) {
+        mSdkLocation = sdkLocation;
+    }
+    
+    private void setTargets(IAndroidTarget[] targets) {
+        mTargets = targets;
+    }
+
+    /**
+     * Loads the Platforms from the SDK.
+     * @param list the list to fill with the platforms.
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     */
+    private void loadPlatforms(ArrayList<IAndroidTarget> list, ISdkLog log) {
+        File platformFolder = new File(mSdkLocation, SdkConstants.FD_PLATFORMS);
+        if (platformFolder.isDirectory()) {
+            File[] platforms  = platformFolder.listFiles();
+            
+            for (File platform : platforms) {
+                if (platform.isDirectory()) {
+                    PlatformTarget target = loadPlatform(platform, log);
+                    if (target != null) {
+                        list.add(target);
+                    }
+                } else if (log != null) {
+                    log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
+                }
+            }
+            
+            return;
+        }
+
+        String message = null;
+        if (platformFolder.exists() == false) {
+            message = "%s is missing.";
+        } else {
+            message = "%s is not a folder.";
+        }
+
+        throw new IllegalArgumentException(String.format(message,
+                platformFolder.getAbsolutePath()));
+    }
+
+    /**
+     * Loads a specific Platform at a given location.
+     * @param platform the location of the platform.
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     */
+    private PlatformTarget loadPlatform(File platform, ISdkLog log) {
+        File buildProp = new File(platform, SdkConstants.FN_BUILD_PROP);
+        
+        if (buildProp.isFile()) {
+            Map<String, String> map = parsePropertyFile(buildProp, log);
+            
+            if (map != null) {
+                // look for some specific values in the map.
+                try {
+                    String apiNumber = map.get(PROP_VERSION_SDK);
+                    String apiName = map.get(PROP_VERSION_RELEASE);
+                    if (apiNumber != null && apiName != null) {
+                        // api number and name looks valid, perform a few more checks
+                        if (checkPlatformContent(platform, log) == false) {
+                            return null;
+                        }
+                        // create the target.
+                        PlatformTarget target = new PlatformTarget(
+                                platform.getAbsolutePath(),
+                                map,
+                                Integer.parseInt(apiNumber),
+                                apiName);
+                        
+                        // need to parse the skins.
+                        String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
+                        target.setSkins(skins);
+
+                        return target;
+                    }
+                } catch (NumberFormatException e) {
+                    // looks like apiNumber does not parse to a number.
+                    // Ignore this platform.
+                    if (log != null) {
+                        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(null, "Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
+                    SdkConstants.FN_BUILD_PROP);
+        }
+        
+        return null;
+    }
+
+    /**
+     * Loads the Add-on from the SDK.
+     * @param list the list to fill with the add-ons.
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     */
+    private void loadAddOns(ArrayList<IAndroidTarget> list, ISdkLog log) {
+        File addonFolder = new File(mSdkLocation, SdkConstants.FD_ADDONS);
+        if (addonFolder.isDirectory()) {
+            File[] addons  = addonFolder.listFiles();
+            
+            for (File addon : addons) {
+                // Add-ons have to be folders. Ignore files and no need to warn about them.
+                if (addon.isDirectory()) {
+                    AddOnTarget target = loadAddon(addon, list, log);
+                    if (target != null) {
+                        list.add(target);
+                    }
+                }
+            }
+
+            return;
+        }
+
+        String message = null;
+        if (addonFolder.exists() == false) {
+            message = "%s is missing.";
+        } else {
+            message = "%s is not a folder.";
+        }
+
+        throw new IllegalArgumentException(String.format(message,
+                addonFolder.getAbsolutePath()));
+    }
+    
+    /**
+     * Loads a specific Add-on at a given location.
+     * @param addon the location of the addon.
+     * @param list 
+     * @param log 
+     */
+    private AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> list, ISdkLog log) {
+        File addOnManifest = new File(addon, SdkConstants.FN_MANIFEST_INI);
+        
+        if (addOnManifest.isFile()) {
+            Map<String, String> propertyMap = parsePropertyFile(addOnManifest, log);
+            
+            if (propertyMap != null) {
+                // look for some specific values in the map.
+                // we require name, vendor, and api
+                String name = propertyMap.get(ADDON_NAME);
+                if (name == null) {
+                    displayAddonManifestError(log, addon.getName(), ADDON_NAME);
+                    return null;
+                }
+                
+                String vendor = propertyMap.get(ADDON_VENDOR);
+                if (vendor == null) {
+                    displayAddonManifestError(log, addon.getName(), ADDON_VENDOR);
+                    return null;
+                }
+
+                String api = propertyMap.get(ADDON_API);
+                PlatformTarget baseTarget = null;
+                if (api == null) {
+                    displayAddonManifestError(log, addon.getName(), ADDON_API);
+                    return null;
+                } else {
+                    try {
+                        int apiValue = Integer.parseInt(api);
+                        for (IAndroidTarget target : list) {
+                            if (target.isPlatform() &&
+                                    target.getApiVersionNumber() == apiValue) {
+                                baseTarget = (PlatformTarget)target;
+                                break;
+                            }
+                        }
+                        
+                        if (baseTarget == null) {
+                            if (log != null) {
+                                log.error(null,
+                                        "Ignoring add-on '%1$s': Unable to find base platform with API level %2$d",
+                                        addon.getName(), apiValue);
+                            }
+
+                            return null;
+                        }
+                    } catch (NumberFormatException e) {
+                        // looks like apiNumber does not parse to a number.
+                        // Ignore this add-on.
+                        if (log != null) {
+                            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);
+                        }
+                        return null;
+                    }
+                }
+                
+                // get the optional description
+                String description = propertyMap.get(ADDON_DESCRIPTION);
+                
+                // get the optional libraries
+                String librariesValue = propertyMap.get(ADDON_LIBRARIES);
+                Map<String, String[]> libMap = null;
+                
+                if (librariesValue != null) {
+                    librariesValue = librariesValue.trim();
+                    if (librariesValue.length() > 0) {
+                        // split in the string into the libraries name
+                        String[] libraries = librariesValue.split(";");
+                        if (libraries.length > 0) {
+                            libMap = new HashMap<String, String[]>();
+                            for (String libName : libraries) {
+                                libName = libName.trim();
+
+                                // get the library data from the properties
+                                String libData = propertyMap.get(libName);
+                                
+                                if (libData != null) {
+                                    // split the jar file from the description
+                                    Matcher m = PATTERN_LIB_DATA.matcher(libData);
+                                    if (m.matches()) {
+                                        libMap.put(libName, new String[] {
+                                                m.group(1), m.group(2) });
+                                    } else if (log != null) {
+                                        log.error(null,
+                                                "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
+                                                libName, libData);
+                                    }
+                                } else if (log != null) {
+                                    log.error(null,
+                                            "Ignoring library '%1$s', missing property value",
+                                            libName, libData);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                AddOnTarget target = new AddOnTarget(addon.getAbsolutePath(), name, vendor,
+                        description, libMap, baseTarget);
+                
+                // need to parse the skins.
+                String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
+                
+                // get the default skin, or take it from the base platform if needed.
+                String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
+                
+                if (defaultSkin == null) {
+                    if (skins.length == 1) {
+                        defaultSkin = skins[1];
+                    } else {
+                        defaultSkin = baseTarget.getDefaultSkin();
+                    }
+                }
+                
+                target.setSkins(skins, defaultSkin);
+
+                return target;
+            }
+        } else if (log != null) {
+            log.error(null, "Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
+                    SdkConstants.FN_MANIFEST_INI);
+        }
+        
+        return null;
+    }
+    
+    private void displayAddonManifestError(ISdkLog log, String addonName, String valueName) {
+        if (log != null) {
+            log.error(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
+                    addonName, valueName, SdkConstants.FN_MANIFEST_INI);
+        }
+    }
+    
+    /**
+     * Checks the given platform has all the required files, and returns true if they are all
+     * present.
+     * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
+     * aidl(.exe), dx(.bat), and dx.jar
+     */
+    private boolean checkPlatformContent(File platform, ISdkLog log) {
+        for (String relativePath : sPlatformContentList) {
+            File f = new File(platform, relativePath);
+            if (f.exists() == false) {
+                log.error(null,
+                        "Ignoring platform '%1$s': %2$s is missing.",
+                        platform.getName(), relativePath);
+                return false;
+            }
+            
+        }
+        return true;
+    }
+
+    
+    /**
+     * Parses a property file and returns
+     * @param buildProp the property file to parse
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     * @return the map of (key,value) pairs, or null if the parsing failed.
+     */
+    public static Map<String, String> parsePropertyFile(File buildProp, ISdkLog log) {
+        FileInputStream fis = null;
+        BufferedReader reader = null;
+        try {
+            fis = new FileInputStream(buildProp);
+            reader = new BufferedReader(new InputStreamReader(fis));
+
+            String line = null;
+            Map<String, String> map = new HashMap<String, String>();
+            while ((line = reader.readLine()) != null) {
+                if (line.length() > 0 && line.charAt(0) != '#') {
+                    
+                    Matcher m = PATTERN_PROP.matcher(line);
+                    if (m.matches()) {
+                        map.put(m.group(1), m.group(2));
+                    } else {
+                        log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
+                                buildProp.getAbsolutePath(), line);
+                        return null;
+                    }
+                }
+            }
+            
+            return map;
+        } catch (FileNotFoundException e) {
+            // this should not happen since we usually test the file existence before
+            // calling the method.
+            // Return null below.
+        } catch (IOException e) {
+            if (log != null) {
+                log.warning("Error parsing '%1$s': %2$s.", buildProp.getAbsolutePath(),
+                        e.getMessage());
+            }
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    // pass
+                }
+            }
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    // pass
+                }
+            }
+        }
+
+        return null;
+    }
+    
+    /**
+     * Parses the skin folder and builds the skin list.
+     * @param osPath The path of the skin root folder.
+     */
+    private String[] parseSkinFolder(String osPath) {
+        File skinRootFolder = new File(osPath);
+
+        if (skinRootFolder.isDirectory()) {
+            ArrayList<String> skinList = new ArrayList<String>();
+
+            File[] files = skinRootFolder.listFiles();
+
+            for (File skinFolder : files) {
+                if (skinFolder.isDirectory()) {
+                    // check for layout file
+                    File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
+
+                    if (layout.isFile()) {
+                        // for now we don't parse the content of the layout and
+                        // simply add the directory to the list.
+                        skinList.add(skinFolder.getName());
+                    }
+                }
+            }
+
+            return skinList.toArray(new String[skinList.size()]);
+        }
+        
+        return new String[0];
+    }
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
new file mode 100644
index 0000000..0ea89d1
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -0,0 +1,800 @@
+/*
+ * 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.sdklib.avd;
+
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.SdkManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Android Virtual Device Manager to manage AVDs.
+ */
+public final class AvdManager {
+    
+    public static final String AVD_FOLDER_EXTENSION = ".avd";
+
+    private final static String AVD_INFO_PATH = "path";
+    private final static String AVD_INFO_TARGET = "target";
+    
+    public final static String AVD_INI_SKIN_PATH = "skin.path";
+    public final static String AVD_INI_SKIN_NAME = "skin.name";
+    public final static String AVD_INI_SDCARD_PATH = "sdcard.path";
+    public final static String AVD_INI_SDCARD_SIZE = "sdcard.size";
+    public final static String AVD_INI_IMAGES_1 = "image.sysdir.1";
+    public final static String AVD_INI_IMAGES_2 = "image.sysdir.2";
+
+    /**
+     * Pattern to match pixel-sized skin "names", e.g. "320x480".
+     */
+    public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}");
+
+    
+    private final static String USERDATA_IMG = "userdata.img";
+    private final static String CONFIG_INI = "config.ini";
+    private final static String SDCARD_IMG = "sdcard.img";
+
+    private final static String INI_EXTENSION = ".ini";
+    private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + INI_EXTENSION + "$",
+            Pattern.CASE_INSENSITIVE);
+
+    private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
+
+    /** An immutable structure describing an Android Virtual Device. */
+    public static final class AvdInfo {
+        private final String mName;
+        private final String mPath;
+        private final IAndroidTarget mTarget;
+        private final Map<String, String> mProperties;
+        
+        /** Creates a new AVD info. Values are immutable. 
+         * @param properties */
+        public AvdInfo(String name, String path, IAndroidTarget target,
+                Map<String, String> properties) {
+            mName = name;
+            mPath = path;
+            mTarget = target;
+            mProperties = properties;
+        }
+
+        /** Returns the name of the AVD. */
+        public String getName() {
+            return mName;
+        }
+
+        /** Returns the path of the AVD data directory. */
+        public String getPath() {
+            return mPath;
+        }
+
+        /** Returns the target of the AVD. */
+        public IAndroidTarget getTarget() {
+            return mTarget;
+        }
+
+        /** 
+         * Helper method that returns the .ini {@link File} for a given AVD name. 
+         * @throws AndroidLocationException if there's a problem getting android root directory.
+         */
+        public static File getIniFile(String name) throws AndroidLocationException {
+            String avdRoot;
+            avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+            return new File(avdRoot, name + INI_EXTENSION);
+        }
+        
+        /** 
+         * Returns the .ini {@link File} for this AVD. 
+         * @throws AndroidLocationException if there's a problem getting android root directory.
+         */
+        public File getIniFile() throws AndroidLocationException {
+            return getIniFile(mName);
+        }
+        
+        /**
+         * Returns a map of properties for the AVD.
+         */
+        public Map<String, String> getProperties() {
+            return mProperties;
+        }
+    }
+
+    private final ArrayList<AvdInfo> mAvdList = new ArrayList<AvdInfo>();
+    private ISdkLog mSdkLog;
+    private final SdkManager mSdk;
+
+    public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
+        mSdk = sdk;
+        mSdkLog = sdkLog;
+        buildAvdList();
+    }
+
+    /**
+     * Returns the existing AVDs.
+     * @return a newly allocated array containing all the AVDs.
+     */
+    public AvdInfo[] getAvds() {
+        return mAvdList.toArray(new AvdInfo[mAvdList.size()]);
+    }
+
+    /**
+     * Returns the {@link AvdInfo} matching the given <var>name</var>.
+     * @return the matching AvdInfo or <code>null</code> if none were found.
+     */
+    public AvdInfo getAvd(String name) {
+        for (AvdInfo info : mAvdList) {
+            if (info.getName().equals(name)) {
+                return info;
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Creates a new AVD. It is expected that there is no existing AVD with this name already.
+     * @param avdFolder the data folder for the AVD. It will be created as needed.
+     * @param name the name of the AVD
+     * @param target the target of the AVD
+     * @param skinName the name of the skin. Can be null. Must have been verified by caller.
+     * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
+     * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
+     * @param hardwareConfig the hardware setup for the AVD
+     * @param removePrevious If true remove any previous files.
+     */
+    public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
+            String skinName, String sdcard, Map<String,String> hardwareConfig,
+            boolean removePrevious, ISdkLog log) {
+        
+        File iniFile = null;
+        boolean needCleanup = false;
+        try {
+            if (avdFolder.exists()) {
+                if (removePrevious) {
+                    // AVD already exists and removePrevious is set, try to remove the
+                    // directory's content first (but not the directory itself).
+                    recursiveDelete(avdFolder);
+                } else {
+                    // AVD shouldn't already exist if removePrevious is false.
+                    if (log != null) {
+                        log.error(null,
+                                "Folder %s is in the way. Use --force if you want to overwrite.",
+                                avdFolder.getAbsolutePath());
+                    }
+                    return null;
+                }
+            } else {
+                // create the AVD folder.
+                avdFolder.mkdir();
+            }
+
+            // actually write the ini file
+            iniFile = createAvdIniFile(name, avdFolder, target);
+
+            // writes the userdata.img in it.
+            String imagePath = target.getPath(IAndroidTarget.IMAGES);
+            File userdataSrc = new File(imagePath, USERDATA_IMG);
+            
+            if (userdataSrc.exists() == false && target.isPlatform() == false) {
+                imagePath = target.getParent().getPath(IAndroidTarget.IMAGES);
+                userdataSrc = new File(imagePath, USERDATA_IMG);
+            }
+            
+            if (userdataSrc.exists() == false) {
+                log.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.",
+                        USERDATA_IMG);
+                needCleanup = true;
+                return null;
+            }
+            
+            FileInputStream fis = new FileInputStream(userdataSrc);
+            
+            File userdataDest = new File(avdFolder, USERDATA_IMG);
+            FileOutputStream fos = new FileOutputStream(userdataDest);
+            
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = fis.read(buffer)) != -1) {
+                fos.write(buffer, 0, count);
+            }
+            
+            fos.close();
+            fis.close();
+            
+            // Config file.
+            HashMap<String, String> values = new HashMap<String, String>();
+
+            // First the image folders of the target itself
+            imagePath = getImageRelativePath(target, log);
+            if (imagePath == null) {
+                needCleanup = true;
+                return null;
+            }
+            
+            values.put(AVD_INI_IMAGES_1, imagePath);
+            
+            // If the target is an add-on we need to add the Platform image as a backup.
+            IAndroidTarget parent = target.getParent();
+            if (parent != null) {
+                imagePath = getImageRelativePath(parent, log);
+                if (imagePath == null) {
+                    needCleanup = true;
+                    return null;
+                }
+
+                values.put(AVD_INI_IMAGES_2, imagePath);
+            }
+            
+            
+            // Now the skin.
+            if (skinName == null) {
+                skinName = target.getDefaultSkin();
+            }
+
+            if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
+                // Skin name is an actual screen resolution, no skin.path
+                values.put(AVD_INI_SKIN_NAME, skinName);
+            } else {
+                // get the path of the skin (relative to the SDK)
+                // assume skin name is valid
+                String skinPath = getSkinRelativePath(skinName, target, log);
+                if (skinPath == null) {
+                    needCleanup = true;
+                    return null;
+                }
+
+                values.put(AVD_INI_SKIN_PATH, skinPath);
+                values.put(AVD_INI_SKIN_NAME, skinName);
+            }
+
+            if (sdcard != null) {
+                File sdcardFile = new File(sdcard);
+                if (sdcardFile.isFile()) {
+                    // sdcard value is an external sdcard, so we put its path into the config.ini
+                    values.put(AVD_INI_SDCARD_PATH, sdcard);
+                } else {
+                    // Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
+                    // in the AVD folder, and do not put any value in config.ini.
+                    
+                    // First, check that it matches the pattern for sdcard size
+                    Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
+                    if (m.matches()) {
+                        // create the sdcard.
+                        sdcardFile = new File(avdFolder, SDCARD_IMG);
+                        String path = sdcardFile.getAbsolutePath();
+                        
+                        // execute mksdcard with the proper parameters.
+                        File toolsFolder = new File(mSdk.getLocation(), SdkConstants.FD_TOOLS);
+                        File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
+                        
+                        if (mkSdCard.isFile() == false) {
+                            log.error(null, "'%1$s' is missing from the SDK tools folder.",
+                                    mkSdCard.getName());
+                            needCleanup = true;
+                            return null;
+                        }
+                        
+                        if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) {
+                            needCleanup = true;
+                            return null; // mksdcard output has already been displayed, no need to
+                                         // output anything else.
+                        }
+                        
+                        // add a property containing the size of the sdcard for display purpose
+                        // only when the dev does 'android list avd'
+                        values.put(AVD_INI_SDCARD_SIZE, sdcard);
+                    } else {
+                        log.error(null,
+                                "'%1$s' is not recognized as a valid sdcard value.\n" +
+                                "Value should be:\n" +
+                                "1. path to an sdcard.\n" +
+                                "2. size of the sdcard to create: <size>[K|M]",
+                                sdcard);
+                        needCleanup = true;
+                        return null;
+                    }
+                }
+            }
+
+            if (hardwareConfig != null) {
+                values.putAll(hardwareConfig);
+            }
+
+            File configIniFile = new File(avdFolder, CONFIG_INI);
+            createConfigIni(configIniFile, values);
+            
+            if (log != null) {
+                if (target.isPlatform()) {
+                    log.printf("Created AVD '%s' based on %s\n", name, target.getName());
+                } else {
+                    log.printf("Created AVD '%s' based on %s (%s)\n", name, target.getName(),
+                               target.getVendor());
+                }
+            }
+            
+            // create the AvdInfo object, and add it to the list
+            AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target, values);
+            
+            mAvdList.add(avdInfo);
+            
+            return avdInfo;
+        } catch (AndroidLocationException e) {
+            if (log != null) {
+                log.error(e, null);
+            }
+        } catch (IOException e) {
+            if (log != null) {
+                log.error(e, null);
+            }
+        } finally {
+            if (needCleanup) {
+                if (iniFile != null && iniFile.exists()) {
+                    iniFile.delete();
+                }
+                
+                recursiveDelete(avdFolder);
+                avdFolder.delete();
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Returns the path to the target images folder as a relative path to the SDK.
+     */
+    private String getImageRelativePath(IAndroidTarget target, ISdkLog log) {
+        String imageFullPath = target.getPath(IAndroidTarget.IMAGES);
+
+        // make this path relative to the SDK location
+        String sdkLocation = mSdk.getLocation();
+        if (imageFullPath.startsWith(sdkLocation) == false) {
+            // this really really should not happen.
+            log.error(null, "Target location is not inside the SDK.");
+            assert false;
+            return null;
+        }
+
+        imageFullPath = imageFullPath.substring(sdkLocation.length());
+        if (imageFullPath.charAt(0) == File.separatorChar) {
+            imageFullPath = imageFullPath.substring(1);
+        }
+        return imageFullPath;
+    }
+    
+    /**
+     * Returns the path to the skin, as a relative path to the SDK.
+     */
+    private String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) {
+        // first look to see if the skin is in the target
+        
+        String path = target.getPath(IAndroidTarget.SKINS);
+        File skin = new File(path, skinName);
+        
+        if (skin.exists() == false && target.isPlatform() == false) {
+            target = target.getParent();
+
+            path = target.getPath(IAndroidTarget.SKINS);
+            skin = new File(path, skinName);
+        }
+        
+        // skin really does not exist!
+        if (skin.exists() == false) {
+            log.error(null, "Skin '%1$s' does not exist.", skinName);
+            return null;
+        }
+        
+        // get the skin path
+        path = skin.getAbsolutePath();
+
+        // make this path relative to the SDK location
+        String sdkLocation = mSdk.getLocation();
+        if (path.startsWith(sdkLocation) == false) {
+            // this really really should not happen.
+            log.error(null, "Target location is not inside the SDK.");
+            assert false;
+            return null;
+        }
+
+        path = path.substring(sdkLocation.length());
+        if (path.charAt(0) == File.separatorChar) {
+            path = path.substring(1);
+        }
+        return path;
+    }
+
+    /**
+     * Creates the ini file for an AVD.
+     * 
+     * @param name of the AVD.
+     * @param avdFolder path for the data folder of the AVD.
+     * @param target of the AVD.
+     * @throws AndroidLocationException if there's a problem getting android root directory.
+     * @throws IOException if {@link File#getAbsolutePath()} fails.
+     */
+    private File createAvdIniFile(String name, File avdFolder, IAndroidTarget target)
+            throws AndroidLocationException, IOException {
+        HashMap<String, String> values = new HashMap<String, String>();
+        File iniFile = AvdInfo.getIniFile(name);
+        values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
+        values.put(AVD_INFO_TARGET, target.hashString());
+        createConfigIni(iniFile, values);
+        
+        return iniFile;
+    }
+    
+    /**
+     * Creates the ini file for an AVD.
+     * 
+     * @param info of the AVD.
+     * @throws AndroidLocationException if there's a problem getting android root directory.
+     * @throws IOException if {@link File#getAbsolutePath()} fails.
+     */
+    private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException {
+        return createAvdIniFile(info.getName(), new File(info.getPath()), info.getTarget());
+    }
+
+    /**
+     * Actually deletes the files of an existing AVD.
+     * <p/>
+     * This also remove it from the manager's list, The caller does not need to
+     * call {@link #removeAvd(AvdInfo)} afterwards.
+     * 
+     * @param avdInfo the information on the AVD to delete
+     */
+    public void deleteAvd(AvdInfo avdInfo, ISdkLog log) {
+        try {
+            File f = avdInfo.getIniFile();
+            if (f.exists()) {
+                log.warning("Deleting file %s", f.getCanonicalPath());
+                if (!f.delete()) {
+                    log.error(null, "Failed to delete %s", f.getCanonicalPath());
+                }
+            }
+            
+            f = new File(avdInfo.getPath());
+            if (f.exists()) {
+                log.warning("Deleting folder %s", f.getCanonicalPath());
+                recursiveDelete(f);
+                if (!f.delete()) {
+                    log.error(null, "Failed to delete %s", f.getCanonicalPath());
+                }
+            }
+
+            removeAvd(avdInfo);
+        } catch (AndroidLocationException e) {
+            log.error(e, null);
+        } catch (IOException e) {
+            log.error(e, null);
+        }
+    }
+    
+    /**
+     * Moves and/or rename an existing AVD and its files.
+     * This also change it in the manager's list.
+     * <p/>
+     * The caller should make sure the name or path given are valid, do not exist and are
+     * actually different than current values.
+     * 
+     * @param avdInfo the information on the AVD to move.
+     * @param newName the new name of the AVD if non null.
+     * @param paramFolderPath the new data folder if non null.
+     * @return True if the move succeeded or there was nothing to do.
+     *         If false, this method will have had already output error in the log. 
+     */
+    public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) {
+        
+        try {
+            if (paramFolderPath != null) {
+                File f = new File(avdInfo.getPath());
+                log.warning("Moving '%s' to '%s'.", avdInfo.getPath(), paramFolderPath);
+                if (!f.renameTo(new File(paramFolderPath))) {
+                    log.error(null, "Failed to move '%s' to '%s'.",
+                            avdInfo.getPath(), paramFolderPath);
+                    return false;
+                }
+    
+                // update avd info
+                AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget(),
+                        avdInfo.getProperties());
+                mAvdList.remove(avdInfo);
+                mAvdList.add(info);
+                avdInfo = info;
+
+                // update the ini file
+                createAvdIniFile(avdInfo);
+            }
+
+            if (newName != null) {
+                File oldIniFile = avdInfo.getIniFile();
+                File newIniFile = AvdInfo.getIniFile(newName);
+                
+                log.warning("Moving '%s' to '%s'.", oldIniFile.getPath(), newIniFile.getPath());
+                if (!oldIniFile.renameTo(newIniFile)) {
+                    log.error(null, "Failed to move '%s' to '%s'.", 
+                            oldIniFile.getPath(), newIniFile.getPath());
+                    return false;
+                }
+
+                // update avd info
+                AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget(),
+                        avdInfo.getProperties());
+                mAvdList.remove(avdInfo);
+                mAvdList.add(info);
+            }
+        } catch (AndroidLocationException e) {
+            log.error(e, null);
+        } catch (IOException e) {
+            log.error(e, null);
+        }
+
+        // nothing to do or succeeded
+        return true;
+    }
+
+    /**
+     * Helper method to recursively delete a folder's content (but not the folder itself).
+     * 
+     * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
+     */
+    public void recursiveDelete(File folder) {
+        for (File f : folder.listFiles()) {
+            if (f.isDirectory()) {
+                recursiveDelete(folder);
+            }
+            f.delete();
+        }
+    }
+
+    private void buildAvdList() throws AndroidLocationException {
+        // get the Android prefs location.
+        String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
+
+        final boolean avdListDebug = System.getenv("AVD_LIST_DEBUG") != null;
+        if (avdListDebug) {
+            mSdkLog.printf("[AVD LIST DEBUG] AVD root: '%s'\n", avdRoot);
+        }
+        
+        // ensure folder validity.
+        File folder = new File(avdRoot);
+        if (folder.isFile()) {
+            if (avdListDebug) {
+                mSdkLog.printf("[AVD LIST DEBUG] AVD root is a file.\n");
+            }
+            throw new AndroidLocationException(String.format("%s is not a valid folder.", avdRoot));
+        } else if (folder.exists() == false) {
+            if (avdListDebug) {
+                mSdkLog.printf("[AVD LIST DEBUG] AVD root folder doesn't exist, creating.\n");
+            }
+            // folder is not there, we create it and return
+            folder.mkdirs();
+            return;
+        }
+        
+        File[] avds = folder.listFiles(new FilenameFilter() {
+            public boolean accept(File parent, String name) {
+                if (INI_NAME_PATTERN.matcher(name).matches()) {
+                    // check it's a file and not a folder
+                    boolean isFile = new File(parent, name).isFile();
+                    if (avdListDebug) {
+                        mSdkLog.printf("[AVD LIST DEBUG] Item '%s': %s\n",
+                                name, isFile ? "accepted file" : "rejected");
+                    }
+                    return isFile;
+                }
+
+                return false;
+            }
+        });
+        
+        for (File avd : avds) {
+            AvdInfo info = parseAvdInfo(avd);
+            if (info != null) {
+                mAvdList.add(info);
+                if (avdListDebug) {
+                    mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath());
+                }
+            } else if (avdListDebug) {
+                mSdkLog.printf("[AVD LIST DEBUG] Failed to parse AVD '%s'\n", avd.getPath());
+            }
+        }
+    }
+    
+    private AvdInfo parseAvdInfo(File path) {
+        Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
+        
+        String avdPath = map.get(AVD_INFO_PATH);
+        if (avdPath == null) {
+            return null;
+        }
+        
+        String targetHash = map.get(AVD_INFO_TARGET);
+        if (targetHash == null) {
+            return null;
+        }
+
+        IAndroidTarget target = mSdk.getTargetFromHashString(targetHash);
+        if (target == null) {
+            return null;
+        }
+        
+        // load the avd properties.
+        File configIniFile = new File(avdPath, CONFIG_INI);
+        Map<String, String> properties = SdkManager.parsePropertyFile(configIniFile, mSdkLog);
+
+        Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
+
+        AvdInfo info = new AvdInfo(
+                matcher.matches() ? matcher.group(1) : path.getName(), // should not happen
+                avdPath,
+                target,
+                properties);
+        
+        return info;
+    }
+
+    private static void createConfigIni(File iniFile, Map<String, String> values)
+            throws IOException {
+        FileWriter writer = new FileWriter(iniFile);
+        
+        for (Entry<String, String> entry : values.entrySet()) {
+            writer.write(String.format("%s=%s\n", entry.getKey(), entry.getValue()));
+        }
+        writer.close();
+
+    }
+    
+    private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
+        try {
+            String[] command = new String[3];
+            command[0] = toolLocation;
+            command[1] = size;
+            command[2] = location;
+            Process process = Runtime.getRuntime().exec(command);
+    
+            ArrayList<String> errorOutput = new ArrayList<String>();
+            ArrayList<String> stdOutput = new ArrayList<String>();
+            int status = grabProcessOutput(process, errorOutput, stdOutput,
+                    true /* waitForReaders */);
+    
+            if (status != 0) {
+                log.error(null, "Failed to create the SD card.");
+                for (String error : errorOutput) {
+                    log.error(null, error);
+                }
+                
+                return false;
+            }
+
+            return true;
+        } catch (InterruptedException e) {
+            log.error(null, "Failed to create the SD card.");
+        } catch (IOException e) {
+            log.error(null, "Failed to create the SD card.");
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Gets the stderr/stdout outputs of a process and returns 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
+     * @param errorOutput The array to store the stderr output. cannot be null.
+     * @param stdOutput The array to store the stdout output. cannot be null.
+     * @param waitforReaders if true, this will wait for the reader threads. 
+     * @return the process return code.
+     * @throws InterruptedException
+     */
+    private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
+            final ArrayList<String> stdOutput, boolean waitforReaders)
+            throws InterruptedException {
+        assert errorOutput != null;
+        assert stdOutput != null;
+        // read the lines as they come. if null is returned, it's
+        // because the process finished
+        Thread t1 = new Thread("") { //$NON-NLS-1$
+            @Override
+            public void run() {
+                // create a buffer to read the stderr output
+                InputStreamReader is = new InputStreamReader(process.getErrorStream());
+                BufferedReader errReader = new BufferedReader(is);
+
+                try {
+                    while (true) {
+                        String line = errReader.readLine();
+                        if (line != null) {
+                            errorOutput.add(line);
+                        } else {
+                            break;
+                        }
+                    }
+                } catch (IOException e) {
+                    // do nothing.
+                }
+            }
+        };
+
+        Thread t2 = new Thread("") { //$NON-NLS-1$
+            @Override
+            public void run() {
+                InputStreamReader is = new InputStreamReader(process.getInputStream());
+                BufferedReader outReader = new BufferedReader(is);
+
+                try {
+                    while (true) {
+                        String line = outReader.readLine();
+                        if (line != null) {
+                            stdOutput.add(line);
+                        } else {
+                            break;
+                        }
+                    }
+                } catch (IOException e) {
+                    // do nothing.
+                }
+            }
+        };
+
+        t1.start();
+        t2.start();
+
+        // it looks like on windows process#waitFor() can return
+        // before the thread have filled the arrays, so we wait for both threads and the
+        // process itself.
+        if (waitforReaders) {
+            try {
+                t1.join();
+            } catch (InterruptedException e) {
+            }
+            try {
+                t2.join();
+            } catch (InterruptedException e) {
+            }
+        }
+
+        // get the return code from the process
+        return process.waitFor();
+    }
+
+    /**
+     * Removes an {@link AvdInfo} from the internal list.
+     * 
+     * @param avdInfo The {@link AvdInfo} to remove.
+     * @return true if this {@link AvdInfo} was present and has been removed.
+     */
+    public boolean removeAvd(AvdInfo avdInfo) {
+        return mAvdList.remove(avdInfo);
+    }
+
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java
new file mode 100644
index 0000000..ed5b949
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java
@@ -0,0 +1,159 @@
+/*
+ * 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.sdklib.avd;
+
+import com.android.sdklib.ISdkLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HardwareProperties {
+    private final static Pattern PATTERN_PROP = Pattern.compile(
+    "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
+    
+    private final static String HW_PROP_NAME = "name";
+    private final static String HW_PROP_TYPE = "type";
+    private final static String HW_PROP_DEFAULT = "default";
+    private final static String HW_PROP_ABSTRACT = "abstract";
+    private final static String HW_PROP_DESC = "description";
+    
+    public enum ValueType {
+        INTEGER("integer"),
+        BOOLEAN("boolean"),
+        DISKSIZE("diskSize");
+        
+        private String mValue;
+
+        ValueType(String value) {
+            mValue = value;
+        }
+        
+        public static ValueType getEnum(String value) {
+            for (ValueType type : values()) {
+                if (type.mValue.equals(value)) {
+                    return type;
+                }
+            }
+            
+            return null;
+        }
+    }
+    
+    public static final class HardwareProperty {
+        private String mName;
+        private ValueType mType;
+        /** the string representation of the default value. can be null. */
+        private String mDefault;
+        private String mAbstract;
+        private String mDescription;
+        
+        public String getName() {
+            return mName;
+        }
+        
+        public ValueType getType() {
+            return mType;
+        }
+        
+        public String getDefault() {
+            return mDefault;
+        }
+        
+        public String getAbstract() {
+            return mAbstract;
+        }
+        
+        public String getDescription() {
+            return mDescription;
+        }
+    }
+    
+    /**
+     * Parses the hardware definition file.
+     * @param file the property file to parse
+     * @param log the ISdkLog object receiving warning/error from the parsing.
+     * @return the map of (key,value) pairs, or null if the parsing failed.
+     */
+    public static List<HardwareProperty> parseHardwareDefinitions(File file, ISdkLog log) {
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
+
+            List<HardwareProperty> map = new ArrayList<HardwareProperty>();
+
+            String line = null;
+            HardwareProperty prop = null;
+            while ((line = reader.readLine()) != null) {
+                if (line.length() > 0 && line.charAt(0) != '#') {
+                    Matcher m = PATTERN_PROP.matcher(line);
+                    if (m.matches()) {
+                        String valueName = m.group(1);
+                        String value = m.group(2);
+
+                        if (HW_PROP_NAME.equals(valueName)) {
+                            prop = new HardwareProperty();
+                            prop.mName = value;
+                            map.add(prop);
+                        }
+                        
+                        if (prop == null) {
+                            log.warning("Error parsing '%1$s': missing '%2$s'",
+                                    file.getAbsolutePath(), HW_PROP_NAME);
+                            return null;
+                        }
+                        
+                        if (HW_PROP_TYPE.equals(valueName)) {
+                            prop.mType = ValueType.getEnum(value);
+                        } else if (HW_PROP_DEFAULT.equals(valueName)) {
+                            prop.mDefault = value;
+                        } else if (HW_PROP_ABSTRACT.equals(valueName)) {
+                            prop.mAbstract = value;
+                        } else if (HW_PROP_DESC.equals(valueName)) {
+                            prop.mDescription = value;
+                        }
+                    } else {
+                        log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
+                                file.getAbsolutePath(), line);
+                        return null;
+                    }
+                }
+            }
+            
+            return map;
+        } catch (FileNotFoundException e) {
+            // this should not happen since we usually test the file existence before
+            // calling the method.
+            // Return null below.
+        } catch (IOException e) {
+            if (log != null) {
+                log.warning("Error parsing '%1$s': %2$s.", file.getAbsolutePath(),
+                        e.getMessage());
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
new file mode 100644
index 0000000..b89d3bd
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2009 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 java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to read and write Apk Configuration into a {@link ProjectProperties} file.
+ */
+public class ApkConfigurationHelper {
+    /** Prefix for property names for config definition. This prevents having config named
+     * after other valid properties such as "target". */
+    final static String CONFIG_PREFIX = "apk-config-";
+
+    /**
+     * Reads the Apk Configurations from a {@link ProjectProperties} file and returns them as a map.
+     * <p/>If there are no defined configurations, the returned map will be empty.
+     * @return a map of apk configurations. The map contains (name, filter) where name is
+     * the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
+     * resource configuration to include in the apk (see aapt -c) 
+     */
+    public static Map<String, String> getConfigs(ProjectProperties properties) {
+        HashMap<String, String> configMap = new HashMap<String, String>();
+
+        // get the list of configs.
+        String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
+        if (configList != null) {
+            // this is a comma separated list
+            String[] configs = configList.split(","); //$NON-NLS-1$
+            
+            // read the value of each config and store it in a map
+            for (String config : configs) {
+                config = config.trim();
+                String configValue = properties.getProperty(CONFIG_PREFIX + config);
+                if (configValue != null) {
+                    configMap.put(config, configValue);
+                }
+            }
+        }
+        
+        return configMap;
+    }
+    
+    /**
+     * Writes the Apk Configurations from a given map into a {@link ProjectProperties}.
+     * @param properties the {@link ProjectProperties} in which to store the apk configurations. 
+     * @param configMap a map of apk configurations. The map contains (name, filter) where name is
+     * the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
+     * resource configuration to include in the apk (see aapt -c) 
+     * @return true if the {@link ProjectProperties} contained Apk Configuration that were not
+     * present in the map. 
+     */
+    public static boolean setConfigs(ProjectProperties properties, Map<String, String> configMap) {
+        // load the current configs, in order to remove the value properties for each of them
+        // in case a config was removed.
+        
+        // get the list of configs.
+        String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
+
+        boolean hasRemovedConfig = false;
+
+        if (configList != null) {
+            // this is a comma separated list
+            String[] configs = configList.split(","); //$NON-NLS-1$
+            
+            for (String config : configs) {
+                config = config.trim();
+                if (configMap.containsKey(config) == false) {
+                    hasRemovedConfig = true;
+                    properties.removeProperty(CONFIG_PREFIX + config);
+                }
+            }
+        }
+        
+        // now add the properties.
+        Set<Entry<String, String>> entrySet = configMap.entrySet();
+        StringBuilder sb = new StringBuilder();
+        for (Entry<String, String> entry : entrySet) {
+            if (sb.length() > 0) {
+                sb.append(",");
+            }
+            sb.append(entry.getKey());
+            properties.setProperty(CONFIG_PREFIX + entry.getKey(), entry.getValue());
+        }
+        properties.setProperty(ProjectProperties.PROPERTY_APK_CONFIGS, sb.toString());
+        
+        return hasRemovedConfig;
+    }
+}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
new file mode 100644
index 0000000..7489b65
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
@@ -0,0 +1,742 @@
+/*
+ * 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 org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+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.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * Creates the basic files needed to get an Android project up and running. Also
+ * allows creation of IntelliJ project files.
+ *
+ * @hide
+ */
+public class ProjectCreator {
+    
+    /** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
+    private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
+    /** Package name substitution string used in template files, i.e. "PACKAGE" */
+    private final static String PH_PACKAGE = "PACKAGE";
+    /** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME". */
+    private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
+    /** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
+    private final static String PH_PROJECT_NAME = "PROJECT_NAME";
+    
+    private final static String FOLDER_TESTS = "tests";
+    
+    public enum OutputLevel {
+        /** Silent mode. Project creation will only display errors. */
+        SILENT,
+        /** Normal mode. Project creation will display what's being done, display
+         * error but not warnings. */
+        NORMAL,
+        /** Verbose mode. Project creation will display what's being done, errors and warnings. */
+        VERBOSE;
+    }
+
+    /**
+     * Exception thrown when a project creation fails, typically because a template
+     * file cannot be written.
+     */
+    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.
+     * @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) {
+        
+        // create project folder if it does not exist
+        File projectFolder = new File(folderPath);
+        if (!projectFolder.exists()) {
+
+            boolean created = false;
+            Throwable t = null;
+            try {
+                created = projectFolder.mkdirs();
+            } catch (Exception e) {
+                t = e;
+            }
+            
+            if (created) {
+                println("Created project directory: %1$s", projectFolder);
+            } else {
+                mLog.error(t, "Could not create directory: %1$s", projectFolder);
+                return;
+            }
+        } else {
+            Exception e = null;
+            String error = null;
+            try {
+                String[] content = projectFolder.list();
+                if (content == null) {
+                    error = "Project folder '%1$s' is not a directory.";
+                } else if (content.length != 0) {
+                    error = "Project folder '%1$s' is not empty. Please consider using '%2$s update' instead.";
+                }
+            } catch (Exception e1) {
+                e = e1;
+            }
+            
+            if (e != null || error != null) {
+                mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
+            }
+        }
+
+        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 an empty build.properties
+            ProjectProperties buildProperties = ProjectProperties.create(folderPath,
+                    PropertyType.BUILD);
+            buildProperties.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);
+            }
+
+            // Take the project name from the command line if there's one
+            if (projectName != null) {
+                keywords.put(PH_PROJECT_NAME, projectName);
+            } else {
+                if (activityName != null) {
+                    // Use the activity as project name 
+                    keywords.put(PH_PROJECT_NAME, activityName);
+                } else {
+                    // We need a project name. Just pick up the basename of the project
+                    // directory.
+                    projectName = projectFolder.getName();
+                    keywords.put(PH_PROJECT_NAME, projectName);                    
+                }
+            }
+            
+            // create the source folder and the java package folders.
+            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 the generate source folder
+            srcFolderPath = SdkConstants.FD_GEN_SOURCES + File.separator + packagePath;
+            sourceFolder = createDirs(projectFolder, srcFolderPath);
+
+            // 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, SdkConstants.FN_ANDROID_MANIFEST_XML),
+                    keywords, target);
+            
+            installTemplate("build.template",
+                    new File(projectFolder, SdkConstants.FN_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);
+        }
+    }
+    
+    /**
+     * Updates an existing project.
+     * <p/>
+     * Workflow:
+     * <ul>
+     * <li> Check AndroidManifest.xml is present (required)
+     * <li> Check there's a default.properties with a target *or* --target was specified
+     * <li> Update default.prop if --target was specified
+     * <li> Refresh/create "sdk" in local.properties
+     * <li> Build.xml: create if not present or no <androidinit(\w|/>) in it
+     * </ul>
+     * 
+     * @param folderPath the folder of the project to update. This folder must exist.
+     * @param target the project target. Can be null.
+     * @param projectName The project name from --name. Can be null.
+     */
+    public void updateProject(String folderPath, IAndroidTarget target, String projectName ) {
+        // project folder must exist and be a directory, since this is an update
+        File projectFolder = new File(folderPath);
+        if (!projectFolder.isDirectory()) {
+            mLog.error(null, "Project folder '%1$s' is not a valid directory, this is not an Android project you can update.",
+                    projectFolder);
+            return;
+        }
+
+        // Check AndroidManifest.xml is present
+        File androidManifest = new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML);
+        if (!androidManifest.isFile()) {
+            mLog.error(null,
+                    "%1$s not found in '%2$s', this is not an Android project you can update.",
+                    SdkConstants.FN_ANDROID_MANIFEST_XML,
+                    folderPath);
+            return;
+        }
+        
+        // Check there's a default.properties with a target *or* --target was specified
+        ProjectProperties props = ProjectProperties.load(folderPath, PropertyType.DEFAULT);
+        if (props == null || props.getProperty(ProjectProperties.PROPERTY_TARGET) == null) {
+            if (target == null) {
+                mLog.error(null,
+                    "There is no %1$s file in '%2$s'. Please provide a --target to the '%3$s update' command.",
+                    PropertyType.DEFAULT.getFilename(),
+                    folderPath,
+                    SdkConstants.androidCmdName());
+                return;
+            }
+        }
+
+        // Update default.prop if --target was specified
+        if (target != null) {
+            // we already attempted to load the file earlier, if that failed, create it.
+            if (props == null) {
+                props = ProjectProperties.create(folderPath, PropertyType.DEFAULT);
+            }
+            
+            // set or replace the target
+            props.setAndroidTarget(target);
+            try {
+                props.save();
+                println("Updated %1$s", PropertyType.DEFAULT.getFilename());
+            } catch (IOException e) {
+                mLog.error(e, "Failed to write %1$s file in '%2$s'",
+                        PropertyType.DEFAULT.getFilename(),
+                        folderPath);
+                return;
+            }
+        }
+        
+        // Refresh/create "sdk" in local.properties
+        // because the file may already exists and contain other values (like apk config),
+        // we first try to load it.
+        props = ProjectProperties.load(folderPath, PropertyType.LOCAL);
+        if (props == null) {
+            props = ProjectProperties.create(folderPath, PropertyType.LOCAL);
+        }
+        
+        // set or replace the sdk location.
+        props.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
+        try {
+            props.save();
+            println("Updated %1$s", PropertyType.LOCAL.getFilename());
+        } catch (IOException e) {
+            mLog.error(e, "Failed to write %1$s file in '%2$s'",
+                    PropertyType.LOCAL.getFilename(),
+                    folderPath);
+            return;
+        }
+        
+        // Build.xml: create if not present or no <androidinit/> in it
+        File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML);
+        boolean needsBuildXml = projectName != null || !buildXml.exists();
+        if (!needsBuildXml) {
+            // Note that "<androidinit" must be followed by either a whitespace, a "/" (for the
+            // XML /> closing tag) or an end-of-line. This way we know the XML tag is really this
+            // one and later we will be able to use an "androidinit2" tag or such as necessary.
+            needsBuildXml = !checkFileContainsRegexp(buildXml, "<androidinit(?:\\s|/|$)");
+            if (needsBuildXml) {
+                println("File %1$s is too old and needs to be updated.", SdkConstants.FN_BUILD_XML);
+            }
+        }
+        
+        if (needsBuildXml) {
+            // create the map for place-holders of values to replace in the templates
+            final HashMap<String, String> keywords = new HashMap<String, String>();
+
+            // Take the project name from the command line if there's one
+            if (projectName != null) {
+                keywords.put(PH_PROJECT_NAME, projectName);
+            } else {
+                extractPackageFromManifest(androidManifest, keywords);
+                if (keywords.containsKey(PH_ACTIVITY_NAME)) {
+                    // Use the activity as project name 
+                    keywords.put(PH_PROJECT_NAME, keywords.get(PH_ACTIVITY_NAME));
+                } else {
+                    // We need a project name. Just pick up the basename of the project
+                    // directory.
+                    projectName = projectFolder.getName();
+                    keywords.put(PH_PROJECT_NAME, projectName);                    
+                }
+            }
+
+            if (mLevel == OutputLevel.VERBOSE) {
+                println("Regenerating %1$s with project name %2$s",
+                        SdkConstants.FN_BUILD_XML,
+                        keywords.get(PH_PROJECT_NAME));
+            }
+            
+            try {
+                installTemplate("build.template",
+                        new File(projectFolder, SdkConstants.FN_BUILD_XML),
+                        keywords);
+            } catch (ProjectCreateException e) {
+                mLog.error(e, null);
+            }
+        }
+    }
+
+    /**
+     * Returns true if any line of the input file contains the requested regexp.
+     */
+    private boolean checkFileContainsRegexp(File file, String regexp) {
+        Pattern p = Pattern.compile(regexp);
+
+        try {
+            BufferedReader in = new BufferedReader(new FileReader(file));
+            String line;
+            
+            while ((line = in.readLine()) != null) {
+                if (p.matcher(line).find()) {
+                    return true;
+                }
+            }
+            
+            in.close();
+        } catch (Exception e) {
+            // ignore
+        }
+        
+        return false;
+    }
+
+    /**
+     * Extracts a "full" package & activity name from an AndroidManifest.xml.
+     * <p/>
+     * The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
+     * If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_NAME}.
+     * When no activity is found, this key is not created.
+     *  
+     * @param manifestFile The AndroidManifest.xml file 
+     * @param outKeywords  Place where to put the out parameters: package and activity names.
+     * @return True if the package/activity was parsed and updated in the keyword dictionary.
+     */
+    private boolean extractPackageFromManifest(File manifestFile,
+            Map<String, String> outKeywords) {
+        try {
+            final String nsPrefix = "android";
+            final String nsURI = SdkConstants.NS_RESOURCES;
+            
+            XPath xpath = XPathFactory.newInstance().newXPath();
+            
+            xpath.setNamespaceContext(new NamespaceContext() {
+                public String getNamespaceURI(String prefix) {
+                    if (nsPrefix.equals(prefix)) {
+                        return nsURI;
+                    }
+                    return XMLConstants.NULL_NS_URI;
+                }
+
+                public String getPrefix(String namespaceURI) {
+                    if (nsURI.equals(namespaceURI)) {
+                        return nsPrefix;
+                    }
+                    return null;
+                }
+
+                @SuppressWarnings("unchecked")
+                public Iterator getPrefixes(String namespaceURI) {
+                    if (nsURI.equals(namespaceURI)) {
+                        ArrayList<String> list = new ArrayList<String>();
+                        list.add(nsPrefix);
+                        return list.iterator();
+                    }
+                    return null;
+                }
+                
+            });
+            
+            InputSource source = new InputSource(new FileReader(manifestFile));
+            String packageName = xpath.evaluate("/manifest/@package", source);
+
+            source = new InputSource(new FileReader(manifestFile)); 
+            
+            // Select the "android:name" attribute of all <activity> nodes but only if they
+            // contain a sub-node <intent-filter><action> with an "android:name" attribute which
+            // is 'android.intent.action.MAIN' and an <intent-filter><category> with an
+            // "android:name" attribute which is 'android.intent.category.LAUNCHER'  
+            String expression = String.format("/manifest/application/activity" +
+                    "[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
+                    "intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
+                    "/@%1$s:name", nsPrefix);
+
+            NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
+                    XPathConstants.NODESET);
+
+            // If we get here, both XPath expressions were valid so we're most likely dealing
+            // with an actual AndroidManifest.xml file. The nodes may not have the requested
+            // attributes though, if which case we should warn.
+            
+            if (packageName == null || packageName.length() == 0) {
+                mLog.error(null,
+                        "Missing <manifest package=\"...\"> in '%1$s'",
+                        manifestFile.getName());
+                return false;
+            }
+
+            // Get the first activity that matched earlier. If there is no activity,
+            // activityName is set to an empty string and the generated "combined" name
+            // will be in the form "package." (with a dot at the end).
+            String activityName = "";
+            if (activityNames.getLength() > 0) {
+                activityName = activityNames.item(0).getNodeValue();
+            }
+
+            if (mLevel == OutputLevel.VERBOSE && activityNames.getLength() > 1) {
+                println("WARNING: There is more than one activity defined in '%1$s'.\n" +
+                        "Only the first one will be used. If this is not appropriate, you need\n" +
+                        "to specify one of these values manually instead:",
+                        manifestFile.getName());
+                
+                for (int i = 0; i < activityNames.getLength(); i++) {
+                    String name = activityNames.item(i).getNodeValue();
+                    name = combinePackageActivityNames(packageName, name);
+                    println("- %1$s", name);
+                }
+            }
+            
+            if (activityName.length() == 0) {
+                mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
+                        "No activity will be generated.",
+                        nsPrefix, manifestFile.getName());
+            } else {
+                outKeywords.put(PH_ACTIVITY_NAME, activityName);
+            }
+
+            outKeywords.put(PH_PACKAGE, packageName);
+            return true;
+            
+        } catch (IOException e) {
+            mLog.error(e, "Failed to read %1$s", manifestFile.getName());
+        } catch (XPathExpressionException e) {
+            Throwable t = e.getCause();
+            mLog.error(t == null ? e : t,
+                    "Failed to parse %1$s",
+                    manifestFile.getName());
+        }
+        
+        return false;
+    }
+    
+    private String combinePackageActivityNames(String packageName, String activityName) {
+        // Activity Name can have 3 forms:
+        // - ".Name" means this is a class name in the given package name.
+        //    The full FQCN is thus packageName + ".Name"
+        // - "Name" is an older variant of the former. Full FQCN is packageName + "." + "Name"
+        // - "com.blah.Name" is a full FQCN. Ignore packageName and use activityName as-is.
+        //   To be valid, the package name should have at least two components. This is checked
+        //   later during the creation of the build.xml file, so we just need to detect there's
+        //   a dot but not at pos==0.
+        
+        int pos = activityName.indexOf('.');
+        if (pos == 0) {
+            return packageName + activityName;
+        } else if (pos > 0) {
+            return activityName;
+        } else {
+            return packageName + "." + activityName;
+        }
+    }
+
+    /**
+     * 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 destFile 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 destFile 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 {
+        
+        boolean existed = destFile.exists();
+        
+        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("%1$s file %2$s",
+                existed ? "Updated" : "Added",
+                destFile);
+    }
+
+    /**
+     * Prints a message unless silence is enabled.
+     * <p/>
+     * This is just a convenience wrapper around {@link ISdkLog#printf(String, Object...)} from
+     * {@link #mLog} after testing if ouput level is {@link OutputLevel#VERBOSE}.
+     * 
+     * @param format Format for String.format
+     * @param args Arguments for String.format
+     */
+    private void println(String format, Object... args) {
+        if (mLevel != OutputLevel.SILENT) {
+            if (!format.endsWith("\n")) {
+                format += "\n";
+            }
+            mLog.printf(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/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
new file mode 100644
index 0000000..69a16be
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
@@ -0,0 +1,263 @@
+/*
+ * 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.sdklib.project;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class to load and save project properties for both ADT and Ant-based build.
+ *
+ */
+public final class ProjectProperties {
+    /** The property name for the project target */
+    public final static String PROPERTY_TARGET = "target";
+    public final static String PROPERTY_APK_CONFIGS = "apk-configurations";
+    public final static String PROPERTY_SDK = "sdk-location";
+    
+    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;
+        }
+        
+        public String getFilename() {
+            return mFilename;
+        }
+    }
+    
+    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" +
+            "# \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\n" +
+           "# 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\n" +
+           "# integral to the build system of your project.\n" +
+           "\n" +
+           "# The name of your application package as defined in the manifest.\n" +
+           "# Used by the 'uninstall' rule.\n"+
+           "#application-package=com.example.myproject\n" +
+           "\n" +
+           "# The name of the source folder.\n" +
+           "#source-folder=src\n" +
+           "\n" +
+           "# The name of the output folder.\n" +
+           "#out-folder=bin\n" +
+           "\n";
+
+    private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>();
+    static {
+//               1-------10--------20--------30--------40--------50--------60--------70--------80        
+        COMMENT_MAP.put(PROPERTY_TARGET,
+                "# Project target.\n");
+        COMMENT_MAP.put(PROPERTY_APK_CONFIGS,
+                "# apk configurations. This property allows creation of APK files with limited\n" +
+                "# resources. For example, if your application contains many locales and\n" +
+                "# you wish to release multiple smaller apks instead of a large one, you can\n" +
+                "# define configuration to create apks with limited language sets.\n" +
+                "# Format is a comma separated list of configuration names. For each\n" +
+                "# configuration, a property will declare the resource configurations to\n" +
+                "# include. Example:\n" +
+                "#     " + PROPERTY_APK_CONFIGS +"=european,northamerica\n" +
+                "#     " + ApkConfigurationHelper.CONFIG_PREFIX + "european=en,fr,it,de,es\n" +
+                "#     " + ApkConfigurationHelper.CONFIG_PREFIX + "northamerica=en,es\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.
+     * @param type One the possible {@link PropertyType}s. 
+     */
+    public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
+        File projectFolder = new File(projectFolderOsPath);
+        if (projectFolder.isDirectory()) {
+            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, type);
+                }
+            }
+        }
+        return null;
+    }
+ 
+    /**
+     * Merges all properties from the given file into the current properties.
+     * <p/>
+     * This emulates the Ant behavior: existing properties are <em>not</em> overriden.
+     * Only new undefined properties become defined.
+     * <p/>
+     * Typical usage:
+     * <ul>
+     * <li>Create a ProjectProperties with {@link PropertyType#BUILD}
+     * <li>Merge in values using {@link PropertyType#DEFAULT}
+     * <li>The result is that this contains all the properties from default plus those
+     *     overridden by the build.properties file.
+     * </ul>
+     * 
+     * @param type One the possible {@link PropertyType}s. 
+     * @return this object, for chaining.
+     */
+    public ProjectProperties merge(PropertyType type) {
+        File projectFolder = new File(mProjectFolderOsPath);
+        if (projectFolder.isDirectory()) {
+            File defaultFile = new File(projectFolder, type.mFilename);
+            if (defaultFile.isFile()) {
+                Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */);
+                if (map != null) {
+                    for(Entry<String, String> entry : map.entrySet()) {
+                        String key = entry.getKey();
+                        String value = entry.getValue();
+                        if (!mProperties.containsKey(key) && value != null) {
+                            mProperties.put(key, value);
+                        }
+                    }
+                }
+            }
+        }
+        return this;
+    }
+
+    /**
+     * 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, PropertyType type) {
+        // create and return a ProjectProperties with an empty map.
+        return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>(), type);
+    }
+    
+    /**
+     * Sets a new properties. If a property with the same name already exists, it is replaced.
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, String value) {
+        mProperties.put(name, value);
+    }
+    
+    /**
+     * 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.
+     */
+    public String getProperty(String name) {
+        return mProperties.get(name);
+    }
+    
+    /**
+     * Removes a property and returns its previous value (or null if the property did not exist).
+     * @param name the name of the property to remove.
+     */
+    public String removeProperty(String name) {
+        return mProperties.remove(name);
+    }
+
+    /**
+     * Saves the property file.
+     * @throws IOException
+     */
+    public void save() throws IOException {
+        File toSave = new File(mProjectFolderOsPath, mType.mFilename);
+        
+        FileWriter writer = new FileWriter(toSave);
+        
+        // write the header
+        writer.write(mType.mHeader);
+        
+        // write the properties.
+        for (Entry<String, String> entry : mProperties.entrySet()) {
+            String comment = COMMENT_MAP.get(entry.getKey());
+            if (comment != null) {
+                writer.write(comment);
+            }
+            writer.write(String.format("%s=%s\n", entry.getKey(), entry.getValue()));
+        }
+        
+        // close the file to flush
+        writer.close();
+    }
+    
+    /**
+     * Private constructor.
+     * <p/>
+     * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
+     * to instantiate.
+     */
+    private ProjectProperties(String projectFolderOsPath, Map<String, String> map,
+            PropertyType type) {
+        mProjectFolderOsPath = projectFolderOsPath;
+        mProperties = map;
+        mType = type;
+    }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/.classpath b/tools/sdkmanager/libs/sdkuilib/.classpath
new file mode 100644
index 0000000..eb5af7e
--- /dev/null
+++ b/tools/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/tools/sdkmanager/libs/sdkuilib/.project b/tools/sdkmanager/libs/sdkuilib/.project
new file mode 100644
index 0000000..da430c8
--- /dev/null
+++ b/tools/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/tools/sdkmanager/libs/sdkuilib/Android.mk b/tools/sdkmanager/libs/sdkuilib/Android.mk
new file mode 100644
index 0000000..8e0bc23
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/Android.mk
@@ -0,0 +1,4 @@
+# Copyright 2008 The Android Open Source Project
+#
+SDKUILIB_LOCAL_DIR := $(call my-dir)
+include $(SDKUILIB_LOCAL_DIR)/src/Android.mk
diff --git a/tools/sdkmanager/libs/sdkuilib/README b/tools/sdkmanager/libs/sdkuilib/README
new file mode 100644
index 0000000..d66b84a
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/README
@@ -0,0 +1,11 @@
+Using the Eclipse projects for ddmuilib.
+
+ddmuilib requires SWT to compile.
+
+SWT is available in the depot under prebuild/<platform>/swt
+
+Because the build path cannot contain relative path that are not inside the project directory,
+the .classpath file references a user library called ANDROID_SWT.
+
+In order to compile the project, make a user library called ANDROID_SWT containing the jar
+available at prebuild/<platform>/swt.
\ No newline at end of file
diff --git a/tools/sdkmanager/libs/sdkuilib/src/Android.mk b/tools/sdkmanager/libs/sdkuilib/src/Android.mk
new file mode 100644
index 0000000..2d3c774
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/Android.mk
@@ -0,0 +1,21 @@
+# Copyright 2008 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# no resources yet.
+# LOCAL_JAVA_RESOURCE_DIRS := resources
+
+LOCAL_JAVA_LIBRARIES := \
+	sdklib \
+	swt \
+	org.eclipse.jface_3.2.0.I20060605-1400 \
+	org.eclipse.equinox.common_3.2.0.v20060603 \
+	org.eclipse.core.commands_3.2.0.I20060605-1400
+	
+LOCAL_MODULE := sdkuilib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java
new file mode 100644
index 0000000..1460fd7
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2009 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.sdkuilib;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Edit dialog to create/edit APK configuration. The dialog displays 2 text fields for the config
+ * name and its filter.
+ */
+class ApkConfigEditDialog extends Dialog implements ModifyListener, VerifyListener {
+
+    private String mName;
+    private String mFilter;
+    private Text mNameField;
+    private Text mFilterField;
+    private Button mOkButton;
+    
+    /**
+     * Creates an edit dialog with optional initial values for the name and filter.
+     * @param name optional value for the name. Can be null.
+     * @param filter optional value for the filter. Can be null.
+     * @param parentShell the parent shell.
+     */
+    protected ApkConfigEditDialog(String name, String filter, Shell parentShell) {
+        super(parentShell);
+        mName = name;
+        mFilter = filter;
+    }
+    
+    /**
+     * Returns the name of the config. This is only valid if the user clicked OK and {@link #open()}
+     * returned {@link Window#OK}
+     */
+    public String getName() {
+        return mName;
+    }
+    
+    /**
+     * Returns the filter for the config. This is only valid if the user clicked OK and
+     * {@link #open()} returned {@link Window#OK}
+     */
+    public String getFilter() {
+        return mFilter;
+    }
+    
+    @Override
+    protected Control createContents(Composite parent) {
+        Control control = super.createContents(parent);
+
+        mOkButton = getButton(IDialogConstants.OK_ID);
+        validateButtons();
+
+        return control;
+    }
+    
+    @Override
+    protected Control createDialogArea(Composite parent) {
+        Composite composite = new Composite(parent, SWT.NONE);
+        GridLayout layout;
+        composite.setLayout(layout = new GridLayout(2, false));
+        layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
+        layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
+        layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
+        layout.horizontalSpacing = convertHorizontalDLUsToPixels(
+                IDialogConstants.HORIZONTAL_SPACING);
+
+        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+        
+        Label l = new Label(composite, SWT.NONE);
+        l.setText("Name");
+        
+        mNameField = new Text(composite, SWT.BORDER);
+        mNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        mNameField.addVerifyListener(this);
+        if (mName != null) {
+            mNameField.setText(mName);
+        }
+        mNameField.addModifyListener(this);
+
+        l = new Label(composite, SWT.NONE);
+        l.setText("Filter");
+        
+        mFilterField = new Text(composite, SWT.BORDER);
+        mFilterField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        if (mFilter != null) {
+            mFilterField.setText(mFilter);
+        }
+        mFilterField.addVerifyListener(this);
+        mFilterField.addModifyListener(this);
+        
+        applyDialogFont(composite);
+        return composite;
+    }
+    
+    /**
+     * Validates the OK button based on the content of the 2 text fields.
+     */
+    private void validateButtons() {
+        mOkButton.setEnabled(mNameField.getText().trim().length() > 0 &&
+                mFilterField.getText().trim().length() > 0);
+    }
+
+    @Override
+    protected void okPressed() {
+        mName = mNameField.getText();
+        mFilter = mFilterField.getText().trim();
+        super.okPressed();
+    }
+
+    /**
+     * Callback for text modification in the 2 text fields.
+     */
+    public void modifyText(ModifyEvent e) {
+        validateButtons();
+    }
+
+    /**
+     * Callback to ensure the content of the text field are proper.
+     */
+    public void verifyText(VerifyEvent e) {
+        Text source = ((Text)e.getSource());
+        if (source == mNameField) {
+            // check for a-zA-Z0-9.
+            final String text = e.text;
+            final int len = text.length();
+            for (int i = 0 ; i < len; i++) {
+                char letter = text.charAt(i);
+                if (letter > 255 || Character.isLetterOrDigit(letter) == false) {
+                    e.doit = false;
+                    return;
+                }
+            }
+        } else if (source == mFilterField) {
+            // we can't validate the content as its typed, but we can at least ensure the characters
+            // are valid. Same as mNameFiled + the comma.
+            final String text = e.text;
+            final int len = text.length();
+            for (int i = 0 ; i < len; i++) {
+                char letter = text.charAt(i);
+                if (letter > 255 || (Character.isLetterOrDigit(letter) == false && letter != ',')) {
+                    e.doit = false;
+                    return;
+                }
+            }
+        }
+    }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java
new file mode 100644
index 0000000..6bf1df3
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2009 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.sdkuilib;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The APK Configuration widget is a table that is added to the given parent composite.
+ * <p/>
+ * To use, create it using {@link #ApkConfigWidget(Composite)} then
+ * call {@link #fillTable(Map) to set the initial list of configurations.
+ */
+public class ApkConfigWidget {
+    private final static int INDEX_NAME = 0;
+    private final static int INDEX_FILTER = 1;
+    
+    private Table mApkConfigTable;
+    private Button mEditButton;
+    private Button mDelButton;
+
+    public ApkConfigWidget(final Composite parent) {
+        final Composite apkConfigComp = new Composite(parent, SWT.NONE);
+        apkConfigComp.setLayoutData(new GridData(GridData.FILL_BOTH));
+        apkConfigComp.setLayout(new GridLayout(2, false));
+        
+        mApkConfigTable = new Table(apkConfigComp, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
+        mApkConfigTable.setHeaderVisible(true);
+        mApkConfigTable.setLinesVisible(true);
+
+        GridData data = new GridData();
+        data.grabExcessVerticalSpace = true;
+        data.grabExcessHorizontalSpace = true;
+        data.horizontalAlignment = GridData.FILL;
+        data.verticalAlignment = GridData.FILL;
+        mApkConfigTable.setLayoutData(data);
+
+        // create the table columns
+        final TableColumn column0 = new TableColumn(mApkConfigTable, SWT.NONE);
+        column0.setText("Name");
+        column0.setWidth(100);
+        final TableColumn column1 = new TableColumn(mApkConfigTable, SWT.NONE);
+        column1.setText("Configuration");
+        column1.setWidth(100);
+
+        Composite buttonComp = new Composite(apkConfigComp, SWT.NONE);
+        buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
+        GridLayout gl;
+        buttonComp.setLayout(gl = new GridLayout(1, false));
+        gl.marginHeight = gl.marginWidth = 0;
+
+        Button newButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+        newButton.setText("New...");
+        newButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        mEditButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+        mEditButton.setText("Edit...");
+        mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        mDelButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+        mDelButton.setText("Delete");
+        mDelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        
+        newButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                ApkConfigEditDialog dlg = new ApkConfigEditDialog(null /*name*/, null /*filter*/,
+                        apkConfigComp.getShell());
+                if (dlg.open() == Dialog.OK) {
+                    TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
+                    item.setText(INDEX_NAME, dlg.getName());
+                    item.setText(INDEX_FILTER, dlg.getFilter());
+                    
+                    onSelectionChanged();
+                }
+            }
+        });
+        
+        mEditButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                // get the current selection (single mode so we don't care about any item beyond
+                // index 0).
+                TableItem[] items = mApkConfigTable.getSelection();
+                if (items.length != 0) {
+                    ApkConfigEditDialog dlg = new ApkConfigEditDialog(
+                            items[0].getText(INDEX_NAME), items[0].getText(INDEX_FILTER),
+                            apkConfigComp.getShell());
+                    if (dlg.open() == Dialog.OK) {
+                        items[0].setText(INDEX_NAME, dlg.getName());
+                        items[0].setText(INDEX_FILTER, dlg.getFilter());
+                    }
+                }
+            }
+        });
+        
+        mDelButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                // get the current selection (single mode so we don't care about any item beyond
+                // index 0).
+                int[] indices = mApkConfigTable.getSelectionIndices();
+                if (indices.length != 0) {
+                    TableItem item = mApkConfigTable.getItem(indices[0]);
+                    if (MessageDialog.openQuestion(parent.getShell(),
+                            "Apk Configuration deletion",
+                            String.format(
+                                    "Are you sure you want to delete configuration '%1$s'?",
+                                    item.getText(INDEX_NAME)))) {
+                        // delete the item.
+                        mApkConfigTable.remove(indices[0]);
+                        
+                        onSelectionChanged();
+                    }
+                }
+            }
+        });
+        
+        // Add a listener to resize the column to the full width of the table
+        mApkConfigTable.addControlListener(new ControlAdapter() {
+            @Override
+            public void controlResized(ControlEvent e) {
+                Rectangle r = mApkConfigTable.getClientArea();
+                column0.setWidth(r.width * 30 / 100); // 30%  
+                column1.setWidth(r.width * 70 / 100); // 70%
+            }
+        });
+        
+        // add a selection listener on the table, to enable/disable buttons.
+        mApkConfigTable.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onSelectionChanged();
+            }
+        });
+    }
+    
+    public void fillTable(Map<String, String> apkConfigMap) {
+        // get the names in a list so that we can sort them.
+        if (apkConfigMap != null) {
+            Set<String> keys = apkConfigMap.keySet();
+            String[] keyArray = keys.toArray(new String[keys.size()]);
+            Arrays.sort(keyArray);
+            
+            for (String key : keyArray) {
+                TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
+                item.setText(INDEX_NAME, key);
+                item.setText(INDEX_FILTER, apkConfigMap.get(key));
+            }
+        }
+        
+        onSelectionChanged();
+    }
+
+    public Map<String, String> getApkConfigs() {
+        // go through all the items from the table and fill a new map
+        HashMap<String, String> map = new HashMap<String, String>();
+        
+        TableItem[] items = mApkConfigTable.getItems();
+        for (TableItem item : items) {
+            map.put(item.getText(INDEX_NAME), item.getText(INDEX_FILTER));
+        }
+
+        return map;
+    }
+    
+    /**
+     * Handles table selection changes.
+     */
+    private void onSelectionChanged() {
+        if (mApkConfigTable.getSelectionCount() > 0) {
+            mEditButton.setEnabled(true);
+            mDelButton.setEnabled(true);
+        } else {
+            mEditButton.setEnabled(false);
+            mDelButton.setEnabled(false);
+        }
+    }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
new file mode 100644
index 0000000..9d0b928
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
@@ -0,0 +1,418 @@
+/*
+ * 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.avd.AvdManager.AvdInfo;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.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 AVD selector is a table that is added to the given parent composite.
+ * <p/>
+ * To use, create it using {@link #AvdSelector(Composite, AvdInfo[], boolean)} then
+ * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)}
+ * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the
+ * selection.
+ */
+public final class AvdSelector {
+    
+    private AvdInfo[] mAvds;
+    private final boolean mAllowMultipleSelection;
+    private SelectionListener mSelectionListener;
+    private Table mTable;
+    private Label mDescription;
+
+    /**
+     * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
+     * by a {@link IAndroidTarget}.
+     * <p/>Only the {@link AvdInfo} able to run application developed for the given
+     * {@link IAndroidTarget} will be displayed.
+     * 
+     * @param parent The parent composite where the selector will be added.
+     * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+     * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+     *        time.
+     */
+    public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter,
+            boolean allowMultipleSelection) {
+        mAvds = avds;
+
+        // 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("AVD 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, filter);
+        setupTooltip(mTable);
+    }
+    
+    /**
+     * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
+     * 
+     * @param parent The parent composite where the selector will be added.
+     * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+     * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+     *        time.
+     */
+    public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) {
+        this(parent, avds, null /* filter */, allowMultipleSelection);
+    }
+
+    
+    public void setTableHeightHint(int heightHint) {
+        GridData data = new GridData();
+        data.heightHint = heightHint;
+        data.grabExcessVerticalSpace = true;
+        data.grabExcessHorizontalSpace = true;
+        data.horizontalAlignment = GridData.FILL;
+        data.verticalAlignment = GridData.FILL;
+        mTable.setLayoutData(data);
+    }
+    
+    /**
+     * Sets a new set of AVD, with an optional filter.
+     * <p/>This must be called from the UI thread.
+     * 
+     * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify.
+     * @param filter An IAndroidTarget. If non-null, only AVD whose target are compatible with the
+     * filter target will displayed an available for selection.
+     */
+    public void setAvds(AvdInfo[] avds, IAndroidTarget filter) {
+        mAvds = avds;
+        fillTable(mTable, filter);
+    }
+
+    /**
+     * Returns the list of known AVDs.
+     * <p/>
+     * This is not a copy. Callers must <em>not</em> modify this array.
+     */
+    public AvdInfo[] getAvds() {
+        return mAvds;
+    }
+
+    /**
+     * 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(AvdInfo target) {
+        boolean found = false;
+        boolean modified = false;
+        for (TableItem i : mTable.getItems()) {
+            if ((AvdInfo) 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 AvdInfo[] getAllSelected() {
+        ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+        for (TableItem i : mTable.getItems()) {
+            if (i.getChecked()) {
+                list.add((IAndroidTarget) i.getData());
+            }
+        }
+        return list.toArray(new AvdInfo[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 AvdInfo getFirstSelected() {
+        for (TableItem i : mTable.getItems()) {
+            if (i.getChecked()) {
+                return (AvdInfo) i.getData();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Enables the receiver if the argument is true, and disables it otherwise.
+     * A disabled control is typically not selectable from the user interface
+     * and draws with an inactive or "grayed" look.
+     * 
+     * @param enabled the new enabled state.
+     */
+    public void setEnabled(boolean enabled) {
+        mTable.setEnabled(enabled);
+        mDescription.setEnabled(enabled);
+    }
+
+    /**
+     * 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 AVD.
+     * 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 (mAvds != null && mAvds.length > 0) {
+            table.setEnabled(true);
+            for (AvdInfo avd : mAvds) {
+                if (filter == null || filter.isCompatibleBaseFor(avd.getTarget())) {
+                    TableItem item = new TableItem(table, SWT.NONE);
+                    item.setData(avd);
+                    item.setText(0, avd.getName());
+                    IAndroidTarget target = avd.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 AVD 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 AVD, if any.
+     */
+    private void updateDescription(TableItem item) {
+        if (item != null) {
+            Object data = item.getData();
+            if (data instanceof AvdInfo) {
+                String newTooltip = ((AvdInfo) data).getPath();
+                mDescription.setText(newTooltip == null ? "" : newTooltip);  //$NON-NLS-1$
+            }
+        }
+    }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
new file mode 100644
index 0000000..5f9e9c2
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
@@ -0,0 +1,418 @@
+/*
+ * 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 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 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 {
+    
+    private IAndroidTarget[] mTargets;
+    private final boolean mAllowSelection;
+    private final boolean mAllowMultipleSelection;
+    private SelectionListener mSelectionListener;
+    private Table mTable;
+    private Label mDescription;
+    private Composite mInnerGroup;
+    
+    /**
+     * 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.
+     *                Targets can be null or an empty array, in which case the table is disabled.
+     * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+     *        time.
+     */
+    public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
+            boolean allowMultipleSelection) {
+        this(parent, targets, true /*allowSelection*/, allowMultipleSelection);
+    }
+
+    /**
+     * 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.
+     *                Targets can be null or an empty array, in which case the table is disabled.
+     * @param allowSelection True if selection is enabled.
+     * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+     *        time. Used only if allowSelection is true.
+     */
+    public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
+            boolean allowSelection,
+            boolean allowMultipleSelection) {
+        // Layout has 1 column
+        mInnerGroup = new Composite(parent, SWT.NONE);
+        mInnerGroup.setLayout(new GridLayout());
+        mInnerGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
+        mInnerGroup.setFont(parent.getFont());
+        
+        mAllowSelection = allowSelection;
+        mAllowMultipleSelection = allowMultipleSelection;
+        int style = SWT.BORDER;
+        if (allowSelection) {
+            style |= SWT.CHECK | SWT.FULL_SELECTION;
+        }
+        if (!mAllowMultipleSelection) {
+            style |= SWT.SINGLE;
+        }
+        mTable = new Table(mInnerGroup, style);
+        mTable.setHeaderVisible(true);
+        mTable.setLinesVisible(false);
+
+        GridData data = new GridData();
+        data.grabExcessVerticalSpace = true;
+        data.grabExcessHorizontalSpace = true;
+        data.horizontalAlignment = GridData.FILL;
+        data.verticalAlignment = GridData.FILL;
+        mTable.setLayoutData(data);
+
+        mDescription = new Label(mInnerGroup, SWT.WRAP);
+        mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+        // create the table columns
+        final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
+        column0.setText("SDK Target");
+        final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
+        column1.setText("Vendor");
+        final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
+        column2.setText("Version");
+        final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
+        column3.setText("API Level");
+
+        adjustColumnsWidth(mTable, column0, column1, column2, column3);
+        setupSelectionListener(mTable);
+        setTargets(targets);
+        setupTooltip(mTable);
+    }
+    
+    /**
+     * Returns the layout data of the inner composite widget that contains the target selector.
+     * By default the layout data is set to a {@link GridData} with a {@link GridData#FILL_BOTH}
+     * mode.
+     * <p/>
+     * This can be useful if you want to change the {@link GridData#horizontalSpan} for example.
+     */
+    public Object getLayoutData() {
+        return mInnerGroup.getLayoutData();
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Changes the targets of the SDK Target Selector.
+     * 
+     * @param targets The list of targets. This is <em>not</em> copied, the caller must not modify.
+     */
+    public void setTargets(IAndroidTarget[] targets) {
+        mTargets = targets;
+        fillTable(mTable);
+    }
+
+    /**
+     * 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(IAndroidTarget target) {
+        if (!mAllowSelection) {
+            return false;
+        }
+
+        boolean found = false;
+        boolean modified = false;
+        for (TableItem i : mTable.getItems()) {
+            if ((IAndroidTarget) 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 IAndroidTarget[] getAllSelected() {
+        ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+        for (TableItem i : mTable.getItems()) {
+            if (i.getChecked()) {
+                list.add((IAndroidTarget) i.getData());
+            }
+        }
+        return list.toArray(new IAndroidTarget[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 IAndroidTarget getFirstSelected() {
+        for (TableItem i : mTable.getItems()) {
+            if (i.getChecked()) {
+                return (IAndroidTarget) 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) {
+        if (!mAllowSelection) {
+            return;
+        }
+
+        // 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 SDK targets.
+     * 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) {
+        
+        table.removeAll();
+        
+        if (mTargets != null && mTargets.length > 0) {
+            table.setEnabled(true);
+            for (IAndroidTarget target : mTargets) {
+                TableItem item = new TableItem(table, SWT.NONE);
+                item.setData(target);
+                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);
+            TableItem item = new TableItem(table, SWT.NONE);
+            item.setData(null);
+            item.setText(0, "--");
+            item.setText(1, "No target 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 description of the item's android target, if any.
+     */
+    private void updateDescription(TableItem item) {
+        if (item != null) {
+            Object data = item.getData();
+            if (data instanceof IAndroidTarget) {
+                String newTooltip = ((IAndroidTarget) data).getDescription();
+                mDescription.setText(newTooltip == null ? "" : newTooltip);  //$NON-NLS-1$
+            }
+        }
+    }
+}