aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch)
treeecd18b995aea8eeeb8b3823266280d41245bf0f7 /sdkmanager
parent82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff)
downloadsdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'sdkmanager')
-rw-r--r--sdkmanager/Android.mk18
-rw-r--r--sdkmanager/MODULE_LICENSE_APACHE20
-rw-r--r--sdkmanager/app/.classpath11
-rw-r--r--sdkmanager/app/.project17
-rw-r--r--sdkmanager/app/Android.mk5
-rw-r--r--sdkmanager/app/etc/Android.mk8
-rwxr-xr-xsdkmanager/app/etc/android84
-rwxr-xr-xsdkmanager/app/etc/android.bat51
-rw-r--r--sdkmanager/app/etc/manifest.txt1
-rw-r--r--sdkmanager/app/src/Android.mk16
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java791
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/Main.java813
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java220
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java186
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java48
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java141
-rw-r--r--sdkmanager/libs/Android.mk18
-rw-r--r--sdkmanager/libs/sdklib/.classpath7
-rw-r--r--sdkmanager/libs/sdklib/.project17
-rw-r--r--sdkmanager/libs/sdklib/Android.mk17
-rw-r--r--sdkmanager/libs/sdklib/src/Android.mk27
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java258
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java163
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java63
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java214
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java282
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java511
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java800
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java159
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java106
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java742
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java263
-rw-r--r--sdkmanager/libs/sdkuilib/.classpath8
-rw-r--r--sdkmanager/libs/sdkuilib/.project17
-rw-r--r--sdkmanager/libs/sdkuilib/Android.mk4
-rw-r--r--sdkmanager/libs/sdkuilib/README11
-rw-r--r--sdkmanager/libs/sdkuilib/src/Android.mk21
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java177
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java211
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java418
-rw-r--r--sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java418
41 files changed, 7342 insertions, 0 deletions
diff --git a/sdkmanager/Android.mk b/sdkmanager/Android.mk
new file mode 100644
index 0000000..30df7f1
--- /dev/null
+++ b/sdkmanager/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.
+#
+SDKMANAGER_LOCAL_DIR := $(call my-dir)
+include $(SDKMANAGER_LOCAL_DIR)/app/Android.mk
+include $(SDKMANAGER_LOCAL_DIR)/libs/Android.mk
diff --git a/sdkmanager/MODULE_LICENSE_APACHE2 b/sdkmanager/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdkmanager/MODULE_LICENSE_APACHE2
diff --git a/sdkmanager/app/.classpath b/sdkmanager/app/.classpath
new file mode 100644
index 0000000..cbd9d37
--- /dev/null
+++ b/sdkmanager/app/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="tests"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sdkmanager/app/.project b/sdkmanager/app/.project
new file mode 100644
index 0000000..e12c17d
--- /dev/null
+++ b/sdkmanager/app/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SdkManager</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sdkmanager/app/Android.mk b/sdkmanager/app/Android.mk
new file mode 100644
index 0000000..24ba61f
--- /dev/null
+++ b/sdkmanager/app/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2007 The Android Open Source Project
+#
+SDKMANAGERAPP_LOCAL_DIR := $(call my-dir)
+include $(SDKMANAGERAPP_LOCAL_DIR)/etc/Android.mk
+include $(SDKMANAGERAPP_LOCAL_DIR)/src/Android.mk
diff --git a/sdkmanager/app/etc/Android.mk b/sdkmanager/app/etc/Android.mk
new file mode 100644
index 0000000..8723cd8
--- /dev/null
+++ b/sdkmanager/app/etc/Android.mk
@@ -0,0 +1,8 @@
+# Copyright 2008 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := android
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/sdkmanager/app/etc/android b/sdkmanager/app/etc/android
new file mode 100755
index 0000000..af4042b
--- /dev/null
+++ b/sdkmanager/app/etc/android
@@ -0,0 +1,84 @@
+#!/bin/sh
+# Copyright 2005-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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=sdkmanager.jar
+frameworkdir="$progdir"
+libdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/tools/lib
+ libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/framework
+ libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+
+# Check args.
+if [ debug = "$1" ]; then
+ # add this in for debugging
+ java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+else
+ java_debug=
+fi
+
+# Mac OS X needs an additional arg, or you get an "illegal thread" complaint.
+if [ `uname` = "Darwin" ]; then
+ os_opts="-XstartOnFirstThread"
+ #because Java 1.6 is 64 bits only and SWT doesn't support this, we force the usage of java 1.5
+ java_cmd="/System/Library/Frameworks/JavaVM.framework/Versions/1.5/Commands/java"
+else
+ os_opts=
+ java_cmd="java"
+fi
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+ jarpath=`cygpath -w "$frameworkdir/$jarfile"`
+ progdir=`cygpath -w "$progdir"`
+else
+ jarpath="$frameworkdir/$jarfile"
+fi
+
+# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
+# might need more memory, e.g. -Xmx128M
+exec "$java_cmd" -Xmx256M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir" -Djava.library.path="$libdir" -Dcom.android.sdkmanager.toolsdir="$progdir" -jar "$jarpath" "$@"
diff --git a/sdkmanager/app/etc/android.bat b/sdkmanager/app/etc/android.bat
new file mode 100755
index 0000000..de950ed
--- /dev/null
+++ b/sdkmanager/app/etc/android.bat
@@ -0,0 +1,51 @@
+@echo off
+rem Copyright (C) 2007 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Grab current directory before we change it
+set workdir=%cd%
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+set jarfile=sdkmanager.jar
+set frameworkdir=
+set libdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=lib\
+ set libdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=..\framework\
+ set libdir=..\lib\
+
+:JarFileOk
+
+if debug NEQ "%1" goto NoDebug
+ set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+:NoDebug
+
+set jarpath=%frameworkdir%%jarfile%
+
+call java %java_debug% -Djava.ext.dirs=%frameworkdir% -Djava.library.path=%libdir% -Dcom.android.sdkmanager.toolsdir= -Dcom.android.sdkmanager.workdir="%workdir%" -jar %jarpath% %*
diff --git a/sdkmanager/app/etc/manifest.txt b/sdkmanager/app/etc/manifest.txt
new file mode 100644
index 0000000..5676634
--- /dev/null
+++ b/sdkmanager/app/etc/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.sdkmanager.Main
diff --git a/sdkmanager/app/src/Android.mk b/sdkmanager/app/src/Android.mk
new file mode 100644
index 0000000..b508076
--- /dev/null
+++ b/sdkmanager/app/src/Android.mk
@@ -0,0 +1,16 @@
+# Copyright 2007 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ androidprefs \
+ sdklib \
+ sdkuilib
+LOCAL_MODULE := sdkmanager
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
new file mode 100644
index 0000000..9f3fb99
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Parses the command-line and stores flags needed or requested.
+ * <p/>
+ * This is a base class. To be useful you want to:
+ * <ul>
+ * <li>override it.
+ * <li>pass an action array to the constructor.
+ * <li>define flags for your actions.
+ * </ul>
+ * <p/>
+ * To use, call {@link #parseArgs(String[])} and then
+ * call {@link #getValue(String, String, String)}.
+ */
+public class CommandLineProcessor {
+
+ /** Internal verb name for internally hidden flags. */
+ public final static String GLOBAL_FLAG_VERB = "@@internal@@";
+
+ /** String to use when the verb doesn't need any object. */
+ public final static String NO_VERB_OBJECT = "";
+
+ /** The global help flag. */
+ public static final String KEY_HELP = "help";
+ /** The global verbose flag. */
+ public static final String KEY_VERBOSE = "verbose";
+ /** The global silent flag. */
+ public static final String KEY_SILENT = "silent";
+
+ /** Verb requested by the user. Null if none specified, which will be an error. */
+ private String mVerbRequested;
+ /** Direct object requested by the user. Can be null. */
+ private String mDirectObjectRequested;
+
+ /**
+ * Action definitions.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> a direct object (use #NO_VERB_OBJECT if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final String[][] mActions;
+
+ private static final int ACTION_VERB_INDEX = 0;
+ private static final int ACTION_OBJECT_INDEX = 1;
+ private static final int ACTION_DESC_INDEX = 2;
+ private static final int ACTION_ALT_OBJECT_INDEX = 3;
+
+ /**
+ * The map of all defined arguments.
+ * <p/>
+ * The key is a string "verb/directObject/longName".
+ */
+ private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
+ /** Logger */
+ private final ISdkLog mLog;
+
+ public CommandLineProcessor(ISdkLog logger, String[][] actions) {
+ mLog = logger;
+ mActions = actions;
+
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
+ "Verbose mode: errors, warnings and informational messages are printed.",
+ false);
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
+ "Silent mode: only errors are printed out.",
+ false);
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
+ "This help.",
+ false);
+ }
+
+ //------------------
+ // Helpers to get flags values
+
+ /** Helper that returns true if --verbose was requested. */
+ public boolean isVerbose() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
+ }
+
+ /** Helper that returns true if --silent was requested. */
+ public boolean isSilent() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
+ }
+
+ /** Helper that returns true if --help was requested. */
+ public boolean isHelpRequested() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
+ }
+
+ /** Returns the verb name from the command-line. Can be null. */
+ public String getVerb() {
+ return mVerbRequested;
+ }
+
+ /** Returns the direct object name from the command-line. Can be null. */
+ public String getDirectObject() {
+ return mDirectObjectRequested;
+ }
+
+ //------------------
+
+ /**
+ * Raw access to parsed parameter values.
+ * <p/>
+ * The default is to scan all parameters. Parameters that have been explicitly set on the
+ * command line are returned first. Otherwise one with a non-null value is returned.
+ * <p/>
+ * Both a verb and a direct object filter can be specified. When they are non-null they limit
+ * the scope of the search.
+ * <p/>
+ * If nothing has been found, return the last default value seen matching the filter.
+ *
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
+ * verbs that match the direct object condition will be examined and the first
+ * value set will be used.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
+ * all possible direct objects that match the verb condition will be examined and
+ * the first value set will be used.
+ * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
+ * @return The current value object stored in the parameter, which depends on the argument mode.
+ */
+ public Object getValue(String verb, String directObject, String longFlagName) {
+
+ if (verb != null && directObject != null) {
+ String key = verb + "/" + directObject + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ return arg.getCurrentValue();
+ }
+
+ Object lastDefault = null;
+ for (Arg arg : mArguments.values()) {
+ if (arg.getLongArg().equals(longFlagName)) {
+ if (verb == null || arg.getVerb().equals(verb)) {
+ if (directObject == null || arg.getDirectObject().equals(directObject)) {
+ if (arg.isInCommandLine()) {
+ return arg.getCurrentValue();
+ }
+ if (arg.getCurrentValue() != null) {
+ lastDefault = arg.getCurrentValue();
+ }
+ }
+ }
+ }
+ }
+
+ return lastDefault;
+ }
+
+ /**
+ * Internal setter for raw parameter value.
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
+ * @param longFlagName The long flag name for the given action.
+ * @param value The new current value object stored in the parameter, which depends on the
+ * argument mode.
+ */
+ protected void setValue(String verb, String directObject, String longFlagName, Object value) {
+ String key = verb + "/" + directObject + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ arg.setCurrentValue(value);
+ }
+
+ /**
+ * Parses the command-line arguments.
+ * <p/>
+ * This method will exit and not return if a parsing error arise.
+ *
+ * @param args The arguments typically received by a main method.
+ */
+ public void parseArgs(String[] args) {
+ String needsHelp = null;
+ String verb = null;
+ String directObject = null;
+
+ try {
+ int n = args.length;
+ for (int i = 0; i < n; i++) {
+ Arg arg = null;
+ String a = args[i];
+ if (a.startsWith("--")) {
+ arg = findLongArg(verb, directObject, a.substring(2));
+ } else if (a.startsWith("-")) {
+ arg = findShortArg(verb, directObject, a.substring(1));
+ }
+
+ // No matching argument name found
+ if (arg == null) {
+ // Does it looks like a dashed parameter?
+ if (a.startsWith("-")) {
+ if (verb == null || directObject == null) {
+ // It looks like a dashed parameter and we don't have a a verb/object
+ // set yet, the parameter was just given too early.
+
+ needsHelp = String.format(
+ "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
+ a);
+ return;
+ } else {
+ // It looks like a dashed parameter and but it is unknown by this
+ // verb-object combination
+
+ needsHelp = String.format(
+ "Flag '%1$s' is not valid for '%2$s %3$s'.",
+ a, verb, directObject);
+ return;
+ }
+ }
+
+ if (verb == null) {
+ // Fill verb first. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
+ verb = a;
+ break;
+ }
+ }
+
+ // Error if it was not a valid verb
+ if (verb == null) {
+ needsHelp = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+ }
+
+ } else if (directObject == null) {
+ // Then fill the direct object. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
+ if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
+ directObject = a;
+ break;
+ } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
+ actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
+ // if the alternate form exist and is used, we internally
+ // only memorize the default direct object form.
+ directObject = actionDesc[ACTION_OBJECT_INDEX];
+ break;
+ }
+ }
+ }
+
+ // Error if it was not a valid object for that verb
+ if (directObject == null) {
+ needsHelp = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+
+ }
+ }
+ } else if (arg != null) {
+ // This argument was present on the command line
+ arg.setInCommandLine(true);
+
+ // Process keyword
+ String error = null;
+ if (arg.getMode().needsExtra()) {
+ if (++i >= n) {
+ needsHelp = String.format("Missing argument for flag %1$s.", a);
+ return;
+ }
+
+ error = arg.getMode().process(arg, args[i]);
+ } else {
+ error = arg.getMode().process(arg, null);
+
+ // If we just toggled help, we want to exit now without printing any error.
+ // We do this test here only when a Boolean flag is toggled since booleans
+ // are the only flags that don't take parameters and help is a boolean.
+ if (isHelpRequested()) {
+ printHelpAndExit(null);
+ // The call above should terminate however in unit tests we override
+ // it so we still need to return here.
+ return;
+ }
+ }
+
+ if (error != null) {
+ needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
+ return;
+ }
+ }
+ }
+
+ if (needsHelp == null) {
+ if (verb == null) {
+ needsHelp = "Missing verb name.";
+ } else {
+ if (directObject == null) {
+ // Make sure this verb has an optional direct object
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
+ actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
+ directObject = NO_VERB_OBJECT;
+ break;
+ }
+ }
+
+ if (directObject == null) {
+ needsHelp = String.format("Missing object name for verb '%1$s'.", verb);
+ return;
+ }
+ }
+
+ // Validate that all mandatory arguments are non-null for this action
+ String missing = null;
+ boolean plural = false;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) &&
+ arg.getDirectObject().equals(directObject)) {
+ if (arg.isMandatory() && arg.getCurrentValue() == null) {
+ if (missing == null) {
+ missing = "--" + arg.getLongArg();
+ } else {
+ missing += ", --" + arg.getLongArg();
+ plural = true;
+ }
+ }
+ }
+ }
+
+ if (missing != null) {
+ needsHelp = String.format(
+ "The %1$s %2$s must be defined for action '%3$s %4$s'",
+ plural ? "parameters" : "parameter",
+ missing,
+ verb,
+ directObject);
+ }
+
+ mVerbRequested = verb;
+ mDirectObjectRequested = directObject;
+ }
+ }
+ } finally {
+ if (needsHelp != null) {
+ printHelpAndExitForAction(verb, directObject, needsHelp);
+ }
+ }
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a long flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findLongArg(String verb, String directObject, String longName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+ String key = verb + "/" + directObject + "/" + longName;
+ return mArguments.get(key);
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a short flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findShortArg(String verb, String directObject, String shortName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+ if (shortName.equals(arg.getShortArg())) {
+ return arg;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExit(String errorFormat, Object... args) {
+ printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param verb If null, displays help for all verbs. If not null, display help only
+ * for that specific verb. In all cases also displays general usage and action list.
+ * @param directObject If null, displays help for all verb objects.
+ * If not null, displays help only for that specific action
+ * In all cases also display general usage and action list.
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ stderr(errorFormat, args);
+ }
+
+ /*
+ * usage should fit in 80 columns
+ * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ stdout("\n" +
+ "Usage:\n" +
+ " android [global options] action [action options]\n" +
+ "\n" +
+ "Global options:");
+ listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
+
+ if (verb == null || directObject == null) {
+ stdout("\nValid actions are composed of a verb and an optional direct object:");
+ for (String[] action : mActions) {
+
+ stdout("- %1$6s %2$-7s: %3$s",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX],
+ action[ACTION_DESC_INDEX]);
+ }
+ }
+
+ for (String[] action : mActions) {
+ if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
+ if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
+ stdout("\nAction \"%1$s %2$s\":",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX]);
+ stdout(" %1$s", action[ACTION_DESC_INDEX]);
+ stdout("Options:");
+ listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
+ }
+ }
+ }
+
+ exit();
+ }
+
+ /**
+ * Internal helper to print all the option flags for a given action name.
+ */
+ protected void listOptions(String verb, String directObject) {
+ int numOptions = 0;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+
+ String value = "";
+ if (arg.getDefaultValue() instanceof String[]) {
+ for (String v : (String[]) arg.getDefaultValue()) {
+ if (value.length() > 0) {
+ value += ", ";
+ }
+ value += v;
+ }
+ } else if (arg.getDefaultValue() != null) {
+ value = arg.getDefaultValue().toString();
+ }
+ if (value.length() > 0) {
+ value = " (" + value + ")";
+ }
+
+ String required = arg.isMandatory() ? " [required]" : "";
+
+ stdout(" -%1$s %2$-10s %3$s%4$s%5$s",
+ arg.getShortArg(),
+ "--" + arg.getLongArg(),
+ arg.getDescription(),
+ value,
+ required);
+ numOptions++;
+ }
+ }
+
+ if (numOptions == 0) {
+ stdout(" No options");
+ }
+ }
+
+ //----
+
+ /**
+ * The mode of an argument specifies the type of variable it represents,
+ * whether an extra parameter is required after the flag and how to parse it.
+ */
+ static enum MODE {
+ /** Argument value is a Boolean. Default value is a Boolean. */
+ BOOLEAN {
+ @Override
+ public boolean needsExtra() {
+ return false;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ // Toggle the current value
+ arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
+ return null;
+ }
+ },
+
+ /** Argument value is an Integer. Default value is an Integer. */
+ INTEGER {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ try {
+ arg.setCurrentValue(Integer.parseInt(extra));
+ return null;
+ } catch (NumberFormatException e) {
+ return String.format("Failed to parse '%1$s' as an integer: %2%s",
+ extra, e.getMessage());
+ }
+ }
+ },
+
+ /** Argument value is a String. Default value is a String[]. */
+ ENUM {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ StringBuilder desc = new StringBuilder();
+ String[] values = (String[]) arg.getDefaultValue();
+ for (String value : values) {
+ if (value.equals(extra)) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+
+ if (desc.length() != 0) {
+ desc.append(", ");
+ }
+ desc.append(value);
+ }
+
+ return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
+ }
+ },
+
+ /** Argument value is a String. Default value is a null. */
+ STRING {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+ };
+
+ /**
+ * Returns true if this mode requires an extra parameter.
+ */
+ public abstract boolean needsExtra();
+
+ /**
+ * Processes the flag for this argument.
+ *
+ * @param arg The argument being processed.
+ * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
+ * @return An error string or null if there's no error.
+ */
+ public abstract String process(Arg arg, String extra);
+ }
+
+ /**
+ * An argument accepted by the command-line, also called "a flag".
+ * Arguments must have a short version (one letter), a long version name and a description.
+ * They can have a default value, or it can be null.
+ * Depending on the {@link MODE}, the default value can be a Boolean, an Integer, a String
+ * or a String array (in which case the first item is the current by default.)
+ */
+ static class Arg {
+ /** Verb for that argument. Never null. */
+ private final String mVerb;
+ /** Direct Object for that argument. Never null, but can be empty string. */
+ private final String mDirectObject;
+ /** The 1-letter short name of the argument, e.g. -v. */
+ private final String mShortName;
+ /** The long name of the argument, e.g. --verbose. */
+ private final String mLongName;
+ /** A description. Never null. */
+ private final String mDescription;
+ /** A default value. Can be null. */
+ private final Object mDefaultValue;
+ /** The argument mode (type + process method). Never null. */
+ private final MODE mMode;
+ /** True if this argument is mandatory for this verb/directobject. */
+ private final boolean mMandatory;
+ /** Current value. Initially set to the default value. */
+ private Object mCurrentValue;
+ /** True if the argument has been used on the command line. */
+ private boolean mInCommandLine;
+
+ /**
+ * Creates a new argument flag description.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param mandatory True if this argument is mandatory for this action.
+ * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ public Arg(MODE mode,
+ boolean mandatory,
+ String verb,
+ String directObject,
+ String shortName,
+ String longName,
+ String description,
+ Object defaultValue) {
+ mMode = mode;
+ mMandatory = mandatory;
+ mVerb = verb;
+ mDirectObject = directObject;
+ mShortName = shortName;
+ mLongName = longName;
+ mDescription = description;
+ mDefaultValue = defaultValue;
+ mInCommandLine = false;
+ if (defaultValue instanceof String[]) {
+ mCurrentValue = ((String[])defaultValue)[0];
+ } else {
+ mCurrentValue = mDefaultValue;
+ }
+ }
+
+ /** Return true if this argument is mandatory for this verb/directobject. */
+ public boolean isMandatory() {
+ return mMandatory;
+ }
+
+ /** Returns the 1-letter short name of the argument, e.g. -v. */
+ public String getShortArg() {
+ return mShortName;
+ }
+
+ /** Returns the long name of the argument, e.g. --verbose. */
+ public String getLongArg() {
+ return mLongName;
+ }
+
+ /** Returns the description. Never null. */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /** Returns the verb for that argument. Never null. */
+ public String getVerb() {
+ return mVerb;
+ }
+
+ /** Returns the direct Object for that argument. Never null, but can be empty string. */
+ public String getDirectObject() {
+ return mDirectObject;
+ }
+
+ /** Returns the default value. Can be null. */
+ public Object getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Returns the current value. Initially set to the default value. Can be null. */
+ public Object getCurrentValue() {
+ return mCurrentValue;
+ }
+
+ /** Sets the current value. Can be null. */
+ public void setCurrentValue(Object currentValue) {
+ mCurrentValue = currentValue;
+ }
+
+ /** Returns the argument mode (type + process method). Never null. */
+ public MODE getMode() {
+ return mMode;
+ }
+
+ /** Returns true if the argument has been used on the command line. */
+ public boolean isInCommandLine() {
+ return mInCommandLine;
+ }
+
+ /** Sets if the argument has been used on the command line. */
+ public void setInCommandLine(boolean inCommandLine) {
+ mInCommandLine = inCommandLine;
+ }
+ }
+
+ /**
+ * Internal helper to define a new argument for a give action.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param verb The verb name. Can be #INTERNAL_VERB.
+ * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ protected void define(MODE mode,
+ boolean mandatory,
+ String verb,
+ String directObject,
+ String shortName, String longName,
+ String description, Object defaultValue) {
+ assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory
+
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ String key = verb + "/" + directObject + "/" + longName;
+ mArguments.put(key, new Arg(mode, mandatory,
+ verb, directObject, shortName, longName, description, defaultValue));
+ }
+
+ /**
+ * Exits in case of error.
+ * This is protected so that it can be overridden in unit tests.
+ */
+ protected void exit() {
+ System.exit(1);
+ }
+
+ /**
+ * Prints a line to stdout.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stdout(String format, Object...args) {
+ mLog.printf(format + "\n", args);
+ }
+
+ /**
+ * Prints a line to stderr.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stderr(String format, Object...args) {
+ mLog.error(null, format, args);
+ }
+}
diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java
new file mode 100644
index 0000000..154788e
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.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 com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.HardwareProperties;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
+import com.android.sdklib.avd.HardwareProperties.HardwareProperty;
+import com.android.sdklib.project.ProjectCreator;
+import com.android.sdklib.project.ProjectCreator.OutputLevel;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main class for the 'android' application.
+ */
+class Main {
+
+ /** Java property that defines the location of the sdk/tools directory. */
+ private final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
+ /** Java property that defines the working directory. On Windows the current working directory
+ * is actually the tools dir, in which case this is used to get the original CWD. */
+ private final static String WORKDIR = "com.android.sdkmanager.workdir";
+
+ private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
+ private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
+
+ /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
+ private String mSdkFolder;
+ /** Logger object. Use this to print normal output, warnings or errors. */
+ private ISdkLog mSdkLog;
+ /** The SDK manager parses the SDK folder and gives access to the content. */
+ private SdkManager mSdkManager;
+ /** Command-line processor with options specific to SdkManager. */
+ private SdkCommandLine mSdkCommandLine;
+ /** The working directory, either null or set to an existing absolute canonical directory. */
+ private File mWorkDir;
+
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+
+ /**
+ * Runs the sdk manager app
+ */
+ private void run(String[] args) {
+ createLogger();
+ init();
+ mSdkCommandLine.parseArgs(args);
+ parseSdk();
+ doAction();
+ }
+
+ /**
+ * Creates the {@link #mSdkLog} object.
+ * <p/>
+ * This must be done before {@link #init()} as it will be used to report errors.
+ */
+ private void createLogger() {
+ mSdkLog = new ISdkLog() {
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ if (!errorFormat.endsWith("\n")) {
+ System.err.printf("\n");
+ }
+ }
+ if (t != null) {
+ System.err.printf("Error: %s\n", t.getMessage());
+ }
+ }
+
+ public void warning(String warningFormat, Object... args) {
+ if (mSdkCommandLine.isVerbose()) {
+ System.out.printf("Warning: " + warningFormat, args);
+ if (!warningFormat.endsWith("\n")) {
+ System.out.printf("\n");
+ }
+ }
+ }
+
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+ };
+ }
+
+ /**
+ * Init the application by making sure the SDK path is available and
+ * doing basic parsing of the SDK.
+ */
+ private void init() {
+ mSdkCommandLine = new SdkCommandLine(mSdkLog);
+
+ // We get passed a property for the tools dir
+ String toolsDirProp = System.getProperty(TOOLSDIR);
+ if (toolsDirProp == null) {
+ // for debugging, it's easier to override using the process environment
+ toolsDirProp = System.getenv(TOOLSDIR);
+ }
+
+ if (toolsDirProp != null) {
+ // got back a level for the SDK folder
+ File tools;
+ if (toolsDirProp.length() > 0) {
+ tools = new File(toolsDirProp);
+ mSdkFolder = tools.getParent();
+ } else {
+ try {
+ tools = new File(".").getCanonicalFile();
+ mSdkFolder = tools.getParent();
+ } catch (IOException e) {
+ // Will print an error below since mSdkFolder is not defined
+ }
+ }
+ }
+
+ if (mSdkFolder == null) {
+ errorAndExit("The tools directory property is not set, please make sure you are executing %1$s",
+ SdkConstants.androidCmdName());
+ }
+
+ // We might get passed a property for the working directory
+ // Either it is a valid directory and mWorkDir is set to it's absolute canonical value
+ // or mWorkDir remains null.
+ String workDirProp = System.getProperty(WORKDIR);
+ if (workDirProp == null) {
+ workDirProp = System.getenv(WORKDIR);
+ }
+ if (workDirProp != null) {
+ // This should be a valid directory
+ mWorkDir = new File(workDirProp);
+ try {
+ mWorkDir = mWorkDir.getCanonicalFile().getAbsoluteFile();
+ } catch (IOException e) {
+ mWorkDir = null;
+ }
+ if (mWorkDir == null || !mWorkDir.isDirectory()) {
+ errorAndExit("The working directory does not seem to be valid: '%1$s", workDirProp);
+ }
+ }
+ }
+
+ /**
+ * Does the basic SDK parsing required for all actions
+ */
+ private void parseSdk() {
+ mSdkManager = SdkManager.createManager(mSdkFolder, mSdkLog);
+
+ if (mSdkManager == null) {
+ errorAndExit("Unable to parse SDK content.");
+ }
+ }
+
+ /**
+ * Actually do an action...
+ */
+ private void doAction() {
+ String verb = mSdkCommandLine.getVerb();
+ String directObject = mSdkCommandLine.getDirectObject();
+
+ if (SdkCommandLine.VERB_LIST.equals(verb)) {
+ // list action.
+ if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) {
+ displayTargetList();
+ } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ displayAvdList();
+ } else {
+ displayTargetList();
+ displayAvdList();
+ }
+
+ } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ createAvd();
+
+ } else if (SdkCommandLine.VERB_DELETE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ deleteAvd();
+
+ } else if (SdkCommandLine.VERB_MOVE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ moveAvd();
+
+ } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+ SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
+ createProject();
+
+ } else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
+ SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
+ updateProject();
+ } else {
+ mSdkCommandLine.printHelpAndExit(null);
+ }
+ }
+
+ /**
+ * Creates a new Android project based on command-line parameters
+ */
+ private void createProject() {
+ // get the target and try to resolve it.
+ int targetId = mSdkCommandLine.getParamTargetId();
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ IAndroidTarget target = targets[targetId - 1];
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.createProject(projectDir,
+ mSdkCommandLine.getParamName(),
+ mSdkCommandLine.getParamProjectPackage(),
+ mSdkCommandLine.getParamProjectActivity(),
+ target,
+ false /* isTestProject*/);
+ }
+
+ /**
+ * Updates an existing Android project based on command-line parameters
+ */
+ private void updateProject() {
+ // get the target and try to resolve it.
+ IAndroidTarget target = null;
+ int targetId = mSdkCommandLine.getParamTargetId();
+ if (targetId >= 0) {
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ target = targets[targetId - 1];
+ }
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.updateProject(projectDir,
+ target,
+ mSdkCommandLine.getParamName());
+ }
+
+ /**
+ * Adjusts the project location to make it absolute & canonical relative to the
+ * working directory, if any.
+ *
+ * @return The project absolute path relative to {@link #mWorkDir} or the original
+ * newProjectLocation otherwise.
+ */
+ private String getProjectLocation(String newProjectLocation) {
+
+ // If the new project location is absolute, use it as-is
+ File projectDir = new File(newProjectLocation);
+ if (projectDir.isAbsolute()) {
+ return newProjectLocation;
+ }
+
+ // if there's no working directory, just use the project location as-is.
+ if (mWorkDir == null) {
+ return newProjectLocation;
+ }
+
+ // Combine then and get an absolute canonical directory
+ try {
+ projectDir = new File(mWorkDir, newProjectLocation).getCanonicalFile();
+
+ return projectDir.getPath();
+ } catch (IOException e) {
+ errorAndExit("Failed to combine working directory '%1$s' with project location '%2$s': %3$s",
+ mWorkDir.getPath(),
+ newProjectLocation,
+ e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Displays the list of available Targets (Platforms and Add-ons)
+ */
+ private void displayTargetList() {
+ mSdkLog.printf("Available Android targets:\n");
+
+ int index = 1;
+ for (IAndroidTarget target : mSdkManager.getTargets()) {
+ if (target.isPlatform()) {
+ mSdkLog.printf("[%d] %s\n", index, target.getName());
+ mSdkLog.printf(" API level: %d\n", target.getApiVersionNumber());
+ } else {
+ mSdkLog.printf("[%d] Add-on: %s\n", index, target.getName());
+ mSdkLog.printf(" Vendor: %s\n", target.getVendor());
+ if (target.getDescription() != null) {
+ mSdkLog.printf(" Description: %s\n", target.getDescription());
+ }
+ mSdkLog.printf(" Based on Android %s (API level %d)\n",
+ target.getApiVersionName(), target.getApiVersionNumber());
+
+ // display the optional libraries.
+ IOptionalLibrary[] libraries = target.getOptionalLibraries();
+ if (libraries != null) {
+ mSdkLog.printf(" Libraries:\n");
+ for (IOptionalLibrary library : libraries) {
+ mSdkLog.printf(" * %1$s (%2$s)\n",
+ library.getName(), library.getJarName());
+ mSdkLog.printf(String.format(
+ " %1$s\n", library.getDescription()));
+ }
+ }
+ }
+
+ // get the target skins
+ displaySkinList(target, " Skins: ");
+
+ index++;
+ }
+ }
+
+ /**
+ * Displays the skins valid for the given target.
+ */
+ private void displaySkinList(IAndroidTarget target, String message) {
+ String[] skins = target.getSkins();
+ String defaultSkin = target.getDefaultSkin();
+ mSdkLog.printf(message);
+ if (skins != null) {
+ boolean first = true;
+ for (String skin : skins) {
+ if (first == false) {
+ mSdkLog.printf(", ");
+ } else {
+ first = false;
+ }
+ mSdkLog.printf(skin);
+
+ if (skin.equals(defaultSkin)) {
+ mSdkLog.printf(" (default)");
+ }
+ }
+ mSdkLog.printf("\n");
+ } else {
+ mSdkLog.printf("no skins.\n");
+ }
+ }
+
+ /**
+ * Displays the list of available AVDs.
+ */
+ private void displayAvdList() {
+ try {
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+
+ mSdkLog.printf("Available Android Virtual Devices:\n");
+
+ AvdInfo[] avds = avdManager.getAvds();
+ for (int index = 0 ; index < avds.length ; index++) {
+ AvdInfo info = avds[index];
+ if (index > 0) {
+ mSdkLog.printf("---------\n");
+ }
+ mSdkLog.printf(" Name: %s\n", info.getName());
+ mSdkLog.printf(" Path: %s\n", info.getPath());
+
+ // get the target of the AVD
+ IAndroidTarget target = info.getTarget();
+ if (target.isPlatform()) {
+ mSdkLog.printf(" Target: %s (API level %d)\n", target.getName(),
+ target.getApiVersionNumber());
+ } else {
+ mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
+ .getVendor());
+ mSdkLog.printf(" Based on Android %s (API level %d)\n", target
+ .getApiVersionName(), target.getApiVersionNumber());
+ }
+
+ // display some extra values.
+ Map<String, String> properties = info.getProperties();
+ String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
+ if (skin != null) {
+ mSdkLog.printf(" Skin: %s\n", skin);
+ }
+ String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
+ if (sdcard == null) {
+ sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
+ }
+ if (sdcard != null) {
+ mSdkLog.printf(" Sdcard: %s\n", sdcard);
+ }
+ }
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a new AVD. This is a text based creation with command line prompt.
+ */
+ private void createAvd() {
+ // find a matching target
+ int targetId = mSdkCommandLine.getParamTargetId();
+ IAndroidTarget target = null;
+
+ if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
+ target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based
+ } else {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+
+ try {
+ boolean removePrevious = false;
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+
+ String avdName = mSdkCommandLine.getParamName();
+ AvdInfo info = avdManager.getAvd(avdName);
+ if (info != null) {
+ if (mSdkCommandLine.getFlagForce()) {
+ removePrevious = true;
+ mSdkLog.warning(
+ "Android Virtual Device '%s' already exists and will be replaced.",
+ avdName);
+ } else {
+ errorAndExit("Android Virtual Device '%s' already exists.", avdName);
+ return;
+ }
+ }
+
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ File avdFolder = null;
+ if (paramFolderPath != null) {
+ avdFolder = new File(paramFolderPath);
+ } else {
+ avdFolder = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ avdName + AvdManager.AVD_FOLDER_EXTENSION);
+ }
+
+ Map<String, String> hardwareConfig = null;
+ if (target.isPlatform()) {
+ try {
+ hardwareConfig = promptForHardware(target);
+ } catch (IOException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ AvdInfo oldAvdInfo = null;
+ if (removePrevious) {
+ oldAvdInfo = avdManager.getAvd(avdName);
+ }
+
+ // Validate skin is either default (empty) or NNNxMMM or a valid skin name.
+ String skin = mSdkCommandLine.getParamSkin();
+ if (skin != null && skin.length() == 0) {
+ skin = null;
+ }
+ if (skin != null) {
+ boolean valid = false;
+ // Is it a know skin name for this target?
+ for (String s : target.getSkins()) {
+ if (skin.equalsIgnoreCase(s)) {
+ skin = s; // Make skin names case-insensitive.
+ valid = true;
+ break;
+ }
+ }
+
+ // Is it NNNxMMM?
+ if (!valid) {
+ valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
+ }
+
+ if (!valid) {
+ displaySkinList(target, "Valid skins: ");
+ errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin);
+ return;
+ }
+ }
+
+ AvdInfo newAvdInfo = avdManager.createAvd(avdFolder,
+ avdName,
+ target,
+ skin,
+ mSdkCommandLine.getParamSdCard(),
+ hardwareConfig,
+ removePrevious,
+ mSdkLog);
+
+ if (newAvdInfo != null &&
+ oldAvdInfo != null &&
+ !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
+ mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
+ // Remove the old data directory
+ File dir = new File(oldAvdInfo.getPath());
+ avdManager.recursiveDelete(dir);
+ dir.delete();
+ // Remove old avd info from manager
+ avdManager.removeAvd(oldAvdInfo);
+ }
+
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Delete an AVD.
+ */
+ private void deleteAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ avdManager.deleteAvd(info, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Move an AVD.
+ */
+ private void moveAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ // This is a rename if there's a new name for the AVD
+ String newName = mSdkCommandLine.getParamMoveNewName();
+ if (newName != null && newName.equals(info.getName())) {
+ // same name, not actually a rename operation
+ newName = null;
+ }
+
+ // This is a move (of the data files) if there's a new location path
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ if (paramFolderPath != null) {
+ // check if paths are the same. Use File methods to account for OS idiosyncrasies.
+ try {
+ File f1 = new File(paramFolderPath).getCanonicalFile();
+ File f2 = new File(info.getPath()).getCanonicalFile();
+ if (f1.equals(f2)) {
+ // same canonical path, so not actually a move
+ paramFolderPath = null;
+ }
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now since a move operation might fail
+ // later and be harder to recover from.
+ errorAndExit(e.getMessage());
+ return;
+ }
+ }
+
+ if (newName == null && paramFolderPath == null) {
+ mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path");
+ return;
+ }
+
+ // If a rename was requested and no data move was requested, check if the original
+ // data path is our default constructed from the AVD name. In this case we still want
+ // to rename that folder too.
+ if (newName != null && paramFolderPath == null) {
+ // Compute the original data path
+ File originalFolder = new File(
+ AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ info.getName() + AvdManager.AVD_FOLDER_EXTENSION);
+ if (originalFolder.equals(info.getPath())) {
+ try {
+ // The AVD is using the default data folder path based on the AVD name.
+ // That folder needs to be adjusted to use the new name.
+ File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ newName + AvdManager.AVD_FOLDER_EXTENSION);
+ paramFolderPath = f.getCanonicalPath();
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now rather than later.
+ errorAndExit(e.getMessage());
+ }
+ }
+ }
+
+ // Check for conflicts
+
+ if (newName != null && avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+ if (newName != null) {
+ if (avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+
+ File ini = info.getIniFile();
+ if (ini.equals(AvdInfo.getIniFile(newName))) {
+ errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath());
+ return;
+ }
+ }
+
+ if (paramFolderPath != null && new File(paramFolderPath).exists()) {
+ errorAndExit(
+ "There is already a file or directory at '%s'.\nUse --path to specify a different data folder.",
+ paramFolderPath);
+ }
+
+ avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ } catch (IOException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Prompts the user to setup a hardware config for a Platform-based AVD.
+ * @throws IOException
+ */
+ private Map<String, String> promptForHardware(IAndroidTarget createTarget) throws IOException {
+ byte[] readLineBuffer = new byte[256];
+ String result;
+ String defaultAnswer = "no";
+
+ mSdkLog.printf("%s is a basic Android platform.\n", createTarget.getName());
+ mSdkLog.printf("Do you wish to create a custom hardware profile [%s]",
+ defaultAnswer);
+
+ result = readLine(readLineBuffer).trim();
+ // handle default:
+ if (result.length() == 0) {
+ result = defaultAnswer;
+ }
+
+ if (getBooleanReply(result) == false) {
+ // no custom config.
+ return null;
+ }
+
+ mSdkLog.printf("\n"); // empty line
+
+ // get the list of possible hardware properties
+ File hardwareDefs = new File (mSdkFolder + File.separator +
+ SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
+ List<HardwareProperty> list = HardwareProperties.parseHardwareDefinitions(hardwareDefs,
+ null /*sdkLog*/);
+
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ for (int i = 0 ; i < list.size() ;) {
+ HardwareProperty property = list.get(i);
+
+ String description = property.getDescription();
+ if (description != null) {
+ mSdkLog.printf("%s: %s\n", property.getAbstract(), description);
+ } else {
+ mSdkLog.printf("%s\n", property.getAbstract());
+ }
+
+ String defaultValue = property.getDefault();
+
+ if (defaultValue != null) {
+ mSdkLog.printf("%s [%s]:", property.getName(), defaultValue);
+ } else {
+ mSdkLog.printf("%s (%s):", property.getName(), property.getType());
+ }
+
+ result = readLine(readLineBuffer);
+ if (result.length() == 0) {
+ if (defaultValue != null) {
+ mSdkLog.printf("\n"); // empty line
+ i++; // go to the next property if we have a valid default value.
+ // if there's no default, we'll redo this property
+ }
+ continue;
+ }
+
+ switch (property.getType()) {
+ case BOOLEAN:
+ try {
+ if (getBooleanReply(result)) {
+ map.put(property.getName(), "yes");
+ i++; // valid reply, move to next property
+ } else {
+ map.put(property.getName(), "no");
+ i++; // valid reply, move to next property
+ }
+ } catch (IOException e) {
+ // display error, and do not increment i to redo this property
+ mSdkLog.printf("\n%s\n", e.getMessage());
+ }
+ break;
+ case INTEGER:
+ try {
+ Integer.parseInt(result);
+ map.put(property.getName(), result);
+ i++; // valid reply, move to next property
+ } catch (NumberFormatException e) {
+ // display error, and do not increment i to redo this property
+ mSdkLog.printf("\n%s\n", e.getMessage());
+ }
+ break;
+ case DISKSIZE:
+ // TODO check validity
+ map.put(property.getName(), result);
+ i++; // valid reply, move to next property
+ break;
+ }
+
+ mSdkLog.printf("\n"); // empty line
+ }
+
+ return map;
+ }
+
+ /**
+ * Reads the line from the input stream.
+ * @param buffer
+ * @throws IOException
+ */
+ private String readLine(byte[] buffer) throws IOException {
+ int count = System.in.read(buffer);
+
+ // is the input longer than the buffer?
+ if (count == buffer.length && buffer[count-1] != 10) {
+ // create a new temp buffer
+ byte[] tempBuffer = new byte[256];
+
+ // and read the rest
+ String secondHalf = readLine(tempBuffer);
+
+ // return a concat of both
+ return new String(buffer, 0, count) + secondHalf;
+ }
+
+ // ignore end whitespace
+ while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
+ count--;
+ }
+
+ return new String(buffer, 0, count);
+ }
+
+ /**
+ * Returns the boolean value represented by the string.
+ * @throws IOException If the value is not a boolean string.
+ */
+ private boolean getBooleanReply(String reply) throws IOException {
+
+ for (String valid : BOOLEAN_YES_REPLIES) {
+ if (valid.equalsIgnoreCase(reply)) {
+ return true;
+ }
+ }
+
+ for (String valid : BOOLEAN_NO_REPLIES) {
+ if (valid.equalsIgnoreCase(reply)) {
+ return false;
+ }
+ }
+
+ throw new IOException(String.format("%s is not a valid reply", reply));
+ }
+
+ private void errorAndExit(String format, Object...args) {
+ mSdkLog.error(null, format, args);
+ System.exit(1);
+ }
+} \ No newline at end of file
diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
new file mode 100644
index 0000000..34a69bd
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkManager;
+
+
+/**
+ * Specific command-line flags for the {@link SdkManager}.
+ */
+public class SdkCommandLine extends CommandLineProcessor {
+
+ public final static String VERB_LIST = "list";
+ public final static String VERB_CREATE = "create";
+ public final static String VERB_MOVE = "move";
+ public final static String VERB_DELETE = "delete";
+ public final static String VERB_UPDATE = "update";
+
+ public static final String OBJECT_AVD = "avd";
+ public static final String OBJECT_AVDS = "avds";
+ public static final String OBJECT_TARGET = "target";
+ public static final String OBJECT_TARGETS = "targets";
+ public static final String OBJECT_PROJECT = "project";
+
+ public static final String ARG_ALIAS = "alias";
+ public static final String ARG_ACTIVITY = "activity";
+
+ public static final String KEY_ACTIVITY = ARG_ACTIVITY;
+ public static final String KEY_PACKAGE = "package";
+ public static final String KEY_MODE = "mode";
+ public static final String KEY_TARGET_ID = OBJECT_TARGET;
+ public static final String KEY_NAME = "name";
+ public static final String KEY_PATH = "path";
+ public static final String KEY_FILTER = "filter";
+ public static final String KEY_SKIN = "skin";
+ public static final String KEY_SDCARD = "sdcard";
+ public static final String KEY_FORCE = "force";
+ public static final String KEY_RENAME = "rename";
+
+ /**
+ * Action definitions for SdkManager command line.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> an object (use #NO_VERB_OBJECT if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final static String[][] ACTIONS = {
+ { VERB_LIST, NO_VERB_OBJECT,
+ "Lists existing targets or virtual devices." },
+ { VERB_LIST, OBJECT_AVD,
+ "Lists existing Android Virtual Devices.",
+ OBJECT_AVDS },
+ { VERB_LIST, OBJECT_TARGET,
+ "Lists existing targets.",
+ OBJECT_TARGETS },
+
+ { VERB_CREATE, OBJECT_AVD,
+ "Creates a new Android Virtual Device." },
+ { VERB_MOVE, OBJECT_AVD,
+ "Moves or renames an Android Virtual Device." },
+ { VERB_DELETE, OBJECT_AVD,
+ "Deletes an Android Virtual Device." },
+
+ { VERB_CREATE, OBJECT_PROJECT,
+ "Creates a new Android Project." },
+ { VERB_UPDATE, OBJECT_PROJECT,
+ "Updates an Android Project (must have an AndroidManifest.xml)." },
+ };
+
+ public SdkCommandLine(ISdkLog logger) {
+ super(logger, ACTIONS);
+
+ // --- create avd ---
+
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "p", KEY_PATH,
+ "Location path of the directory where the new AVD will be created", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the new AVD", null);
+ define(MODE.INTEGER, true,
+ VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID,
+ "Target id of the new AVD", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN,
+ "Skin of the new AVD", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD,
+ "Path to a shared SD card image, or size of a new sdcard for the new AVD", null);
+ define(MODE.BOOLEAN, false,
+ VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE,
+ "Force creation (override an existing AVD)", false);
+
+ // --- delete avd ---
+
+ define(MODE.STRING, true,
+ VERB_DELETE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to delete", null);
+
+ // --- move avd ---
+
+ define(MODE.STRING, true,
+ VERB_MOVE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to move or rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME,
+ "New name of the AVD to rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "p", KEY_PATH,
+ "New location path of the directory where to move the AVD", null);
+
+ // --- create project ---
+
+ define(MODE.ENUM, true,
+ VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE,
+ "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT,
+ "p", KEY_PATH,
+ "Location path of new project", null);
+ define(MODE.INTEGER, true,
+ VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID,
+ "Target id of the new project", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE,
+ "Package name", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY,
+ "Activity name", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
+ "Project name", null);
+
+ // --- update project ---
+
+ define(MODE.STRING, true,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "p", KEY_PATH,
+ "Location path of the project", null);
+ define(MODE.INTEGER, true,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "t", KEY_TARGET_ID,
+ "Target id to set for the project", -1);
+ define(MODE.STRING, false,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "n", KEY_NAME,
+ "Project name", null);
+ }
+
+ // -- some helpers for generic action flags
+
+ /** Helper to retrieve the --path value. */
+ public String getParamLocationPath() {
+ return ((String) getValue(null, null, KEY_PATH));
+ }
+
+ /** Helper to retrieve the --target id value. */
+ public int getParamTargetId() {
+ return ((Integer) getValue(null, null, KEY_TARGET_ID)).intValue();
+ }
+
+ /** Helper to retrieve the --name value. */
+ public String getParamName() {
+ return ((String) getValue(null, null, KEY_NAME));
+ }
+
+ /** Helper to retrieve the --skin value. */
+ public String getParamSkin() {
+ return ((String) getValue(null, null, KEY_SKIN));
+ }
+
+ /** Helper to retrieve the --sdcard value. */
+ public String getParamSdCard() {
+ return ((String) getValue(null, null, KEY_SDCARD));
+ }
+
+ /** Helper to retrieve the --force flag. */
+ public boolean getFlagForce() {
+ return ((Boolean) getValue(null, null, KEY_FORCE)).booleanValue();
+ }
+
+ // -- some helpers for avd action flags
+
+ /** Helper to retrieve the --rename value for a move verb. */
+ public String getParamMoveNewName() {
+ return ((String) getValue(VERB_MOVE, null, KEY_RENAME));
+ }
+
+
+ // -- some helpers for project action flags
+
+ /** Helper to retrieve the --package value. */
+ public String getParamProjectPackage() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_PACKAGE));
+ }
+
+ /** Helper to retrieve the --activity for the new project action. */
+ public String getParamProjectActivity() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_ACTIVITY));
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
new file mode 100644
index 0000000..918591b
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import junit.framework.TestCase;
+
+
+public class CommandLineProcessorTest extends TestCase {
+
+ private MockStdLogger mLog;
+
+ /**
+ * A mock version of the {@link CommandLineProcessor} class that does not
+ * exits and captures its stdout/stderr output.
+ */
+ public static class MockCommandLineProcessor extends CommandLineProcessor {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+ private String mStdOut = "";
+ private String mStdErr = "";
+
+ public MockCommandLineProcessor(ISdkLog logger) {
+ super(logger,
+ new String[][] {
+ { "verb1", "action1", "Some action" },
+ { "verb1", "action2", "Another action" },
+ });
+ define(MODE.STRING, false /*mandatory*/,
+ "verb1", "action1", "1", "first", "non-mandatory flag", null);
+ define(MODE.STRING, true /*mandatory*/,
+ "verb1", "action1", "2", "second", "mandatory flag", null);
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdOut += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdErr += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public String getStdOut() {
+ return mStdOut;
+ }
+
+ public String getStdErr() {
+ return mStdErr;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mLog = new MockStdLogger();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testPrintHelpAndExit() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().equals(""));
+ c.printHelpAndExit(null);
+ assertTrue(c.getStdOut().indexOf("-v") != -1);
+ assertTrue(c.getStdOut().indexOf("--verbose") != -1);
+ assertTrue(c.getStdErr().equals(""));
+ assertTrue(c.wasExitCalled());
+
+ c = new MockCommandLineProcessor(mLog);
+ assertFalse(c.wasExitCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") == -1);
+
+ c.printHelpAndExit("Missing %s", "parameter");
+ assertTrue(c.wasExitCalled());
+ assertFalse(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") != -1);
+ }
+
+ public final void testVerbose() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ assertFalse(c.isVerbose());
+ c.parseArgs(new String[] { "-v" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "--verbose" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
+ }
+
+ public final void testHelp() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ c.parseArgs(new String[] { "-h" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "--help" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
+ }
+
+ public final void testMandatory() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ c.parseArgs(new String[] { "verb1", "action1", "-1", "value1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals("value1", c.getValue("verb1", "action1", "first"));
+ assertEquals("value2", c.getValue("verb1", "action1", "second"));
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "verb1", "action1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals(null, c.getValue("verb1", "action1", "first"));
+ assertEquals("value2", c.getValue("verb1", "action1", "second"));
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "verb1", "action1" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("must be defined") != -1);
+ assertEquals(null, c.getValue("verb1", "action1", "first"));
+ assertEquals(null, c.getValue("verb1", "action1", "second"));
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java b/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java
new file mode 100644
index 0000000..961e88d
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java
@@ -0,0 +1,48 @@
+/*
+ * 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.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+/**
+ *
+ */
+public class MockStdLogger implements ISdkLog {
+
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ if (!errorFormat.endsWith("\n")) {
+ System.err.printf("\n");
+ }
+ }
+ if (t != null) {
+ System.err.printf("Error: %s\n", t.getMessage());
+ }
+ }
+
+ public void warning(String warningFormat, Object... args) {
+ System.out.printf("Warning: " + warningFormat, args);
+ if (!warningFormat.endsWith("\n")) {
+ System.out.printf("\n");
+ }
+ }
+
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
new file mode 100644
index 0000000..07a32e0
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import junit.framework.TestCase;
+
+public class SdkCommandLineTest extends TestCase {
+
+ private MockStdLogger mLog;
+
+ /**
+ * A mock version of the {@link SdkCommandLine} class that does not
+ * exits and discards its stdout/stderr output.
+ */
+ public static class MockSdkCommandLine extends SdkCommandLine {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+
+ public MockSdkCommandLine(ISdkLog logger) {
+ super(logger);
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ // discard
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ // discard
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mLog = new MockStdLogger();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /** Test list */
+ public final void testList_Avd_Verbose() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "-v", "list", "avd" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("avd", c.getDirectObject());
+ assertTrue(c.isVerbose());
+ }
+
+ public final void testList_Target() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "target" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("target", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_None() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_Invalid() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "unknown" });
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.wasExitCalled());
+ assertEquals(null, c.getVerb());
+ assertEquals(null, c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_Plural() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "avds" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ // we get the non-plural form
+ assertEquals("avd", c.getDirectObject());
+ assertFalse(c.isVerbose());
+
+ c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "targets" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ // we get the non-plural form
+ assertEquals("target", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+}
diff --git a/sdkmanager/libs/Android.mk b/sdkmanager/libs/Android.mk
new file mode 100644
index 0000000..a934aa7
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/.classpath b/sdkmanager/libs/sdklib/.classpath
new file mode 100644
index 0000000..fc17a43
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/.project b/sdkmanager/libs/sdklib/.project
new file mode 100644
index 0000000..97a8578
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/Android.mk b/sdkmanager/libs/sdklib/Android.mk
new file mode 100644
index 0000000..509c573
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/Android.mk b/sdkmanager/libs/sdklib/src/Android.mk
new file mode 100644
index 0000000..a059a46
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
new file mode 100644
index 0000000..0a59107
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
new file mode 100644
index 0000000..fa462bd
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
new file mode 100644
index 0000000..4894517
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
new file mode 100644
index 0000000..a3da70e
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
new file mode 100644
index 0000000..00594d1
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
new file mode 100644
index 0000000..28227c6
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
new file mode 100644
index 0000000..0ea89d1
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java
new file mode 100644
index 0000000..ed5b949
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
new file mode 100644
index 0000000..b89d3bd
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
new file mode 100644
index 0000000..7489b65
--- /dev/null
+++ b/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/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
new file mode 100644
index 0000000..69a16be
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/.classpath b/sdkmanager/libs/sdkuilib/.classpath
new file mode 100644
index 0000000..eb5af7e
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sdkmanager/libs/sdkuilib/.project b/sdkmanager/libs/sdkuilib/.project
new file mode 100644
index 0000000..da430c8
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SdkUiLib</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sdkmanager/libs/sdkuilib/Android.mk b/sdkmanager/libs/sdkuilib/Android.mk
new file mode 100644
index 0000000..8e0bc23
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/README b/sdkmanager/libs/sdkuilib/README
new file mode 100644
index 0000000..d66b84a
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/src/Android.mk b/sdkmanager/libs/sdkuilib/src/Android.mk
new file mode 100644
index 0000000..2d3c774
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java
new file mode 100644
index 0000000..1460fd7
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java
new file mode 100644
index 0000000..6bf1df3
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java
new file mode 100644
index 0000000..9d0b928
--- /dev/null
+++ b/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/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
new file mode 100644
index 0000000..5f9e9c2
--- /dev/null
+++ b/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$
+ }
+ }
+ }
+}