OSDN Git Service

SDK Updater: use separate license node in XML. Support extra packages.
authorRaphael <raphael@google.com>
Thu, 18 Jun 2009 23:07:55 +0000 (16:07 -0700)
committerRaphael <raphael@google.com>
Thu, 18 Jun 2009 23:40:12 +0000 (16:40 -0700)
17 files changed:
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java [new file with mode: 0755]
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java [new file with mode: 0755]
sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd
sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java
sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/repository_sample.xml
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/addon_icon16.png
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/extra_icon16.png [new file with mode: 0755]

index c9372de..e273a54 100755 (executable)
@@ -28,6 +28,7 @@ import org.w3c.dom.Node;
 \r
 import java.io.File;\r
 import java.util.ArrayList;\r
+import java.util.Map;\r
 \r
 /**\r
  * Represents an add-on XML node in an SDK repository.\r
@@ -64,13 +65,13 @@ public class AddonPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    AddonPackage(RepoSource source, Node packageNode) {\r
-        super(source, packageNode);\r
-        mVendor   = getXmlString(packageNode, SdkRepository.NODE_VENDOR);\r
-        mName     = getXmlString(packageNode, SdkRepository.NODE_NAME);\r
-        mApiLevel = getXmlInt   (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
+    AddonPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
+        super(source, packageNode, licenses);\r
+        mVendor   = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_VENDOR);\r
+        mName     = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_NAME);\r
+        mApiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
 \r
-        mLibs = parseLibs(getFirstChild(packageNode, SdkRepository.NODE_LIBS));\r
+        mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkRepository.NODE_LIBS));\r
     }\r
 \r
     /**\r
@@ -131,8 +132,8 @@ public class AddonPackage extends Package {
      * Parses a <lib> element from a <libs> container.\r
      */\r
     private Lib parseLib(Node libNode) {\r
-        return new Lib(getXmlString(libNode, SdkRepository.NODE_NAME),\r
-                       getXmlString(libNode, SdkRepository.NODE_DESCRIPTION));\r
+        return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepository.NODE_NAME),\r
+                       XmlParserUtils.getXmlString(libNode, SdkRepository.NODE_DESCRIPTION));\r
     }\r
 \r
     /** Returns the vendor, a string, for add-on packages. */\r
index 826d629..191ef19 100755 (executable)
@@ -348,9 +348,18 @@ public class Archive implements IDescription {
             SdkManager sdkManager,\r
             ITaskMonitor monitor) {\r
 \r
+        Package pkg = getParentPackage();\r
+\r
         File archiveFile = null;\r
         try {\r
-            String name = getParentPackage().getShortDescription();\r
+            String name = pkg.getShortDescription();\r
+\r
+            if (pkg instanceof ExtraPackage && !((ExtraPackage) pkg).isPathValid()) {\r
+                monitor.setResult("Skipping %1$s: %2$s is not a valid install path.",\r
+                        name,\r
+                        ((ExtraPackage) pkg).getPath());\r
+                return false;\r
+            }\r
 \r
             if (isLocal()) {\r
                 // This should never happen.\r
index 0e94717..e7fa893 100755 (executable)
@@ -25,6 +25,7 @@ import com.android.sdklib.repository.SdkRepository;
 import org.w3c.dom.Node;\r
 \r
 import java.io.File;\r
+import java.util.Map;\r
 \r
 /**\r
  * Represents a doc XML node in an SDK repository.\r
@@ -38,9 +39,9 @@ public class DocPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    DocPackage(RepoSource source, Node packageNode) {\r
-        super(source, packageNode);\r
-        mApiLevel = getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
+    DocPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
+        super(source, packageNode, licenses);\r
+        mApiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
     }\r
 \r
     /**\r
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
new file mode 100755 (executable)
index 0000000..efa6931
--- /dev/null
@@ -0,0 +1,144 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdklib.internal.repository;\r
+\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
+import com.android.sdklib.repository.SdkRepository;\r
+\r
+import org.w3c.dom.Node;\r
+\r
+import java.io.File;\r
+import java.util.Map;\r
+\r
+/**\r
+ * Represents a extra XML node in an SDK repository.\r
+ */\r
+public class ExtraPackage extends Package {\r
+\r
+    private final String mPath;\r
+\r
+    /**\r
+     * Creates a new tool package from the attributes and elements of the given XML node.\r
+     * <p/>\r
+     * This constructor should throw an exception if the package cannot be created.\r
+     */\r
+    ExtraPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
+        super(source, packageNode, licenses);\r
+        mPath = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_PATH);\r
+    }\r
+\r
+    /**\r
+     * Manually create a new package with one archive and the given attributes.\r
+     * This is used to create packages from local directories in which case there must be\r
+     * one archive which URL is the actual target location.\r
+     */\r
+    ExtraPackage(RepoSource source,\r
+            String path,\r
+            int revision,\r
+            String license,\r
+            String description,\r
+            String descUrl,\r
+            Os archiveOs,\r
+            Arch archiveArch,\r
+            String archiveOsPath) {\r
+        super(source,\r
+                revision,\r
+                license,\r
+                description,\r
+                descUrl,\r
+                archiveOs,\r
+                archiveArch,\r
+                archiveOsPath);\r
+        mPath = path;\r
+    }\r
+\r
+    /**\r
+     * Static helper to check if a given path is acceptable for an "extra" package.\r
+     */\r
+    public boolean isPathValid() {\r
+        if (SdkConstants.FD_ADDONS.equals(mPath) ||\r
+                SdkConstants.FD_PLATFORMS.equals(mPath) ||\r
+                SdkConstants.FD_TOOLS.equals(mPath) ||\r
+                SdkConstants.FD_DOCS.equals(mPath)) {\r
+            return false;\r
+        }\r
+        return mPath != null && mPath.indexOf('/') == -1 && mPath.indexOf('\\') == -1;\r
+    }\r
+\r
+    /**\r
+     * The install folder name. It must be a single-segment path.\r
+     * The paths "add-ons", "platforms", "tools" and "docs" are reserved and cannot be used.\r
+     * This limitation cannot be written in the XML Schema and must be enforced here by using\r
+     * the method {@link #isPathValid()} *before* installing the package.\r
+     */\r
+    public String getPath() {\r
+        return mPath;\r
+    }\r
+\r
+    /** Returns a short description for an {@link IDescription}. */\r
+    @Override\r
+    public String getShortDescription() {\r
+        return String.format("Extra %1$s package, revision %2$d", getPath(), getRevision());\r
+    }\r
+\r
+    /** Returns a long description for an {@link IDescription}. */\r
+    @Override\r
+    public String getLongDescription() {\r
+        return String.format("Extra %1$s package, revision %2$d.\n%3$s",\r
+                getPath(),\r
+                getRevision(),\r
+                super.getLongDescription());\r
+    }\r
+\r
+    /**\r
+     * Computes a potential installation folder if an archive of this package were\r
+     * to be installed right away in the given SDK root.\r
+     * <p/>\r
+     * A "tool" package should always be located in SDK/tools.\r
+     *\r
+     * @param osSdkRoot The OS path of the SDK root folder.\r
+     * @return A new {@link File} corresponding to the directory to use to install this package.\r
+     */\r
+    @Override\r
+    public File getInstallFolder(String osSdkRoot) {\r
+        return new File(osSdkRoot, getPath());\r
+    }\r
+\r
+    /**\r
+     * Computes whether the given extra package is a suitable update for the current package.\r
+     * The base method checks the class type.\r
+     * The tools package also tests that the revision number is greater and the path is the\r
+     * same.\r
+     * <p/>\r
+     * An update is just that: a new package that supersedes the current one. If the new\r
+     * package has the same revision as the current one, it's not an update.\r
+     *\r
+     * @param replacementPackage The potential replacement package.\r
+     * @return True if the replacement package is a suitable update for this one.\r
+     */\r
+    @Override\r
+    public boolean canBeUpdatedBy(Package replacementPackage) {\r
+        if (!super.canBeUpdatedBy(replacementPackage)) {\r
+            return false;\r
+        }\r
+\r
+        ExtraPackage newPkg = (ExtraPackage) replacementPackage;\r
+        return newPkg.getRevision() > this.getRevision() && newPkg.getPath().equals(this.getPath());\r
+    }\r
+}\r
index 10f297e..91e264c 100755 (executable)
@@ -35,6 +35,7 @@ import java.io.IOException;
 import java.io.InputStream;\r
 import java.io.StringReader;\r
 import java.util.ArrayList;\r
+import java.util.HashMap;\r
 import java.util.HashSet;\r
 import java.util.Set;\r
 import java.util.regex.Matcher;\r
@@ -250,6 +251,22 @@ public class LocalSdkParser {
             Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY);\r
             if (root != null) {\r
 \r
+                // Parse license definitions\r
+                HashMap<String, String> licenses = new HashMap<String, String>();\r
+                for (Node child = root.getFirstChild();\r
+                     child != null;\r
+                     child = child.getNextSibling()) {\r
+                    if (child.getNodeType() == Node.ELEMENT_NODE &&\r
+                            SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&\r
+                            child.getLocalName().equals(SdkRepository.NODE_LICENSE)) {\r
+                        Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID);\r
+                        if (id != null) {\r
+                            licenses.put(id.getNodeValue(), child.getTextContent());\r
+                        }\r
+                    }\r
+                }\r
+\r
+                // Parse packages\r
                 for (Node child = root.getFirstChild();\r
                      child != null;\r
                      child = child.getNextSibling()) {\r
@@ -260,16 +277,16 @@ public class LocalSdkParser {
 \r
                         try {\r
                             if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
-                                return new AddonPackage(null /*source*/, child);\r
+                                return new AddonPackage(null /*source*/, child, licenses);\r
 \r
                             } else if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
-                                return new PlatformPackage(null /*source*/, child);\r
+                                return new PlatformPackage(null /*source*/, child, licenses);\r
 \r
                             } else if (SdkRepository.NODE_DOC.equals(name)) {\r
-                                return new DocPackage(null /*source*/, child);\r
+                                return new DocPackage(null /*source*/, child, licenses);\r
 \r
                             } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
-                                return new ToolPackage(null /*source*/, child);\r
+                                return new ToolPackage(null /*source*/, child, licenses);\r
                             }\r
                         } catch (Exception e) {\r
                             // Ignore invalid packages\r
index bd6ccc0..0e11ef5 100755 (executable)
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
 \r
 import java.io.File;\r
 import java.util.ArrayList;\r
+import java.util.Map;\r
 \r
 /**\r
  * A {@link Package} is the base class for "something" that can be downloaded from\r
@@ -52,13 +53,15 @@ public abstract class Package implements IDescription {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    Package(RepoSource source, Node packageNode) {\r
+    Package(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
         mSource = source;\r
-        mRevision    = getXmlInt   (packageNode, SdkRepository.NODE_REVISION, 0);\r
-        mDescription = getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);\r
-        mDescUrl     = getXmlString(packageNode, SdkRepository.NODE_DESC_URL);\r
-        mLicense     = getXmlString(packageNode, SdkRepository.NODE_LICENSE);\r
-        mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES));\r
+        mRevision    = XmlParserUtils.getXmlInt   (packageNode, SdkRepository.NODE_REVISION, 0);\r
+        mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);\r
+        mDescUrl     = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESC_URL);\r
+\r
+        mLicense  = parseLicense(packageNode, licenses);\r
+        mArchives = parseArchives(XmlParserUtils.getFirstChild(\r
+                                  packageNode, SdkRepository.NODE_ARCHIVES));\r
     }\r
 \r
     /**\r
@@ -87,6 +90,24 @@ public abstract class Package implements IDescription {
     }\r
 \r
     /**\r
+     * Parses the uses-licence node of this package, if any, and returns the license\r
+     * definition if there's one. Returns null if there's no uses-license element or no\r
+     * license of this name defined.\r
+     */\r
+    private String parseLicense(Node packageNode, Map<String, String> licenses) {\r
+        Node usesLicense = XmlParserUtils.getFirstChild(\r
+                                            packageNode, SdkRepository.NODE_USES_LICENSE);\r
+        if (usesLicense != null) {\r
+            Node ref = usesLicense.getAttributes().getNamedItem(SdkRepository.ATTR_REF);\r
+            if (ref != null) {\r
+                String licenseRef = ref.getNodeValue();\r
+                return licenses.get(licenseRef);\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
      * Parses an XML node to process the <archives> element.\r
      */\r
     private Archive[] parseArchives(Node archivesNode) {\r
@@ -114,13 +135,13 @@ public abstract class Package implements IDescription {
     private Archive parseArchive(Node archiveNode) {\r
         Archive a = new Archive(\r
                     this,\r
-                    (Os)   getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,\r
+                    (Os)   XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,\r
                             Os.values(), null),\r
-                    (Arch) getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,\r
+                    (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,\r
                             Arch.values(), Arch.ANY),\r
-                    getXmlString(archiveNode, SdkRepository.NODE_URL),\r
-                    getXmlLong(archiveNode, SdkRepository.NODE_SIZE, 0),\r
-                    getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)\r
+                    XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_URL),\r
+                    XmlParserUtils.getXmlLong  (archiveNode, SdkRepository.NODE_SIZE, 0),\r
+                    XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)\r
                 );\r
 \r
         return a;\r
@@ -224,85 +245,4 @@ public abstract class Package implements IDescription {
             replacementPackage.getRevision() > this.getRevision();\r
     }\r
 \r
-    //---\r
-\r
-    /**\r
-     * Returns the first child element with the given XML local name.\r
-     * If xmlLocalName is null, returns the very first child element.\r
-     */\r
-    protected static Node getFirstChild(Node node, String xmlLocalName) {\r
-\r
-        for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
-            if (child.getNodeType() == Node.ELEMENT_NODE &&\r
-                    SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) {\r
-                if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {\r
-                    return child;\r
-                }\r
-            }\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Retrieves the value of that XML element as a string.\r
-     * Returns an empty string when the element is missing.\r
-     */\r
-    protected static String getXmlString(Node node, String xmlLocalName) {\r
-        Node child = getFirstChild(node, xmlLocalName);\r
-\r
-        return child == null ? "" : child.getTextContent();  //$NON-NLS-1$\r
-    }\r
-\r
-    /**\r
-     * Retrieves the value of that XML element as an integer.\r
-     * Returns the default value when the element is missing or is not an integer.\r
-     */\r
-    protected static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {\r
-        String s = getXmlString(node, xmlLocalName);\r
-        try {\r
-            return Integer.parseInt(s);\r
-        } catch (NumberFormatException e) {\r
-            return defaultValue;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Retrieves the value of that XML element as a long.\r
-     * Returns the default value when the element is missing or is not an integer.\r
-     */\r
-    protected static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {\r
-        String s = getXmlString(node, xmlLocalName);\r
-        try {\r
-            return Long.parseLong(s);\r
-        } catch (NumberFormatException e) {\r
-            return defaultValue;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Retrieve an attribute which value must match one of the given enums using a\r
-     * case-insensitive name match.\r
-     *\r
-     * Returns defaultValue if the attribute does not exist or its value does not match\r
-     * the given enum values.\r
-     */\r
-    private Object getEnumAttribute(\r
-            Node archiveNode,\r
-            String attrName,\r
-            Object[] values,\r
-            Object defaultValue) {\r
-\r
-        Node attr = archiveNode.getAttributes().getNamedItem(attrName);\r
-        if (attr != null) {\r
-            String found = attr.getNodeValue();\r
-            for (Object value : values) {\r
-                if (value.toString().equalsIgnoreCase(found)) {\r
-                    return value;\r
-                }\r
-            }\r
-        }\r
-\r
-        return defaultValue;\r
-    }\r
 }\r
index 66dd529..622a6f2 100755 (executable)
@@ -26,6 +26,7 @@ import com.android.sdklib.repository.SdkRepository;
 import org.w3c.dom.Node;\r
 \r
 import java.io.File;\r
+import java.util.Map;\r
 \r
 /**\r
  * Represents a platform XML node in an SDK repository.\r
@@ -40,10 +41,10 @@ public class PlatformPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    PlatformPackage(RepoSource source, Node packageNode) {\r
-        super(source, packageNode);\r
-        mVersion  = getXmlString(packageNode, SdkRepository.NODE_VERSION);\r
-        mApiLevel = getXmlInt   (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
+    PlatformPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
+        super(source, packageNode, licenses);\r
+        mVersion  = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_VERSION);\r
+        mApiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
     }\r
 \r
     /**\r
index b1993ee..b72e7a9 100755 (executable)
@@ -28,6 +28,7 @@ import java.io.IOException;
 import java.io.InputStream;\r
 import java.net.URL;\r
 import java.util.ArrayList;\r
+import java.util.HashMap;\r
 \r
 import javax.xml.XMLConstants;\r
 import javax.xml.parsers.DocumentBuilder;\r
@@ -248,6 +249,22 @@ public class RepoSource implements IDescription {
 \r
                 ArrayList<Package> packages = new ArrayList<Package>();\r
 \r
+                // Parse license definitions\r
+                HashMap<String, String> licenses = new HashMap<String, String>();\r
+                for (Node child = root.getFirstChild();\r
+                     child != null;\r
+                     child = child.getNextSibling()) {\r
+                    if (child.getNodeType() == Node.ELEMENT_NODE &&\r
+                            SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&\r
+                            child.getLocalName().equals(SdkRepository.NODE_LICENSE)) {\r
+                        Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID);\r
+                        if (id != null) {\r
+                            licenses.put(id.getNodeValue(), child.getTextContent());\r
+                        }\r
+                    }\r
+                }\r
+\r
+                // Parse packages\r
                 for (Node child = root.getFirstChild();\r
                      child != null;\r
                      child = child.getNextSibling()) {\r
@@ -258,15 +275,18 @@ public class RepoSource implements IDescription {
 \r
                         try {\r
                             if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
-                                p = new AddonPackage(this, child);\r
+                                p = new AddonPackage(this, child, licenses);\r
+\r
+                            } else if (SdkRepository.NODE_EXTRA.equals(name)) {\r
+                                p = new ExtraPackage(this, child, licenses);\r
 \r
                             } else if (!mAddonOnly) {\r
                                 if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
-                                    p = new PlatformPackage(this, child);\r
+                                    p = new PlatformPackage(this, child, licenses);\r
                                 } else if (SdkRepository.NODE_DOC.equals(name)) {\r
-                                    p = new DocPackage(this, child);\r
+                                    p = new DocPackage(this, child, licenses);\r
                                 } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
-                                    p = new ToolPackage(this, child);\r
+                                    p = new ToolPackage(this, child, licenses);\r
                                 }\r
                             }\r
 \r
index b6d830c..1076049 100755 (executable)
@@ -24,6 +24,7 @@ import com.android.sdklib.internal.repository.Archive.Os;
 import org.w3c.dom.Node;\r
 \r
 import java.io.File;\r
+import java.util.Map;\r
 \r
 /**\r
  * Represents a tool XML node in an SDK repository.\r
@@ -35,8 +36,8 @@ public class ToolPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    ToolPackage(RepoSource source, Node packageNode) {\r
-        super(source, packageNode);\r
+    ToolPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {\r
+        super(source, packageNode, licenses);\r
     }\r
 \r
     /**\r
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
new file mode 100755 (executable)
index 0000000..7a8bc7d
--- /dev/null
@@ -0,0 +1,108 @@
+/*\r
+ * Copyright (C) 2009 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdklib.internal.repository;\r
+\r
+import com.android.sdklib.repository.SdkRepository;\r
+\r
+import org.w3c.dom.Node;\r
+\r
+/**\r
+ * Misc utilities to help extracting elements and attributes out of an XML document.\r
+ */\r
+class XmlParserUtils {\r
+\r
+    /**\r
+     * Returns the first child element with the given XML local name.\r
+     * If xmlLocalName is null, returns the very first child element.\r
+     */\r
+    public static Node getFirstChild(Node node, String xmlLocalName) {\r
+\r
+        for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
+            if (child.getNodeType() == Node.ELEMENT_NODE &&\r
+                    SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) {\r
+                if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {\r
+                    return child;\r
+                }\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Retrieves the value of that XML element as a string.\r
+     * Returns an empty string when the element is missing.\r
+     */\r
+    public static String getXmlString(Node node, String xmlLocalName) {\r
+        Node child = getFirstChild(node, xmlLocalName);\r
+\r
+        return child == null ? "" : child.getTextContent();  //$NON-NLS-1$\r
+    }\r
+\r
+    /**\r
+     * Retrieves the value of that XML element as an integer.\r
+     * Returns the default value when the element is missing or is not an integer.\r
+     */\r
+    public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {\r
+        String s = getXmlString(node, xmlLocalName);\r
+        try {\r
+            return Integer.parseInt(s);\r
+        } catch (NumberFormatException e) {\r
+            return defaultValue;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Retrieves the value of that XML element as a long.\r
+     * Returns the default value when the element is missing or is not an integer.\r
+     */\r
+    public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {\r
+        String s = getXmlString(node, xmlLocalName);\r
+        try {\r
+            return Long.parseLong(s);\r
+        } catch (NumberFormatException e) {\r
+            return defaultValue;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Retrieve an attribute which value must match one of the given enums using a\r
+     * case-insensitive name match.\r
+     *\r
+     * Returns defaultValue if the attribute does not exist or its value does not match\r
+     * the given enum values.\r
+     */\r
+    public static Object getEnumAttribute(\r
+            Node archiveNode,\r
+            String attrName,\r
+            Object[] values,\r
+            Object defaultValue) {\r
+\r
+        Node attr = archiveNode.getAttributes().getNamedItem(attrName);\r
+        if (attr != null) {\r
+            String found = attr.getNodeValue();\r
+            for (Object value : values) {\r
+                if (value.toString().equalsIgnoreCase(found)) {\r
+                    return value;\r
+                }\r
+            }\r
+        }\r
+\r
+        return defaultValue;\r
+    }\r
+\r
+}\r
index cd6d45b..a317640 100755 (executable)
@@ -43,11 +43,15 @@ public class SdkRepository {
     public static final String NODE_TOOL     = "tool";                          //$NON-NLS-1$\r
     /** A doc package. */\r
     public static final String NODE_DOC      = "doc";                           //$NON-NLS-1$\r
+    /** An extra package. */\r
+    public static final String NODE_EXTRA    = "extra";                         //$NON-NLS-1$\r
 \r
+    /** The license definition. */\r
+    public static final String NODE_LICENSE = "license";                        //$NON-NLS-1$\r
+    /** The optional uses-license for all packages (platform, add-on, tool, doc) or for a lib. */\r
+    public static final String NODE_USES_LICENSE = "uses-license";              //$NON-NLS-1$\r
     /** The revision, an int > 0, for all packages (platform, add-on, tool, doc). */\r
     public static final String NODE_REVISION    = "revision";                   //$NON-NLS-1$\r
-    /** The optional license for all packages (platform, add-on, tool, doc) or for a lib. */\r
-    public static final String NODE_LICENSE = "license";                        //$NON-NLS-1$\r
     /** The optional description for all packages (platform, add-on, tool, doc) or for a lib. */\r
     public static final String NODE_DESCRIPTION = "description";                //$NON-NLS-1$\r
     /** The optional description URL for all packages (platform, add-on, tool, doc). */\r
@@ -67,6 +71,9 @@ public class SdkRepository {
     /** A lib element in a libs container. */\r
     public static final String NODE_LIB       = "lib";                          //$NON-NLS-1$\r
 \r
+    /** The path, a string, for extra packages. */\r
+    public static final String NODE_PATH = "path";                              //$NON-NLS-1$\r
+\r
     /** The archives container, for all packages. */\r
     public static final String NODE_ARCHIVES = "archives";                      //$NON-NLS-1$\r
     /** An archive element, for the archives container. */\r
@@ -86,6 +93,12 @@ public class SdkRepository {
     /** An optional archive Architecture attribute. */\r
     public static final String ATTR_ARCH = "arch";                              //$NON-NLS-1$\r
 \r
+    /** A license definition ID. */\r
+    public static final String ATTR_ID = "id";                                  //$NON-NLS-1$\r
+    /** A license reference. */\r
+    public static final String ATTR_REF = "ref";                                //$NON-NLS-1$\r
+\r
+\r
     public static InputStream getXsdStream() {\r
         return SdkRepository.class.getResourceAsStream("sdk-repository.xsd");   //$NON-NLS-1$\r
     }\r
index 1862ae8..c55e7d5 100755 (executable)
@@ -40,7 +40,7 @@
         <xsd:complexType>
             <xsd:choice minOccurs="0" maxOccurs="unbounded">
 
-                <!-- The definition of an SDK platform package -->
+                <!-- The definition of an SDK platform package. -->
 
                 <xsd:element name="platform">
                     <xsd:annotation>
 
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
-                            <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <xsd:element name="revision"     type="xsd:positiveInteger" />
                             <!-- The optional license of this package. If present, users will have
                                  to agree to it before downloading. -->
-                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="uses-license" type="sdk:licenseType" minOccurs="0" />
                             <!-- The optional description of this package. -->
-                            <xsd:element name="description" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
                             <!-- The optional description URL of this package -->
-                            <xsd:element name="desc-url"    type="xsd:token"  minOccurs="0" />
+                            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
                             <!-- A list of file archives for this package. -->
-                            <xsd:element name="archives"    type="sdk:archivesType" />
+                            <xsd:element name="archives"     type="sdk:archivesType" />
                         </xsd:all>
                     </xsd:complexType>
                 </xsd:element>
 
-                <!-- The definition of an SDK Add-on package -->
+
+                <!-- The definition of an SDK Add-on package. -->
 
                 <xsd:element name="add-on">
                     <xsd:annotation>
 
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
-                            <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <xsd:element name="revision"     type="xsd:positiveInteger" />
                             <!-- The optional license of this package. If present, users will have
                                  to agree to it before downloading. -->
-                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="uses-license" type="sdk:licenseType" minOccurs="0" />
                             <!-- The optional description of this package. -->
-                            <xsd:element name="description" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
                             <!-- The optional description URL of this package -->
-                            <xsd:element name="desc-url"    type="xsd:token"  minOccurs="0" />
+                            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
                             <!-- A list of file archives for this package. -->
-                            <xsd:element name="archives"    type="sdk:archivesType" />
+                            <xsd:element name="archives"     type="sdk:archivesType" />
 
                             <!-- An add-on can declare 0 or more libraries. -->
 
                     </xsd:complexType>
                 </xsd:element>
 
-                <!-- The definition of an SDK tool package -->
+
+                <!-- The definition of an SDK tool package. -->
 
                 <xsd:element name="tool">
                     <xsd:annotation>
                         <xsd:all>
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
-                            <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <xsd:element name="revision"     type="xsd:positiveInteger" />
                             <!-- The optional license of this package. If present, users will have
                                  to agree to it before downloading. -->
-                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="uses-license" type="sdk:licenseType" minOccurs="0" />
                             <!-- The optional description of this package. -->
-                            <xsd:element name="description" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
                             <!-- The optional description URL of this package -->
-                            <xsd:element name="desc-url"    type="xsd:token"  minOccurs="0" />
+                            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
                             <!-- A list of file archives for this package. -->
-                            <xsd:element name="archives"    type="sdk:archivesType" />
+                            <xsd:element name="archives"     type="sdk:archivesType" />
                         </xsd:all>
                     </xsd:complexType>
                 </xsd:element>
 
-                <!-- The definition of an SDK doc package -->
+
+                <!-- The definition of an SDK doc package. -->
 
                 <xsd:element name="doc">
                     <xsd:annotation>
 
                             <!-- The revision, an int > 0, incremented each time a new
                                  package is generated. -->
-                            <xsd:element name="revision"    type="xsd:positiveInteger" />
+                            <xsd:element name="revision"     type="xsd:positiveInteger" />
+                            <!-- The optional license of this package. If present, users will have
+                                 to agree to it before downloading. -->
+                            <xsd:element name="uses-license" type="sdk:licenseType" minOccurs="0" />
+                            <!-- The optional description of this package. -->
+                            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+                            <!-- The optional description URL of this package -->
+                            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+                            <!-- A list of file archives for this package. -->
+                            <xsd:element name="archives"     type="sdk:archivesType" />
+                        </xsd:all>
+                    </xsd:complexType>
+                </xsd:element>
+
+
+                <!-- The definition of an SDK extra package. This kind of package is for
+                     "free" content and specifies in which fixed root directory it must be
+                     installed.
+                -->
+
+                <xsd:element name="extra">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            An SDK extra package. This kind of package is for "free"
+                            content and specifies in which fixed root directory it must be
+                            installed.
+                            The paths "add-ons", "platforms", "tools" and "docs" are
+                            reserved and cannot be used.
+                        </xsd:documentation>
+                    </xsd:annotation>
+                    <xsd:complexType>
+                        <xsd:all>
+                            <!-- The install folder name. It must be a single-segment path.
+                                 The paths "add-ons", "platforms", "tools" and "docs" are
+                                 reserved and cannot be used.
+                            -->
+                            <xsd:element name="path">
+                                <xsd:simpleType>
+                                    <xsd:restriction base="xsd:token">
+                                        <xsd:pattern value="[^/\\]+"/>
+                                    </xsd:restriction>
+                                </xsd:simpleType>
+                            </xsd:element>
+
+                            <!-- The revision, an int > 0, incremented each time a new
+                                 package is generated. -->
+                            <xsd:element name="revision"     type="xsd:positiveInteger" />
                             <!-- The optional license of this package. If present, users will have
                                  to agree to it before downloading. -->
-                            <xsd:element name="license" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="uses-license" type="sdk:licenseType" minOccurs="0" />
                             <!-- The optional description of this package. -->
-                            <xsd:element name="description" type="xsd:string" minOccurs="0" />
+                            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
                             <!-- The optional description URL of this package -->
-                            <xsd:element name="desc-url"    type="xsd:token"  minOccurs="0" />
+                            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
                             <!-- A list of file archives for this package. -->
-                            <xsd:element name="archives"    type="sdk:archivesType" />
+                            <xsd:element name="archives"     type="sdk:archivesType" />
                         </xsd:all>
                     </xsd:complexType>
                 </xsd:element>
+
+
+
+                <!-- The definition of a license to be referenced by the uses-license element. -->
+
+                <xsd:element name="license">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            A license definition. Such a license must be used later as a reference
+                            using a uses-license element in one of the package elements.
+                        </xsd:documentation>
+                    </xsd:annotation>
+                    <xsd:complexType>
+                        <xsd:simpleContent>
+                            <xsd:extension base="xsd:string">
+                                <xsd:attribute name="id"   type="xsd:ID" />
+                                <xsd:attribute name="type" type="xsd:token" fixed="text" />
+                            </xsd:extension>
+                        </xsd:simpleContent>
+                    </xsd:complexType>
+                </xsd:element>
             </xsd:choice>
         </xsd:complexType>
     </xsd:element>
 
+
+    <!-- Type describing the license used by a package.
+         The license MUST be defined using a license node and referenced
+         using the ref attribute of the license element inside a package.
+     -->
+
+    <xsd:complexType name="licenseType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Describes the license used by a package. The license MUST be defined
+                using a license node and referenced using the ref attribute of the
+                license element inside a package.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:attribute name="ref" type="xsd:IDREF" />
+    </xsd:complexType>
+
+
     <!-- A collection of files that can be downloaded for a given architecture.
          The <archives> node is mandatory in the repository elements and the
          collection must have at least one <archive> declared.
         </xsd:sequence>
     </xsd:complexType>
 
+
     <!-- The definition of a file checksum -->
 
     <xsd:simpleType name="sha1Number">
         </xsd:simpleContent>
     </xsd:complexType>
 
+
 </xsd:schema>
index 879f3e6..207a5a2 100755 (executable)
@@ -16,6 +16,8 @@
 \r
 package com.android.sdklib.repository;\r
 \r
+import com.android.sdklib.SdkConstants;\r
+\r
 import org.xml.sax.ErrorHandler;\r
 import org.xml.sax.SAXException;\r
 import org.xml.sax.SAXParseException;\r
@@ -245,4 +247,57 @@ public class TestSdkRepository extends TestCase {
         // If we get here, the validator has not failed as we expected it to.\r
         fail();\r
     }\r
+\r
+    /** A document an unknown license id. */\r
+    public void testLicenseIdNotFound() throws Exception {\r
+        // we define a license named "lic1" and then reference "lic2" instead\r
+        String document = "<?xml version=\"1.0\"?>" +\r
+            "<r:sdk-repository xmlns:r=\"http://schemas.android.com/sdk/android/repository/1\" >" +\r
+            "<r:license id=\"lic1\"> some license </r:license> " +\r
+            "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision>1</r:revision> " +\r
+            "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +\r
+            "<r:url>url</r:url> </r:archive> </r:archives> </r:tool>" +\r
+            "</r:sdk-repository>";\r
+\r
+        Source source = new StreamSource(new StringReader(document));\r
+\r
+        // don't capture the validator errors, we want it to fail and catch the exception\r
+        Validator validator = getValidator(null);\r
+        try {\r
+            validator.validate(source);\r
+        } catch (SAXParseException e) {\r
+            // We expect a parse error referring to this grammar rule\r
+            assertRegex("cvc-id.1: There is no ID/IDREF binding for IDREF 'lic2'.*",\r
+                    e.getMessage());\r
+            return;\r
+        }\r
+        // If we get here, the validator has not failed as we expected it to.\r
+        fail();\r
+    }\r
+\r
+    /** A document a slash in an extra path. */\r
+    public void testExtraPathWithSlash() throws Exception {\r
+        // we define a license named "lic1" and then reference "lic2" instead\r
+        String document = "<?xml version=\"1.0\"?>" +\r
+            "<r:sdk-repository xmlns:r=\"http://schemas.android.com/sdk/android/repository/1\" >" +\r
+            "<r:extra> <r:revision>1</r:revision> <r:path>path/cannot\\contain\\segments</r:path> " +\r
+            "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +\r
+            "<r:url>url</r:url> </r:archive> </r:archives> </r:extra>" +\r
+            "</r:sdk-repository>";\r
+\r
+        Source source = new StreamSource(new StringReader(document));\r
+\r
+        // don't capture the validator errors, we want it to fail and catch the exception\r
+        Validator validator = getValidator(null);\r
+        try {\r
+            validator.validate(source);\r
+        } catch (SAXParseException e) {\r
+            // We expect a parse error referring to this grammar rule\r
+            assertRegex("cvc-pattern-valid: Value 'path/cannot\\\\contain\\\\segments' is not facet-valid with respect to pattern.*",\r
+                    e.getMessage());\r
+            return;\r
+        }\r
+        // If we get here, the validator has not failed as we expected it to.\r
+        fail();\r
+    }\r
 }\r
index a2d0cda..d4eacf9 100755 (executable)
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
     xmlns:sdk="http://schemas.android.com/sdk/android/repository/1">\r
 \r
+    <!-- Define a couple of licenses. These will be referenced by uses-license later. -->\r
+\r
+    <sdk:license type="text" id="license1">\r
+        This is the license\r
+        for this platform.\r
+    </sdk:license>\r
+\r
+    <sdk:license id="license2">\r
+        Licenses are only of type 'text' right now, so this is implied.\r
+    </sdk:license>\r
+\r
     <!-- Inner elements must be either platform, add-on, doc or tool.\r
          There can be 0 or more of each, in any order. -->\r
 \r
@@ -25,9 +36,8 @@
         <sdk:version>1.0</sdk:version>\r
         <sdk:api-level>1</sdk:api-level>\r
         <sdk:revision>3</sdk:revision>\r
+        <sdk:uses-license ref="license1" />\r
         <sdk:description>Some optional description</sdk:description>\r
-        <sdk:license>This is the license\r
-        for this platform.</sdk:license>\r
         <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>\r
         <!-- The archives node is mandatory and it cannot be empty. -->\r
         <sdk:archives>\r
@@ -42,8 +52,8 @@
     <sdk:doc>\r
         <sdk:api-level>1</sdk:api-level>\r
         <sdk:revision>1</sdk:revision>\r
-        <sdk:description>Some optional description</sdk:description>\r
         <!-- the license element is not mandatory. -->\r
+        <sdk:description>Some optional description</sdk:description>\r
         <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>\r
         <sdk:archives>\r
             <sdk:archive os="any">\r
@@ -59,8 +69,7 @@
         <sdk:api-level>1</sdk:api-level>\r
         <sdk:vendor>John Doe</sdk:vendor>\r
         <sdk:revision>1</sdk:revision>\r
-        <!-- license can be empty. -->\r
-        <sdk:license></sdk:license>\r
+        <sdk:uses-license ref="license2" />\r
         <sdk:description>Some optional description</sdk:description>\r
         <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>\r
         <sdk:archives>\r
@@ -87,7 +96,7 @@
         <sdk:version>1.1</sdk:version>\r
         <sdk:api-level>2</sdk:api-level>\r
         <sdk:revision>12</sdk:revision>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license1" />\r
         <!-- sdk:description and sdk:desc-url are optional -->\r
         <sdk:archives>\r
             <sdk:archive os="windows">\r
                 <sdk:name>com.android.mymaps</sdk:name>\r
             </sdk:lib>\r
         </sdk:libs>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license2" />\r
     </sdk:add-on>\r
 \r
     <sdk:tool>\r
         <sdk:revision>1</sdk:revision>\r
         <sdk:description>Some optional description</sdk:description>\r
         <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license1" />\r
         <sdk:archives>\r
             <sdk:archive os="any">\r
                 <sdk:size>65536</sdk:size>\r
     <sdk:doc>\r
         <sdk:api-level>2</sdk:api-level>\r
         <sdk:revision>42</sdk:revision>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license2" />\r
         <sdk:archives>\r
             <sdk:archive os="windows">\r
                 <sdk:size>65536</sdk:size>\r
 \r
     <sdk:tool>\r
         <sdk:revision>42</sdk:revision>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license1" />\r
         <sdk:archives>\r
             <sdk:archive os="windows">\r
                 <sdk:size>65536</sdk:size>\r
     </sdk:tool>\r
 \r
     <sdk:add-on>\r
-        <sdk:license>This is the license for this package.</sdk:license>\r
+        <sdk:uses-license ref="license2" />\r
         <sdk:name>This add-on has no libraries</sdk:name>\r
         <sdk:api-level>4</sdk:api-level>\r
         <sdk:vendor>Joe Bar</sdk:vendor>\r
         <sdk:libs />\r
     </sdk:add-on>\r
 \r
+    <sdk:extra>\r
+        <sdk:path>usb_driver</sdk:path>\r
+        <sdk:uses-license ref="license2" />\r
+        <sdk:revision>43</sdk:revision>\r
+        <sdk:archives>\r
+            <sdk:archive os="any" arch="any">\r
+                <sdk:size>65536</sdk:size>\r
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>\r
+                <sdk:url>distrib/extraduff.zip</sdk:url>\r
+            </sdk:archive>\r
+        </sdk:archives>\r
+        <sdk:description>An Extra package for the USB driver, it will install in $SDK/usb_driver</sdk:description>\r
+        <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>\r
+    </sdk:extra>\r
+\r
 </sdk:sdk-repository>\r
index 8cfe53f..d4c74d7 100755 (executable)
@@ -19,6 +19,7 @@ package com.android.sdkuilib.internal.repository.icons;
 import com.android.sdklib.internal.repository.AddonPackage;\r
 import com.android.sdklib.internal.repository.Archive;\r
 import com.android.sdklib.internal.repository.DocPackage;\r
+import com.android.sdklib.internal.repository.ExtraPackage;\r
 import com.android.sdklib.internal.repository.Package;\r
 import com.android.sdklib.internal.repository.PlatformPackage;\r
 import com.android.sdklib.internal.repository.RepoSource;\r
@@ -103,8 +104,8 @@ public class ImageFactory {
         } else if (object instanceof DocPackage) {\r
             return getImageByName("doc_icon16.png");\r
 \r
-        } else if (object instanceof Package) {\r
-            return getImageByName("extra_pkg_icon16.png");\r
+        } else if (object instanceof ExtraPackage) {\r
+            return getImageByName("extra_icon16.png");\r
 \r
         } else if (object instanceof Archive) {\r
             if (((Archive) object).isCompatible()) {\r
index 1a3e0f2..ca6a231 100755 (executable)
Binary files a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/addon_icon16.png and b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/addon_icon16.png differ
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/extra_icon16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/extra_icon16.png
new file mode 100755 (executable)
index 0000000..a6529f0
Binary files /dev/null and b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/extra_icon16.png differ