OSDN Git Service

SDK Updater: parse local packages, download and install remote packages.
authorRaphael <raphael@google.com>
Fri, 29 May 2009 21:02:50 +0000 (14:02 -0700)
committerRaphael <raphael@google.com>
Mon, 1 Jun 2009 18:38:51 +0000 (11:38 -0700)
The install phase is still work in progress.
The local part needs to display descriptions.
Buttons callback are generally not implemented yet.

20 files changed:
tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java [new file with mode: 0755]
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java [new file with mode: 0755]
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java [moved from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/InstalledPackagesPage.java with 60% similarity]
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java [new file with mode: 0755]
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java [moved from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvailablePackagesPage.java with 79% similarity]
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java [moved from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSources.java with 73% similarity]
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java

index ddfce4d..4464daa 100644 (file)
@@ -90,7 +90,7 @@ public class AndroidConstants {
 
     /** Name of the android sources directory */
     public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
-    
+
     /** Resource java class  filename, i.e. "R.java" */
     public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$
     /** Resource class file  filename, i.e. "R.class" */
@@ -104,15 +104,11 @@ public class AndroidConstants {
     /** Temporary packaged resources file name for a specific set of configuration */
     public final static String FN_RESOURCES_S_AP_ = "resources-%s.ap_"; //$NON-NLS-1$
     public final static Pattern PATTERN_RESOURCES_S_AP_ =
-        Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE);
+        Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
 
-    public final static String FN_ADB =
-        (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
-            "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
+    public final static String FN_ADB = SdkConstants.FN_ADB;
 
-    public final static String FN_EMULATOR =
-        (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
-            "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
+    public final static String FN_EMULATOR = SdkConstants.FN_EMULATOR;
 
     public final static String FN_TRACEVIEW =
         (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
@@ -128,8 +124,8 @@ public class AndroidConstants {
     public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS;
 
     /** Leaf of the javaDoc folder. Does not start with a separator. */
-    public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/" +
-            SdkConstants.FD_DOCS_REFERENCE; //$NON-NLS-1$
+    public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/" + //$NON-NLS-1$
+            SdkConstants.FD_DOCS_REFERENCE;
 
     /** Path of the samples directory relative to the sdk folder.
      *  This is an OS path, ending with a separator.
@@ -159,10 +155,10 @@ public class AndroidConstants {
 
     /** aidl marker error. */
     public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
-    
+
     /** android marker error */
     public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$
-    
+
     /** Name for the "type" marker attribute */
     public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$
     /** Name for the "class" marker attribute */
@@ -176,9 +172,9 @@ public class AndroidConstants {
     /** provider value for marker attribute "type" */
     public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$
 
-    public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ 
-    public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ 
-    public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ 
+    public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
+    public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
+    public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
     public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
     public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
     public final static String CLASS_INSTRUMENTATION_RUNNER =
@@ -202,7 +198,7 @@ public class AndroidConstants {
         "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$
     public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
     public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
-    
+
     public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
 
     /**
@@ -219,6 +215,6 @@ public class AndroidConstants {
 
     /** The base URL where to find the Android class & manifest documentation */
     public static final String CODESITE_BASE_URL = "http://code.google.com/android";  //$NON-NLS-1$
-    
+
     public static final String LIBRARY_TEST_RUNNER = "android.test.runner"; // $NON-NLS-1$
 }
index 557f354..32b9a2e 100644 (file)
@@ -82,18 +82,26 @@ public final class SdkConstants {
     /** dex.jar file */
     public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
 
-    /** dx executable */
+    /** dx executable (with extension for the current OS)  */
     public final static String FN_DX = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
             "dx.bat" : "dx"; //$NON-NLS-1$ //$NON-NLS-2$
 
-    /** aapt executable */
+    /** aapt executable (with extension for the current OS)  */
     public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
             "aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$
 
-    /** aidl executable */
+    /** aidl executable (with extension for the current OS)  */
     public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
             "aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$
 
+    /** adb executable (with extension for the current OS)  */
+    public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
+            "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
+
+    /** emulator executable (with extension for the current OS) */
+    public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
+            "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
+
     /* Folder Names for Android Projects . */
 
     /** Resources folder name, i.e. "res". */
index becdd57..bd76a4c 100755 (executable)
 \r
 package com.android.sdklib.internal.repository;\r
 \r
+import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;\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.ArrayList;\r
 \r
 /**\r
@@ -57,8 +64,8 @@ public class AddonPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    AddonPackage(Node packageNode) {\r
-        super(packageNode);\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
@@ -66,6 +73,41 @@ public class AddonPackage extends Package {
         mLibs = parseLibs(getFirstChild(packageNode, SdkRepository.NODE_LIBS));\r
     }\r
 \r
+    /**\r
+     * Creates a new platform package based on an actual {@link IAndroidTarget} (with\r
+     * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.\r
+     * This is used to list local SDK folders.\r
+     */\r
+    AddonPackage(IAndroidTarget target) {\r
+        super(  null,                       //source\r
+                0,                          //revision\r
+                target.getDescription(),  //description\r
+                null,                       //descUrl\r
+                Os.getCurrentOs(),          //archiveOs\r
+                Arch.getCurrentArch(),      //archiveArch\r
+                "",                         //archiveUrl   //$NON-NLS-1$\r
+                0,                          //archiveSize\r
+                null                        //archiveChecksum\r
+                );\r
+\r
+        mApiLevel = target.getApiVersionNumber();\r
+        mName     = target.getName();\r
+        mVendor   = target.getVendor();\r
+\r
+        IOptionalLibrary[] optLibs = target.getOptionalLibraries();\r
+        if (optLibs == null || optLibs.length == 0) {\r
+            mLibs = new Lib[0];\r
+        } else {\r
+            mLibs = new Lib[optLibs.length];\r
+            for (int i = 0; i < optLibs.length; i++) {\r
+                mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription());\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Parses a <libs> element.\r
+     */\r
     private Lib[] parseLibs(Node libsNode) {\r
         ArrayList<Lib> libs = new ArrayList<Lib>();\r
 \r
@@ -85,6 +127,9 @@ public class AddonPackage extends Package {
         return libs.toArray(new Lib[libs.size()]);\r
     }\r
 \r
+    /**\r
+     * 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
@@ -126,4 +171,31 @@ public class AddonPackage extends Package {
                 getShortDescription(),\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
+     * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level".\r
+     * The name needs to be sanitized to be acceptable as a directory name.\r
+     * However if we can find a different directory under SDK/add-ons that already\r
+     * has this add-ons installed, we'll use that one.\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
+        File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);\r
+\r
+        String name = String.format("%s-%d", getName(), getApiLevel()); // $NON-NLS-1$\r
+\r
+        name = name.replaceAll("[^a-zA-Z0-9_-]+", "_");                 // $NON-NLS-1$\r
+        name = name.replaceAll("_+", "_");                              // $NON-NLS-1$\r
+\r
+        File folder = new File(addons, name);\r
+\r
+        // TODO find similar existing addon in addons folder\r
+        return folder;\r
+    }\r
 }\r
index 267c179..9686cbd 100755 (executable)
@@ -16,6 +16,9 @@
 \r
 package com.android.sdklib.internal.repository;\r
 \r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
 \r
 /**\r
  * A {@link Archive} is the base class for "something" that can be downloaded from\r
@@ -32,23 +35,106 @@ public class Archive implements IDescription {
     /** The checksum type. */\r
     public enum ChecksumType {\r
         /** A SHA1 checksum, represented as a 40-hex string. */\r
-        SHA1\r
+        SHA1("SHA-1");  //$NON-NLS-1$\r
+\r
+        private final String mAlgorithmName;\r
+\r
+        /**\r
+         * Constructs a {@link ChecksumType} with the algorigth name\r
+         * suitable for {@link MessageDigest#getInstance(String)}.\r
+         * <p/>\r
+         * These names are officially documented at\r
+         * http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest\r
+         */\r
+        private ChecksumType(String algorithmName) {\r
+            mAlgorithmName = algorithmName;\r
+        }\r
+\r
+        /**\r
+         * Returns a new {@link MessageDigest} instance for this checksum type.\r
+         * @throws NoSuchAlgorithmException if this algorithm is not available.\r
+         */\r
+        public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {\r
+            return MessageDigest.getInstance(mAlgorithmName);\r
+        }\r
     }\r
 \r
     /** The OS that this archive can be downloaded on. */\r
     public enum Os {\r
-        ANY,\r
-        LINUX,\r
-        MACOSX,\r
-        WINDOWS\r
+        ANY("Any"),\r
+        LINUX("Linux"),\r
+        MACOSX("MacOS X"),\r
+        WINDOWS("Windows");\r
+\r
+        private final String mUiName;\r
+\r
+        private Os(String uiName) {\r
+            mUiName = uiName;\r
+        }\r
+\r
+        @Override\r
+        public String toString() {\r
+            return mUiName;\r
+        }\r
+\r
+        /**\r
+         * Returns the current OS as one of the {@link Os} enum values or null.\r
+         */\r
+        public static Os getCurrentOs() {\r
+            String os = System.getProperty("os.name");          //$NON-NLS-1$\r
+            if (os.startsWith("Mac OS")) {                      //$NON-NLS-1$\r
+                return Os.MACOSX;\r
+\r
+            } else if (os.startsWith("Windows")) {              //$NON-NLS-1$\r
+                return Os.WINDOWS;\r
+\r
+            } else if (os.startsWith("Linux")) {                //$NON-NLS-1$\r
+                return Os.LINUX;\r
+            }\r
+\r
+            return null;\r
+        }\r
     }\r
 \r
-    /** The Architecture that this archvie can be downloaded on. */\r
+    /** The Architecture that this archive can be downloaded on. */\r
     public enum Arch {\r
-        ANY,\r
-        PPC,\r
-        X86,\r
-        X86_64\r
+        ANY("Any"),\r
+        PPC("PowerPC"),\r
+        X86("x86"),\r
+        X86_64("x86_64");\r
+\r
+        private final String mUiName;\r
+\r
+        private Arch(String uiName) {\r
+            mUiName = uiName;\r
+        }\r
+\r
+        @Override\r
+        public String toString() {\r
+            return mUiName;\r
+        }\r
+\r
+        /**\r
+         * Returns the current architecture as one of the {@link Arch} enum values or null.\r
+         */\r
+        public static Arch getCurrentArch() {\r
+            // Values listed from http://lopica.sourceforge.net/os.html\r
+            String arch = System.getProperty("os.arch");\r
+\r
+            if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) {\r
+                return Arch.X86_64;\r
+\r
+            } else if (arch.equalsIgnoreCase("x86")\r
+                    || arch.equalsIgnoreCase("i386")\r
+                    || arch.equalsIgnoreCase("i686")) {\r
+                return Arch.X86;\r
+\r
+            } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) {\r
+                return Arch.PPC;\r
+            }\r
+\r
+            return null;\r
+        }\r
     }\r
 \r
     private final Os     mOs;\r
@@ -57,11 +143,13 @@ public class Archive implements IDescription {
     private final long   mSize;\r
     private final String mChecksum;\r
     private final ChecksumType mChecksumType = ChecksumType.SHA1;\r
+    private final Package mPackage;\r
 \r
     /**\r
      * Creates a new archive.\r
      */\r
-    Archive(Os os, Arch arch, String url, long size, String checksum) {\r
+    Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {\r
+        mPackage = pkg;\r
         mOs = os;\r
         mArch = arch;\r
         mUrl = url;\r
@@ -69,63 +157,117 @@ public class Archive implements IDescription {
         mChecksum = checksum;\r
     }\r
 \r
-    /** Returns the archive size, an int > 0. */\r
+    /**\r
+     * Returns the package that created and owns this archive.\r
+     * It should generally not be null.\r
+     */\r
+    public Package getParentPackage() {\r
+        return mPackage;\r
+    }\r
+\r
+    /**\r
+     * Returns the archive size, an int > 0.\r
+     * Size will be 0 if this a local installed folder of unknown size.\r
+     */\r
     public long getSize() {\r
         return mSize;\r
     }\r
 \r
-    /** Returns the SHA1 archive checksum, as a 40-char hex. */\r
+    /**\r
+     * Returns the SHA1 archive checksum, as a 40-char hex.\r
+     * Can be empty but not null for local installed folders.\r
+     */\r
     public String getChecksum() {\r
         return mChecksum;\r
     }\r
 \r
-    /** Returns the checksum type, always {@link ChecksumType#SHA1} right now. */\r
+    /**\r
+     * Returns the checksum type, always {@link ChecksumType#SHA1} right now.\r
+     */\r
     public ChecksumType getChecksumType() {\r
         return mChecksumType;\r
     }\r
 \r
-    /** Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
-     * Can be empty but not null. */\r
-    public String getDescUrl() {\r
+    /**\r
+     * Returns the download archive URL, either absolute or relative to the repository xml.\r
+     * For a local installed folder, an URL is frabricated from the folder path.\r
+     */\r
+    public String getUrl() {\r
         return mUrl;\r
     }\r
 \r
-    /** Returns the archive {@link Os} enum. */\r
+    /**\r
+     * Returns the archive {@link Os} enum.\r
+     * Can be null for a local installed folder on an unknown OS.\r
+     */\r
     public Os getOs() {\r
         return mOs;\r
     }\r
 \r
-    /** Returns the archive {@link Arch} enum. */\r
+    /**\r
+     * Returns the archive {@link Arch} enum.\r
+     * Can be null for a local installed folder on an unknown architecture.\r
+     */\r
     public Arch getArch() {\r
         return mArch;\r
     }\r
 \r
+    /**\r
+     * Generates a short description for this archive.\r
+     */\r
     public String getShortDescription() {\r
-        String os = "any OS";\r
-        if (mOs != Os.ANY) {\r
-            os = capitalize(mOs.toString());\r
+        String os;\r
+        if (mOs == null) {\r
+            os = "unknown OS";\r
+        } else if (mOs == Os.ANY) {\r
+            os = "any OS";\r
+        } else {\r
+            os = mOs.toString();\r
         }\r
 \r
         String arch = "";\r
-        if (mArch != Arch.ANY) {\r
-            arch = mArch.toString().toLowerCase();\r
+        if (mArch != null && mArch != Arch.ANY) {\r
+            arch = mArch.toString();\r
         }\r
 \r
         return String.format("Archive for %1$s %2$s", os, arch);\r
     }\r
 \r
-    private String capitalize(String string) {\r
-        if (string.length() > 1) {\r
-            return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();\r
-        } else {\r
-            return string.toUpperCase();\r
-        }\r
-    }\r
-\r
+    /**\r
+     * Generates a longer description for this archive.\r
+     */\r
     public String getLongDescription() {\r
         return String.format("%1$s\nSize: %2$d MiB\nSHA1: %3$s",\r
                 getShortDescription(),\r
                 Math.round(getSize() / (1024*1024)),\r
                 getChecksum());\r
     }\r
+\r
+    /**\r
+     * Returns true if this archive can be installed on the current platform.\r
+     */\r
+    public boolean isCompatible() {\r
+        // Check OS\r
+        Os os = getOs();\r
+\r
+        if (os != Os.ANY) {\r
+            Os os2 = Os.getCurrentOs();\r
+            if (os2 != os) {\r
+                return false;\r
+            }\r
+        }\r
+\r
+        // Check Arch\r
+        Arch arch = getArch();\r
+\r
+        if (arch != Arch.ANY) {\r
+            Arch arch2 = Arch.getCurrentArch();\r
+            if (arch2 != arch) {\r
+                return false;\r
+            }\r
+        }\r
+\r
+        return true;\r
+    }\r
 }\r
+\r
index 7601766..8f07255 100755 (executable)
 \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
+\r
 /**\r
  * Represents a doc XML node in an SDK repository.\r
  */\r
@@ -32,12 +37,39 @@ public class DocPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    DocPackage(Node packageNode) {\r
-        super(packageNode);\r
+    DocPackage(RepoSource source, Node packageNode) {\r
+        super(source, packageNode);\r
         mApiLevel = getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0);\r
     }\r
 \r
-    /** Returns the api-level, an int > 0, for platform, add-on and doc packages. */\r
+    /**\r
+     * Manually create a new package with one archive and the given attributes.\r
+     * This is used to create packages from local directories.\r
+     */\r
+    DocPackage(RepoSource source,\r
+            int apiLevel,\r
+            int revision,\r
+            String description,\r
+            String descUrl,\r
+            Os archiveOs,\r
+            Arch archiveArch,\r
+            String archiveUrl,\r
+            long archiveSize,\r
+            String archiveChecksum) {\r
+        super(source,\r
+                revision,\r
+                description,\r
+                descUrl,\r
+                archiveOs,\r
+                archiveArch,\r
+                archiveUrl,\r
+                archiveSize,\r
+                archiveChecksum);\r
+        mApiLevel = apiLevel;\r
+    }\r
+\r
+    /** Returns the api-level, an int > 0, for platform, add-on and doc packages.\r
+     *  Can be 0 if this is a local package of unknown api-level. */\r
     public int getApiLevel() {\r
         return mApiLevel;\r
     }\r
@@ -45,7 +77,11 @@ public class DocPackage extends Package {
     /** Returns a short description for an {@link IDescription}. */\r
     @Override\r
     public String getShortDescription() {\r
-        return String.format("Documentation for SDK Android API %1$d", getApiLevel());\r
+        if (mApiLevel != 0) {\r
+            return String.format("Documentation for Android SDK, API %1$d", mApiLevel);\r
+        } else {\r
+            return String.format("Documentation for Android SDK");\r
+        }\r
     }\r
 \r
     /** Returns a long description for an {@link IDescription}. */\r
@@ -55,4 +91,18 @@ public class DocPackage extends Package {
                 getShortDescription(),\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 "doc" package should always be located in SDK/docs.\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, SdkConstants.FD_DOCS);\r
+    }\r
 }\r
index 832d246..05c982d 100755 (executable)
@@ -34,13 +34,13 @@ public interface ITaskMonitor {
      * Sets the description in the current task dialog.\r
      * This method can be invoked from a non-UI thread.\r
      */\r
-    public void setDescription(String description);\r
+    public void setDescription(String descriptionFormat, Object...args);\r
 \r
     /**\r
      * Sets the result text in the current task dialog.\r
      * This method can be invoked from a non-UI thread.\r
      */\r
-    public void setResult(String result);\r
+    public void setResult(String resultFormat, Object...args);\r
 \r
     /**\r
      * Sets the max value of the progress bar.\r
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
new file mode 100755 (executable)
index 0000000..f150510
--- /dev/null
@@ -0,0 +1,415 @@
+/*\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.IAndroidTarget;\r
+import com.android.sdklib.ISdkLog;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\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.Document;\r
+import org.w3c.dom.Node;\r
+import org.xml.sax.InputSource;\r
+import org.xml.sax.SAXException;\r
+\r
+import java.io.BufferedReader;\r
+import java.io.File;\r
+import java.io.FileReader;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.StringReader;\r
+import java.net.MalformedURLException;\r
+import java.util.ArrayList;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import javax.xml.XMLConstants;\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import javax.xml.parsers.ParserConfigurationException;\r
+import javax.xml.transform.stream.StreamSource;\r
+import javax.xml.validation.Schema;\r
+import javax.xml.validation.SchemaFactory;\r
+import javax.xml.validation.Validator;\r
+\r
+/**\r
+ * Scans a local SDK to find which packages are currently installed.\r
+ */\r
+public class LocalSdkParser {\r
+\r
+    private static final String SOURCE_XML = "source.xml";  //$NON-NLS-1$ // TODO move to global constants\r
+    private Package[] mPackages;\r
+\r
+    public LocalSdkParser() {\r
+        // TODO Auto-generated constructor stub\r
+    }\r
+\r
+    /**\r
+     * Returns the packages found by the last call to {@link #parseSdk(String)}.\r
+     */\r
+    public Package[] getPackages() {\r
+        return mPackages;\r
+    }\r
+\r
+    /**\r
+     * Clear the internal packages list. After this call, {@link #getPackages()} will return\r
+     * null till {@link #parseSdk(String)} is called.\r
+     */\r
+    public void clearPackages() {\r
+        mPackages = null;\r
+    }\r
+\r
+    /**\r
+     * Scan the give SDK to find all the packages already installed at this location.\r
+     * <p/>\r
+     * Store the packages internally. You can use {@link #getPackages()} to retrieve them\r
+     * at any time later.\r
+     *\r
+     * @param osSdkRoot The path to the SDK folder.\r
+     * @return The packages found. Can be retrieved later using {@link #getPackages()}.\r
+     */\r
+    public Package[] parseSdk(String osSdkRoot) {\r
+        ArrayList<Package> packages = new ArrayList<Package>();\r
+\r
+        Package pkg = scanDoc(new File(osSdkRoot, SdkConstants.FD_DOCS));\r
+        if (pkg != null) {\r
+            packages.add(pkg);\r
+        }\r
+\r
+        pkg = scanTools(new File(osSdkRoot, SdkConstants.FD_TOOLS));\r
+        if (pkg != null) {\r
+            packages.add(pkg);\r
+        }\r
+\r
+        // for platforms and add-ons, rely on the SdkManager parser\r
+        SdkManager sdkman = SdkManager.createManager(osSdkRoot, new ISdkLog() {\r
+            // A dummy sdk logger that doesn't log anything.\r
+            public void error(Throwable t, String errorFormat, Object... args) {\r
+                // pass\r
+            }\r
+            public void printf(String msgFormat, Object... args) {\r
+                // pass\r
+            }\r
+            public void warning(String warningFormat, Object... args) {\r
+                // pass\r
+            }\r
+        });\r
+\r
+        for(IAndroidTarget target : sdkman.getTargets()) {\r
+            pkg = null;\r
+\r
+            if (target.isPlatform()) {\r
+                pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
+                               SdkRepository.NODE_PLATFORM);\r
+                if (pkg == null) {\r
+                    pkg = new PlatformPackage(target);\r
+                }\r
+\r
+            } else {\r
+                pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
+                                        SdkRepository.NODE_ADD_ON);\r
+\r
+                if (pkg == null) {\r
+                    pkg = new AddonPackage(target);\r
+                }\r
+            }\r
+\r
+            if (pkg != null) {\r
+                packages.add(pkg);\r
+            }\r
+        }\r
+\r
+        mPackages = packages.toArray(new Package[packages.size()]);\r
+        return mPackages;\r
+    }\r
+\r
+    /**\r
+     * Try to find a tools package at the given location.\r
+     * Returns null if not found.\r
+     */\r
+    private Package scanTools(File toolFolder) {\r
+        // Can we find a source.xml?\r
+        Package pkg = parseXml(new File(toolFolder, SOURCE_XML), SdkRepository.NODE_TOOL);\r
+\r
+        // We're not going to check that all tools are present. At the very least\r
+        // we should expect to find adb, android and an emulator adapted to the current OS.\r
+        Set<String> names = new HashSet<String>();\r
+        for (File file : toolFolder.listFiles()) {\r
+            names.add(file.getName());\r
+        }\r
+        if (!names.contains(SdkConstants.FN_ADB) ||\r
+                !names.contains(SdkConstants.androidCmdName()) ||\r
+                !names.contains(SdkConstants.FN_EMULATOR)) {\r
+            return null;\r
+        }\r
+\r
+        // if we don't have the package info, make one up\r
+        if (pkg == null) {\r
+            pkg = new ToolPackage(\r
+                    null,                       //source\r
+                    0,                          //revision\r
+                    "Tools",                    //description\r
+                    null,                       //descUrl\r
+                    Os.getCurrentOs(),          //archiveOs\r
+                    Arch.getCurrentArch(),      //archiveArch\r
+                    "",                         //archiveUrl   //$NON-NLS-1$\r
+                    0,                          //archiveSize\r
+                    null                        //archiveChecksum\r
+                    );\r
+        }\r
+\r
+        return pkg;\r
+    }\r
+\r
+    /**\r
+     * Try to find a docs package at the given location.\r
+     * Returns null if not found.\r
+     */\r
+    private Package scanDoc(File docFolder) {\r
+        // Can we find a source.xml?\r
+        Package pkg = parseXml(new File(docFolder, SOURCE_XML), SdkRepository.NODE_DOC);\r
+\r
+        // To start with, a doc folder should have an "index.html" to be acceptable.\r
+        String html = readFile(new File(docFolder, "index.html"));\r
+        if (html != null) {\r
+            // Try to find something that looks like this line:\r
+            //   <a href="./sdk/1.5_r1/index.html">\r
+            // We should find one or more of these and we want the highest version\r
+            // and release numbers. Note that unfortunately that doesn't give us\r
+            // the api-level we care about for the doc package.\r
+\r
+            String found = null;\r
+            Pattern re = Pattern.compile(\r
+                    "<a\\s+href=\"./sdk/(\\d\\.\\d_r\\d)/index.html\">",\r
+                    Pattern.DOTALL);\r
+            Matcher m = re.matcher(html);\r
+            while(m.find()) {\r
+                String v = m.group(1);\r
+                if (found == null || v.compareTo(found) == 1) {\r
+                    found = v;\r
+                }\r
+            }\r
+\r
+            if (found == null) {\r
+                // That doesn't look like a doc folder.\r
+                return null;\r
+            }\r
+\r
+            // We found the line, so it seems like an SDK doc.\r
+            // Create a pkg if we don't have one yet.\r
+\r
+            if (pkg == null) {\r
+                String url = null;\r
+                try {\r
+                    url = docFolder.toURI().toURL().toString();\r
+                } catch (MalformedURLException e) {\r
+                    // ignore\r
+                }\r
+\r
+                pkg = new DocPackage(\r
+                        null,                       //source\r
+                        0,                          //apiLevel\r
+                        0,                          //revision\r
+                        String.format("Documentation for %1$s", found),     //description\r
+                        null,                       //descUrl\r
+                        Os.getCurrentOs(),          //archiveOs\r
+                        Arch.getCurrentArch(),      //archiveArch\r
+                        url,                        //archiveUrl\r
+                        0,                          //archiveSize\r
+                        null                        //archiveChecksum\r
+                        );\r
+            }\r
+        }\r
+\r
+        return pkg;\r
+    }\r
+\r
+    /**\r
+     * Parses the given XML file for the specific element filter.\r
+     * The element must one of the package type local names: doc, tool, platform or addon.\r
+     * Returns null if no such package was found.\r
+     */\r
+    private Package parseXml(File sourceXmlFile, String elementFilter) {\r
+\r
+        String xml = readFile(sourceXmlFile);\r
+        if (xml != null) {\r
+            if (validateXml(xml)) {\r
+                return parsePackages(xml, elementFilter);\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Parses the given XML to find the specific element filter.\r
+     * The element must one of the package type local names: doc, tool, platform or addon.\r
+     * Returns null if no such package was found.\r
+     */\r
+    private Package parsePackages(String xml, String elementFilter) {\r
+\r
+        try {\r
+            Document doc = getDocument(xml);\r
+\r
+            Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY);\r
+            if (root != null) {\r
+\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
+                            elementFilter.equals(child.getLocalName())) {\r
+                        String name = child.getLocalName();\r
+                        Package p = null;\r
+\r
+                        try {\r
+                            if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
+                                return new AddonPackage(null /*source*/, child);\r
+\r
+                            } else if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
+                                return new PlatformPackage(null /*source*/, child);\r
+\r
+                            } else if (SdkRepository.NODE_DOC.equals(name)) {\r
+                                return new DocPackage(null /*source*/, child);\r
+\r
+                            } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
+                                return new ToolPackage(null /*source*/, child);\r
+                            }\r
+                        } catch (Exception e) {\r
+                            // Ignore invalid packages\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+\r
+        } catch (Exception e) {\r
+            // ignore\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Reads a file as a string.\r
+     * Returns null if the file could not be read.\r
+     */\r
+    private String readFile(File sourceXmlFile) {\r
+        FileReader fr = null;\r
+        try {\r
+            fr = new FileReader(sourceXmlFile);\r
+            BufferedReader br = new BufferedReader(fr);\r
+            StringBuilder dest = new StringBuilder();\r
+            char[] buf = new char[65536];\r
+            int n;\r
+            while ((n = br.read(buf)) > 0) {\r
+                if (n > 0) {\r
+                    dest.append(buf, 0, n);\r
+                }\r
+            }\r
+            return dest.toString();\r
+\r
+        } catch (IOException e) {\r
+            // ignore\r
+\r
+        } finally {\r
+            if (fr != null) {\r
+                try {\r
+                    fr.close();\r
+                } catch (IOException e) {\r
+                    // ignore\r
+                }\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Validates this XML against the SDK Repository schema.\r
+     * Returns true if the XML was correctly validated.\r
+     */\r
+    private boolean validateXml(String xml) {\r
+\r
+        try {\r
+            Validator validator = getValidator();\r
+            validator.validate(new StreamSource(new StringReader(xml)));\r
+            return true;\r
+\r
+        } catch (SAXException e) {\r
+            // ignore\r
+\r
+        } catch (IOException e) {\r
+            // ignore\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Helper method that returns a validator for our XSD\r
+     */\r
+    private Validator getValidator() throws SAXException {\r
+        InputStream xsdStream = SdkRepository.getXsdStream();\r
+        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
+\r
+        // This may throw a SAX Exception if the schema itself is not a valid XSD\r
+        Schema schema = factory.newSchema(new StreamSource(xsdStream));\r
+\r
+        Validator validator = schema.newValidator();\r
+\r
+        return validator;\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
+    private 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 || child.getLocalName().equals(xmlLocalName)) {\r
+                    return child;\r
+                }\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Takes an XML document as a string as parameter and returns a DOM for it.\r
+     */\r
+    private Document getDocument(String xml)\r
+            throws ParserConfigurationException, SAXException, IOException {\r
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
+        factory.setIgnoringComments(true);\r
+        factory.setNamespaceAware(true);\r
+\r
+        DocumentBuilder builder = factory.newDocumentBuilder();\r
+        Document doc = builder.parse(new InputSource(new StringReader(xml)));\r
+\r
+        return doc;\r
+    }\r
+}\r
index 061eb16..55ecaef 100755 (executable)
@@ -22,6 +22,7 @@ import com.android.sdklib.repository.SdkRepository;
 \r
 import org.w3c.dom.Node;\r
 \r
+import java.io.File;\r
 import java.util.ArrayList;\r
 \r
 /**\r
@@ -42,13 +43,15 @@ public abstract class Package implements IDescription {
     private final String mDescription;\r
     private final String mDescUrl;\r
     private final Archive[] mArchives;\r
+    private final RepoSource mSource;\r
 \r
     /**\r
      * Creates a new 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
-    Package(Node packageNode) {\r
+    Package(RepoSource source, Node packageNode) {\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
@@ -56,6 +59,35 @@ public abstract class Package implements IDescription {
         mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES));\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.\r
+     */\r
+    public Package(RepoSource source,\r
+            int revision,\r
+            String description,\r
+            String descUrl,\r
+            Os archiveOs,\r
+            Arch archiveArch,\r
+            String archiveUrl,\r
+            long archiveSize,\r
+            String archiveChecksum) {\r
+        mSource = source;\r
+        mRevision = revision;\r
+        mDescription = description;\r
+        mDescUrl = descUrl;\r
+        mArchives = new Archive[1];\r
+        mArchives[0] = new Archive(this,\r
+                archiveOs,\r
+                archiveArch,\r
+                archiveUrl,\r
+                archiveSize,\r
+                archiveChecksum);\r
+    }\r
+\r
+    /**\r
+     * Parses an XML node to process the <archives> element.\r
+     */\r
     private Archive[] parseArchives(Node archivesNode) {\r
         ArrayList<Archive> archives = new ArrayList<Archive>();\r
 \r
@@ -75,52 +107,99 @@ public abstract class Package implements IDescription {
         return archives.toArray(new Archive[archives.size()]);\r
     }\r
 \r
+    /**\r
+     * Parses one <archive> element from an <archives> container.\r
+     */\r
     private Archive parseArchive(Node archiveNode) {\r
         Archive a = new Archive(\r
+                    this,\r
                     (Os)   getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,\r
                             Os.values(), null),\r
                     (Arch) getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,\r
                             Arch.values(), Arch.ANY),\r
                     getXmlString(archiveNode, SdkRepository.NODE_URL),\r
-                    getXmlInt(archiveNode, SdkRepository.NODE_SIZE, 0),\r
+                    getXmlLong(archiveNode, SdkRepository.NODE_SIZE, 0),\r
                     getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)\r
                 );\r
 \r
         return a;\r
     }\r
 \r
-    /** Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). */\r
+    /**\r
+     * Returns the source that created (and owns) this package. Can be null.\r
+     */\r
+    public RepoSource getParentSource() {\r
+        return mSource;\r
+    }\r
+\r
+    /**\r
+     * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).\r
+     * Can be 0 if this is a local package of unknown revision.\r
+     */\r
     public int getRevision() {\r
         return mRevision;\r
     }\r
 \r
-    /** Returns the optional description for all packages (platform, add-on, tool, doc) or\r
-     *  for a lib. */\r
+    /**\r
+     * Returns the optional description for all packages (platform, add-on, tool, doc) or\r
+     * for a lib. Can be empty but not null.\r
+     */\r
     public String getDescription() {\r
         return mDescription;\r
     }\r
 \r
-    /** Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
-     * Can be empty but not null. */\r
+    /**\r
+     * Returns the optional description URL for all packages (platform, add-on, tool, doc).\r
+     * Can be empty but not null.\r
+     */\r
     public String getDescUrl() {\r
         return mDescUrl;\r
     }\r
 \r
-    /** Returns the archives defined in this package. Can be an empty array but not null. */\r
+    /**\r
+     * Returns the archives defined in this package.\r
+     * Can be an empty array but not null.\r
+     */\r
     public Archive[] getArchives() {\r
         return mArchives;\r
     }\r
 \r
-    /** Returns a short description for an {@link IDescription}. */\r
+    /**\r
+     * Returns a short description for an {@link IDescription}.\r
+     * Can be empty but not null.\r
+     */\r
     public abstract String getShortDescription();\r
 \r
-    /** Returns a long description for an {@link IDescription}. */\r
+    /**\r
+     * Returns a long description for an {@link IDescription}.\r
+     * Can be empty but not null.\r
+     */\r
     public String getLongDescription() {\r
         return String.format("%1$s\nRevision %2$d", getDescription(), getRevision());\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
+     * Some types of packages install in a fix location, for example docs and tools.\r
+     * In this case the returned folder may already exist with a different archive installed\r
+     * at the desired location.\r
+     * For other packages types, such as add-on or platform, the folder name is only partially\r
+     * relevant to determine the content and thus a real check will be done to provide an\r
+     * existing or new folder depending on the current content of the SDK.\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
+    public abstract File getInstallFolder(String osSdkRoot);\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
@@ -159,6 +238,19 @@ public abstract class Package implements IDescription {
     }\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
index 8284c08..0d51c58 100755 (executable)
 \r
 package com.android.sdklib.internal.repository;\r
 \r
+import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SdkManager;\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
+\r
 /**\r
  * Represents a platform XML node in an SDK repository.\r
  */\r
@@ -33,12 +40,33 @@ public class PlatformPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    PlatformPackage(Node packageNode) {\r
-        super(packageNode);\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
     }\r
 \r
+    /**\r
+     * Creates a new platform package based on an actual {@link IAndroidTarget} (with\r
+     * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.\r
+     * This is used to list local SDK folders.\r
+     */\r
+    PlatformPackage(IAndroidTarget target) {\r
+        super(  null,                       //source\r
+                0,                          //revision\r
+                target.getDescription(),  //description\r
+                null,                       //descUrl\r
+                Os.getCurrentOs(),          //archiveOs\r
+                Arch.getCurrentArch(),      //archiveArch\r
+                "",                         //archiveUrl   //$NON-NLS-1$\r
+                0,                          //archiveSize\r
+                null                        //archiveChecksum\r
+                );\r
+\r
+        mApiLevel = target.getApiVersionNumber();\r
+        mVersion  = target.getApiVersionName();\r
+    }\r
+\r
     /** Returns the version, a string, for platform packages. */\r
     public String getVersion() {\r
         return mVersion;\r
@@ -64,4 +92,23 @@ public class PlatformPackage extends Package {
                 getShortDescription(),\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 platform package is typically installed in SDK/platforms/android-"version".\r
+     * However if we can find a different directory under SDK/platform that already\r
+     * has this platform version installed, we'll use that one.\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
+        File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);\r
+        File folder = new File(platforms, String.format("android-%s", getVersion())); //$NON-NLS-1$\r
+        // TODO find similar existing platform in platforms folder\r
+        return folder;\r
+    }\r
 }\r
index 20777c3..1fd880f 100755 (executable)
@@ -68,12 +68,21 @@ public class RepoSource implements IDescription {
     }\r
 \r
     /**\r
-     * Returns the list of known packages. This is null when the source hasn't been loaded yet.\r
+     * Returns the list of known packages found by the last call to {@link #load(ITaskFactory)}.\r
+     * This is null when the source hasn't been loaded yet.\r
      */\r
     public Package[] getPackages() {\r
         return mPackages;\r
     }\r
 \r
+    /**\r
+     * Clear the internal packages list. After this call, {@link #getPackages()} will return\r
+     * null till {@link #load(ITaskFactory)} is called.\r
+     */\r
+    public void clearPackages() {\r
+        mPackages = null;\r
+    }\r
+\r
     public String getShortDescription() {\r
         return mUrl;\r
     }\r
@@ -93,7 +102,7 @@ public class RepoSource implements IDescription {
 \r
                 setDefaultDescription();\r
 \r
-                monitor.setDescription(String.format("Fetching %1$s", mUrl));\r
+                monitor.setDescription("Fetching %1$s", mUrl);\r
                 monitor.incProgress(1);\r
 \r
                 String xml = fetchUrl(mUrl, monitor);\r
@@ -136,7 +145,10 @@ public class RepoSource implements IDescription {
         }\r
     }\r
 \r
-    /*\r
+    /**\r
+     * Fetches the document at the given URL and returns it as a string.\r
+     * Returns null if anything wrong happens and write errors to the monitor.\r
+     *\r
      * References:\r
      * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html\r
      * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html\r
@@ -186,6 +198,10 @@ public class RepoSource implements IDescription {
         return null;\r
     }\r
 \r
+    /**\r
+     * Validates this XML against the SDK Repository schema.\r
+     * Returns true if the XML was correctly validated.\r
+     */\r
     private boolean validateXml(String xml, ITaskMonitor monitor) {\r
 \r
         try {\r
@@ -203,7 +219,9 @@ public class RepoSource implements IDescription {
         return false;\r
     }\r
 \r
-    /** Helper method that returns a validator for our XSD */\r
+    /**\r
+     * Helper method that returns a validator for our XSD\r
+     */\r
     private Validator getValidator() throws SAXException {\r
         InputStream xsdStream = SdkRepository.getXsdStream();\r
         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
@@ -217,6 +235,10 @@ public class RepoSource implements IDescription {
     }\r
 \r
 \r
+    /**\r
+     * Parse all packages defined in the SDK Repository XML and creates\r
+     * a new mPackages array with them.\r
+     */\r
     private boolean parsePackages(String xml, ITaskMonitor monitor) {\r
 \r
         try {\r
@@ -237,22 +259,21 @@ public class RepoSource implements IDescription {
 \r
                         try {\r
                             if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
-                                p = new AddonPackage(child);\r
+                                p = new AddonPackage(this, child);\r
 \r
                             } else if (!mAddonOnly) {\r
                                 if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
-                                    p = new PlatformPackage(child);\r
+                                    p = new PlatformPackage(this, child);\r
                                 } else if (SdkRepository.NODE_DOC.equals(name)) {\r
-                                    p = new DocPackage(child);\r
+                                    p = new DocPackage(this, child);\r
                                 } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
-                                    p = new ToolPackage(child);\r
+                                    p = new ToolPackage(this, child);\r
                                 }\r
                             }\r
 \r
                             if (p != null) {\r
                                 packages.add(p);\r
-                                monitor.setDescription(\r
-                                        String.format("Found %1$s", p.getShortDescription()));\r
+                                monitor.setDescription("Found %1$s", p.getShortDescription());\r
                             }\r
                         } catch (Exception e) {\r
                             // Ignore invalid packages\r
@@ -278,6 +299,10 @@ public class RepoSource implements IDescription {
         return false;\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
     private Node getFirstChild(Node node, String xmlLocalName) {\r
 \r
         for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
@@ -292,6 +317,9 @@ public class RepoSource implements IDescription {
         return null;\r
     }\r
 \r
+    /**\r
+     * Takes an XML document as a string as parameter and returns a DOM for it.\r
+     */\r
     private Document getDocument(String xml)\r
             throws ParserConfigurationException, SAXException, IOException {\r
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
new file mode 100755 (executable)
index 0000000..0a70953
--- /dev/null
@@ -0,0 +1,47 @@
+/*\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 java.util.ArrayList;\r
+\r
+/**\r
+ * A list of sdk-repository sources.\r
+ */\r
+public class RepoSources {\r
+\r
+    private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();\r
+    private ITaskFactory mTaskFactory;\r
+\r
+    public RepoSources() {\r
+    }\r
+\r
+    public void setTaskFactory(ITaskFactory taskFactory) {\r
+        mTaskFactory = taskFactory;\r
+    }\r
+\r
+    public ITaskFactory getTaskFactory() {\r
+        return mTaskFactory;\r
+    }\r
+\r
+    public void add(RepoSource source) {\r
+        mSources.add(source);\r
+    }\r
+\r
+    public ArrayList<RepoSource> getSources() {\r
+        return mSources;\r
+    }\r
+}\r
index 4416ea0..71e35c4 100755 (executable)
 \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
+\r
 import org.w3c.dom.Node;\r
 \r
+import java.io.File;\r
+\r
 /**\r
  * Represents a tool XML node in an SDK repository.\r
  */\r
@@ -28,8 +34,32 @@ public class ToolPackage extends Package {
      * <p/>\r
      * This constructor should throw an exception if the package cannot be created.\r
      */\r
-    ToolPackage(Node packageNode) {\r
-        super(packageNode);\r
+    ToolPackage(RepoSource source, Node packageNode) {\r
+        super(source, packageNode);\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.\r
+     */\r
+    ToolPackage(RepoSource source,\r
+            int revision,\r
+            String description,\r
+            String descUrl,\r
+            Os archiveOs,\r
+            Arch archiveArch,\r
+            String archiveUrl,\r
+            long archiveSize,\r
+            String archiveChecksum) {\r
+        super(source,\r
+                revision,\r
+                description,\r
+                descUrl,\r
+                archiveOs,\r
+                archiveArch,\r
+                archiveUrl,\r
+                archiveSize,\r
+                archiveChecksum);\r
     }\r
 \r
     /** Returns a short description for an {@link IDescription}. */\r
@@ -45,4 +75,18 @@ public class ToolPackage extends Package {
                 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, SdkConstants.FD_TOOLS);\r
+    }\r
 }\r
index d0ea56c..673e43f 100755 (executable)
@@ -16,6 +16,7 @@
 \r
 package com.android.sdklib.repository;\r
 \r
+\r
 import java.io.InputStream;\r
 \r
 /**\r
@@ -23,6 +24,10 @@ import java.io.InputStream;
  */\r
 public class SdkRepository {\r
 \r
+    /** The URL of the official Google sdk-repository site. */\r
+    public static final String URL_GOOGLE_SDK_REPO_SITE =\r
+        "https://dl.google.com/android/eclipse/repository/index.xml";           //$NON-NLS-1$\r
+\r
     /** The XML namespace of the sdk-repository XML. */\r
     public static final String NS_SDK_REPOSITORY =\r
         "http://schemas.android.com/sdk/android/repository/1";                  //$NON-NLS-1$\r
@@ -21,8 +21,11 @@ import com.android.sdklib.internal.repository.ITaskMonitor;
 \r
 import org.eclipse.jface.viewers.TableViewer;\r
 import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.ControlAdapter;\r
+import org.eclipse.swt.events.ControlEvent;\r
 import org.eclipse.swt.events.SelectionAdapter;\r
 import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.graphics.Rectangle;\r
 import org.eclipse.swt.layout.GridData;\r
 import org.eclipse.swt.layout.GridLayout;\r
 import org.eclipse.swt.widgets.Button;\r
@@ -44,25 +47,22 @@ import org.eclipse.swt.widgets.Text;
  * - refresh callback\r
  */\r
 \r
-public class InstalledPackagesPage extends Composite {\r
+public class LocalPackagesPage extends Composite {\r
     private UpdaterData mUpdaterData;\r
 \r
     private Label mSdkLocLabel;\r
     private Text mSdkLocText;\r
     private Button mSdkLocBrowse;\r
-    private Label mInstalledPkgLabel;\r
-    private TableViewer mTableViewerInstPkg;\r
-    private Table mTableInstPkg;\r
-    private TableColumn mColumnInstSummary;\r
-    private TableColumn mColumnInstApiLevel;\r
-    private TableColumn mColumnInstRevision;\r
+    private TableViewer mTableViewerPackages;\r
+    private Table mTablePackages;\r
+    private TableColumn mColumnPackages;\r
     private Group mDescriptionContainer;\r
-    private Composite mInstButtons;\r
-    private Button mInstUpdate;\r
+    private Composite mContainerButtons;\r
+    private Button mUpdateButton;\r
     private Label mPlaceholder1;\r
-    private Button mInstDelete;\r
+    private Button mDeleteButton;\r
     private Label mPlaceholder2;\r
-    private Button mInstHomePage;\r
+    private Button mHomePageButton;\r
     private Label mDescriptionLabel;\r
 \r
     /**\r
@@ -71,12 +71,13 @@ public class InstalledPackagesPage extends Composite {
      * @param updaterData An instance of {@link UpdaterData}. If null, a local\r
      *        one will be allocated just to help with the SWT Designer.\r
      */\r
-    public InstalledPackagesPage(Composite parent, UpdaterData updaterData) {\r
+    public LocalPackagesPage(Composite parent, UpdaterData updaterData) {\r
         super(parent, SWT.BORDER);\r
 \r
         mUpdaterData = updaterData != null ? updaterData : new UpdaterData();\r
 \r
         createContents(this);\r
+        postCreate();  //$hide$\r
     }\r
 \r
     private void createContents(Composite parent) {\r
@@ -84,26 +85,14 @@ public class InstalledPackagesPage extends Composite {
 \r
         createSdkLocation(parent);\r
 \r
-        mInstalledPkgLabel = new Label(parent, SWT.NONE);\r
-        mInstalledPkgLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));\r
-        mInstalledPkgLabel.setText("Installed Packages:");\r
+        mTableViewerPackages = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION);\r
+        mTablePackages = mTableViewerPackages.getTable();\r
+        mTablePackages.setHeaderVisible(true);\r
+        mTablePackages.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));\r
 \r
-        mTableViewerInstPkg = new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION);\r
-        mTableInstPkg = mTableViewerInstPkg.getTable();\r
-        mTableInstPkg.setHeaderVisible(true);\r
-        mTableInstPkg.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));\r
-\r
-        mColumnInstSummary = new TableColumn(mTableInstPkg, SWT.NONE);\r
-        mColumnInstSummary.setWidth(377);\r
-        mColumnInstSummary.setText("Summary");\r
-\r
-        mColumnInstApiLevel = new TableColumn(mTableInstPkg, SWT.NONE);\r
-        mColumnInstApiLevel.setWidth(100);\r
-        mColumnInstApiLevel.setText("API Level");\r
-\r
-        mColumnInstRevision = new TableColumn(mTableInstPkg, SWT.NONE);\r
-        mColumnInstRevision.setWidth(100);\r
-        mColumnInstRevision.setText("Revision");\r
+        mColumnPackages = new TableColumn(mTablePackages, SWT.NONE);\r
+        mColumnPackages.setWidth(377);\r
+        mColumnPackages.setText("Installed Packages");\r
 \r
         mDescriptionContainer = new Group(parent, SWT.NONE);\r
         mDescriptionContainer.setLayout(new GridLayout(1, false));\r
@@ -114,32 +103,32 @@ public class InstalledPackagesPage extends Composite {
         mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true, 1, 1));\r
         mDescriptionLabel.setText("Line1\nLine2\nLine3");\r
 \r
-        mInstButtons = new Composite(parent, SWT.NONE);\r
-        mInstButtons.setLayout(new GridLayout(5, false));\r
-        mInstButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));\r
+        mContainerButtons = new Composite(parent, SWT.NONE);\r
+        mContainerButtons.setLayout(new GridLayout(5, false));\r
+        mContainerButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));\r
 \r
-        mInstUpdate = new Button(mInstButtons, SWT.NONE);\r
-        mInstUpdate.addSelectionListener(new SelectionAdapter() {\r
+        mUpdateButton = new Button(mContainerButtons, SWT.NONE);\r
+        mUpdateButton.addSelectionListener(new SelectionAdapter() {\r
             @Override\r
             public void widgetSelected(SelectionEvent e) {\r
                 onUpdateInstalledPackage();  //$hide$ (hide from SWT designer)\r
             }\r
         });\r
-        mInstUpdate.setText("Update...");\r
+        mUpdateButton.setText("Update...");\r
 \r
-        mPlaceholder1 = new Label(mInstButtons, SWT.NONE);\r
+        mPlaceholder1 = new Label(mContainerButtons, SWT.NONE);\r
         mPlaceholder1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));\r
 \r
-        mInstDelete = new Button(mInstButtons, SWT.NONE);\r
-        mInstDelete.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));\r
-        mInstDelete.setText("Delete...");\r
+        mDeleteButton = new Button(mContainerButtons, SWT.NONE);\r
+        mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));\r
+        mDeleteButton.setText("Delete...");\r
 \r
-        mPlaceholder2 = new Label(mInstButtons, SWT.NONE);\r
+        mPlaceholder2 = new Label(mContainerButtons, SWT.NONE);\r
         mPlaceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));\r
 \r
-        mInstHomePage = new Button(mInstButtons, SWT.NONE);\r
-        mInstHomePage.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
-        mInstHomePage.setText("Home Page...");\r
+        mHomePageButton = new Button(mContainerButtons, SWT.NONE);\r
+        mHomePageButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));\r
+        mHomePageButton.setText("Home Page...");\r
     }\r
 \r
     private void createSdkLocation(Composite parent) {\r
@@ -175,7 +164,36 @@ public class InstalledPackagesPage extends Composite {
     // Hide everything down-below from SWT designer\r
     //$hide>>$\r
 \r
+    private void postCreate() {\r
+        adjustColumnsWidth();\r
+    }\r
+\r
+\r
+    /**\r
+     * Adds a listener to adjust the columns width when the parent is resized.\r
+     * <p/>\r
+     * If we need something more fancy, we might want to use this:\r
+     * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co\r
+     */\r
+    private void adjustColumnsWidth() {\r
+        // Add a listener to resize the column to the full width of the table\r
+        mTablePackages.addControlListener(new ControlAdapter() {\r
+            @Override\r
+            public void controlResized(ControlEvent e) {\r
+                Rectangle r = mTablePackages.getClientArea();\r
+                mColumnPackages.setWidth(r.width);\r
+            }\r
+        });\r
+    }\r
+\r
+    public void setInput(LocalSdkAdapter localSdkAdapter) {\r
+        mTableViewerPackages.setLabelProvider(  localSdkAdapter.getLabelProvider());\r
+        mTableViewerPackages.setContentProvider(localSdkAdapter.getContentProvider());\r
+        mTableViewerPackages.setInput(localSdkAdapter);\r
+    }\r
+\r
     protected void onUpdateInstalledPackage() {\r
+        // TODO just a test, needs to be removed later.\r
         ProgressTask.start(getShell(), "Test", new ITask() {\r
             public void run(ITaskMonitor monitor) {\r
                 monitor.setDescription("Test");\r
@@ -189,6 +207,7 @@ public class InstalledPackagesPage extends Composite {
                     try {\r
                         Thread.sleep(5);\r
                     } catch (InterruptedException e) {\r
+                        // ignore\r
                     }\r
                 }\r
             }\r
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java
new file mode 100755 (executable)
index 0000000..330be18
--- /dev/null
@@ -0,0 +1,116 @@
+/*\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.sdkuilib.internal.repository;\r
+\r
+import com.android.sdklib.internal.repository.IDescription;\r
+import com.android.sdklib.internal.repository.LocalSdkParser;\r
+import com.android.sdklib.internal.repository.Package;\r
+import com.android.sdklib.internal.repository.RepoSource;\r
+\r
+import org.eclipse.jface.viewers.IContentProvider;\r
+import org.eclipse.jface.viewers.ILabelProvider;\r
+import org.eclipse.jface.viewers.IStructuredContentProvider;\r
+import org.eclipse.jface.viewers.LabelProvider;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.swt.graphics.Image;\r
+\r
+/**\r
+ * Table adapters to use the local SDK list.\r
+ */\r
+class LocalSdkAdapter  {\r
+\r
+    private final LocalSdkParser mLocalSdkParser;\r
+    private String mOsSdkRoot;\r
+\r
+    public LocalSdkAdapter(LocalSdkParser localSdkParser) {\r
+        mLocalSdkParser = localSdkParser;\r
+    }\r
+\r
+    public void setSdkRoot(String osSdkRoot) {\r
+        mOsSdkRoot = osSdkRoot;\r
+        mLocalSdkParser.clearPackages();\r
+    }\r
+\r
+    public ILabelProvider getLabelProvider() {\r
+        return new ViewerLabelProvider();\r
+    }\r
+\r
+\r
+    public IContentProvider getContentProvider() {\r
+        return new TableContentProvider();\r
+    }\r
+\r
+    // ------------\r
+\r
+    public static class ViewerLabelProvider extends LabelProvider {\r
+        /** Returns null by default */\r
+        @Override\r
+        public Image getImage(Object element) {\r
+            return super.getImage(element);\r
+        }\r
+\r
+        /** Returns the toString of the element. */\r
+        @Override\r
+        public String getText(Object element) {\r
+            if (element instanceof IDescription) {\r
+                return ((IDescription) element).getShortDescription();\r
+            }\r
+            return super.getText(element);\r
+        }\r
+    }\r
+\r
+    // ------------\r
+\r
+    private static class TableContentProvider implements IStructuredContentProvider {\r
+\r
+        // Called when the viewer is disposed\r
+        public void dispose() {\r
+            // pass\r
+        }\r
+\r
+        // Called when the input is set or changed on the provider\r
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+            // pass\r
+        }\r
+\r
+        /**\r
+         * Called to collect the root elements for the given input.\r
+         * The input here is a {@link LocalSdkAdapter} object, this returns an array\r
+         * of {@link RepoSource}.\r
+         */\r
+        public Object[] getElements(Object inputElement) {\r
+            if (inputElement instanceof LocalSdkAdapter) {\r
+                LocalSdkAdapter adapter = (LocalSdkAdapter) inputElement;\r
+                LocalSdkParser parser = adapter.mLocalSdkParser;\r
+\r
+                Package[] packages = parser.getPackages();\r
+\r
+                if (packages == null) {\r
+                    // load on demand the first time\r
+                    packages = parser.parseSdk(adapter.mOsSdkRoot);\r
+                }\r
+\r
+                if (packages != null) {\r
+                    return packages;\r
+                }\r
+            }\r
+\r
+            return new Object[0];\r
+        }\r
+    }\r
+\r
+}\r
index 3897096..7667355 100755 (executable)
@@ -152,11 +152,11 @@ class ProgressTask extends Dialog
      * Sets the description in the current task dialog.\r
      * This method can be invoke from a non-UI thread.\r
      */\r
-    public void setDescription(final String description) {\r
+    public void setDescription(final String descriptionFormat, final Object...args) {\r
         mDialogShell.getDisplay().asyncExec(new Runnable() {\r
             public void run() {\r
                 if (!mLabel.isDisposed()) {\r
-                    mLabel.setText(description);\r
+                    mLabel.setText(String.format(descriptionFormat, args));\r
                 }\r
             }\r
         });\r
@@ -166,14 +166,14 @@ class ProgressTask extends Dialog
      * Sets the description in the current task dialog.\r
      * This method can be invoke from a non-UI thread.\r
      */\r
-    public void setResult(final String result) {\r
+    public void setResult(final String resultFormat, final Object...args) {\r
         mAutomaticallyCloseOnTaskCompletion = false;\r
         if (!mDialogShell.isDisposed()) {\r
             mDialogShell.getDisplay().asyncExec(new Runnable() {\r
                 public void run() {\r
                     if (!mResultText.isDisposed()) {\r
                         mResultText.setVisible(true);\r
-                        mResultText.setText(result);\r
+                        mResultText.setText(String.format(resultFormat, args));\r
                     }\r
                 }\r
             });\r
 package com.android.sdkuilib.internal.repository;\r
 \r
 \r
+import com.android.sdklib.internal.repository.Archive;\r
 import com.android.sdklib.internal.repository.IDescription;\r
 \r
+import org.eclipse.jface.viewers.CheckStateChangedEvent;\r
 import org.eclipse.jface.viewers.CheckboxTreeViewer;\r
+import org.eclipse.jface.viewers.DoubleClickEvent;\r
+import org.eclipse.jface.viewers.ICheckStateListener;\r
+import org.eclipse.jface.viewers.IDoubleClickListener;\r
 import org.eclipse.jface.viewers.ISelection;\r
 import org.eclipse.jface.viewers.ITreeSelection;\r
 import org.eclipse.swt.SWT;\r
@@ -36,10 +41,8 @@ import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Label;\r
 import org.eclipse.swt.widgets.Tree;\r
 import org.eclipse.swt.widgets.TreeColumn;\r
-import org.eclipse.jface.viewers.ICheckStateListener;\r
-import org.eclipse.jface.viewers.CheckStateChangedEvent;\r
-import org.eclipse.jface.viewers.IDoubleClickListener;\r
-import org.eclipse.jface.viewers.DoubleClickEvent;\r
+\r
+import java.util.ArrayList;\r
 \r
 /*\r
  * TODO list\r
@@ -54,8 +57,9 @@ import org.eclipse.jface.viewers.DoubleClickEvent;
  * - install selected callback\r
  */\r
 \r
-public class AvailablePackagesPage extends Composite {\r
+public class RemotePackagesPage extends Composite {\r
 \r
+    private final UpdaterWindowImpl mUpdaterWindow;\r
     private final UpdaterData mUpdaterData;\r
 \r
     private CheckboxTreeViewer mTreeViewerSources;\r
@@ -70,14 +74,18 @@ public class AvailablePackagesPage extends Composite {
     private Label mDescriptionLabel;\r
 \r
 \r
+\r
     /**\r
      * Create the composite.\r
      * @param parent The parent of the composite.\r
      * @param updaterData An instance of {@link UpdaterData}. If null, a local\r
      *        one will be allocated just to help with the SWT Designer.\r
      */\r
-    public AvailablePackagesPage(Composite parent, UpdaterData updaterData) {\r
+    public RemotePackagesPage(UpdaterWindowImpl updaterWindow,\r
+            Composite parent,\r
+            UpdaterData updaterData) {\r
         super(parent, SWT.BORDER);\r
+        mUpdaterWindow = updaterWindow;\r
 \r
         mUpdaterData = updaterData != null ? updaterData : new UpdaterData();\r
 \r
@@ -91,16 +99,14 @@ public class AvailablePackagesPage extends Composite {
         mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);\r
         mTreeViewerSources.addDoubleClickListener(new IDoubleClickListener() {\r
             public void doubleClick(DoubleClickEvent event) {\r
-                doTreeDoubleClick(event); //$hide$\r
+                onTreeDoubleClick(event); //$hide$\r
             }\r
         });\r
         mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {\r
             public void checkStateChanged(CheckStateChangedEvent event) {\r
-                doTreeCeckStateChanged(event); //$hide$\r
+                onTreeCheckStateChanged(event); //$hide$\r
             }\r
         });\r
-        mTreeViewerSources.setContentProvider(mUpdaterData.getSources().getContentProvider());\r
-        mTreeViewerSources.setLabelProvider(mUpdaterData.getSources().getLabelProvider());\r
         mTreeSources = mTreeViewerSources.getTree();\r
         mTreeSources.addSelectionListener(new SelectionAdapter() {\r
             @Override\r
@@ -137,6 +143,12 @@ public class AvailablePackagesPage extends Composite {
         mRefreshButton.setText("Refresh");\r
 \r
         mInstallSelectedButton = new Button(parent, SWT.NONE);\r
+        mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {\r
+            @Override\r
+            public void widgetSelected(SelectionEvent e) {\r
+                onInstallSelectedArchives();  //$hide$\r
+            }\r
+        });\r
         mInstallSelectedButton.setText("Install Selected");\r
     }\r
 \r
@@ -171,7 +183,9 @@ public class AvailablePackagesPage extends Composite {
         });\r
     }\r
 \r
-    public void setInput(RepoSources sources) {\r
+    public void setInput(RepoSourcesAdapter sources) {\r
+        mTreeViewerSources.setContentProvider(sources.getContentProvider());\r
+        mTreeViewerSources.setLabelProvider(  sources.getLabelProvider());\r
         mTreeViewerSources.setInput(sources);\r
         onTreeSelected();\r
     }\r
@@ -189,13 +203,27 @@ public class AvailablePackagesPage extends Composite {
         mDescriptionLabel.setText("");  //$NON-NLS1-$\r
     }\r
 \r
-    private void doTreeCeckStateChanged(CheckStateChangedEvent event) {\r
+    private void onTreeCheckStateChanged(CheckStateChangedEvent event) {\r
         boolean b = event.getChecked();\r
-        Object elem = event.getElement();\r
+        Object elem = event.getElement(); // Will be Archive or Package or RepoSource\r
         Object src = event.getSource();\r
+        // TODO\r
+    }\r
+\r
+    private void onTreeDoubleClick(DoubleClickEvent event) {\r
+        // TODO\r
     }\r
 \r
-    private void doTreeDoubleClick(DoubleClickEvent event) {\r
+    private void onInstallSelectedArchives() {\r
+\r
+        ArrayList<Archive> archives = new ArrayList<Archive>();\r
+        for (Object element : mTreeViewerSources.getCheckedElements()) {\r
+            if (element instanceof Archive) {\r
+                archives.add((Archive) element);\r
+            }\r
+        }\r
+\r
+        mUpdaterWindow.installArchives(archives);\r
     }\r
 \r
     // End of hiding from SWT Designer\r
@@ -18,9 +18,9 @@ package com.android.sdkuilib.internal.repository;
 \r
 import com.android.sdklib.internal.repository.Archive;\r
 import com.android.sdklib.internal.repository.IDescription;\r
-import com.android.sdklib.internal.repository.ITaskFactory;\r
 import com.android.sdklib.internal.repository.Package;\r
 import com.android.sdklib.internal.repository.RepoSource;\r
+import com.android.sdklib.internal.repository.RepoSources;\r
 \r
 import org.eclipse.jface.viewers.IContentProvider;\r
 import org.eclipse.jface.viewers.ILabelProvider;\r
@@ -29,27 +29,17 @@ import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.Viewer;\r
 import org.eclipse.swt.graphics.Image;\r
 \r
-import java.util.ArrayList;\r
-\r
 /**\r
  * A list of sdk-repository sources.\r
  *\r
  * This implementation is UI dependent.\r
  */\r
-class RepoSources {\r
-\r
-    private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();\r
-    private ITaskFactory mTaskFactory;\r
-\r
-    public RepoSources() {\r
-    }\r
+class RepoSourcesAdapter {\r
 \r
-    public void setTaskFactory(ITaskFactory taskFactory) {\r
-        mTaskFactory = taskFactory;\r
-    }\r
+    private final RepoSources mRepoSources;\r
 \r
-    public void add(RepoSource source) {\r
-        mSources.add(source);\r
+    public RepoSourcesAdapter(RepoSources repoSources) {\r
+        mRepoSources = repoSources;\r
     }\r
 \r
     public ILabelProvider getLabelProvider() {\r
@@ -63,7 +53,7 @@ class RepoSources {
 \r
     // ------------\r
 \r
-    public class ViewerLabelProvider extends LabelProvider {\r
+    public static class ViewerLabelProvider extends LabelProvider {\r
         /** Returns null by default */\r
         @Override\r
         public Image getImage(Object element) {\r
@@ -82,9 +72,9 @@ class RepoSources {
 \r
     // ------------\r
 \r
-    private class TreeContentProvider implements ITreeContentProvider {\r
+    private static class TreeContentProvider implements ITreeContentProvider {\r
 \r
-        private Object mInput;\r
+        private RepoSourcesAdapter mInput;\r
 \r
         // Called when the viewer is disposed\r
         public void dispose() {\r
@@ -93,13 +83,14 @@ class RepoSources {
 \r
         // Called when the input is set or changed on the provider\r
         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
-            mInput = newInput;\r
+            assert newInput == null || newInput instanceof RepoSourcesAdapter;\r
+            mInput = (RepoSourcesAdapter) newInput;\r
             // pass\r
         }\r
 \r
         /**\r
          * Called to collect the root elements for the given input.\r
-         * The input here is a {@link RepoSources} object, this returns an array\r
+         * The input here is a {@link RepoSourcesAdapter} object, this returns an array\r
          * of {@link RepoSource}.\r
          */\r
         public Object[] getElements(Object inputElement) {\r
@@ -110,20 +101,20 @@ class RepoSources {
          * Get the children of the given parent. This is requested on-demand as\r
          * nodes are expanded.\r
          *\r
-         * For a {@link RepoSources} object, returns an array of {@link RepoSource}s.\r
+         * For a {@link RepoSourcesAdapter} object, returns an array of {@link RepoSource}s.\r
          * For a {@link RepoSource}, returns an array of {@link Package}s.\r
          * For a {@link Package}, returns an array of {@link Archive}s.\r
          */\r
         public Object[] getChildren(Object parentElement) {\r
-            if (parentElement instanceof RepoSources) {\r
-                return ((RepoSources) parentElement).mSources.toArray();\r
+            if (parentElement instanceof RepoSourcesAdapter) {\r
+                return ((RepoSourcesAdapter) parentElement).mRepoSources.getSources().toArray();\r
 \r
             } else if (parentElement instanceof RepoSource) {\r
                 RepoSource source = (RepoSource) parentElement;\r
                 Package[] packages = source.getPackages();\r
 \r
                 if (packages == null) {\r
-                    source.load(mTaskFactory);\r
+                    source.load(mInput.mRepoSources.getTaskFactory());\r
                     packages = source.getPackages();\r
                 }\r
                 if (packages != null) {\r
@@ -134,17 +125,20 @@ class RepoSources {
                 return ((Package) parentElement).getArchives();\r
             }\r
 \r
-\r
             return new Object[0];\r
         }\r
 \r
         /**\r
          * Returns the parent of a given element.\r
-         * The input {@link RepoSources} is the parent of all {@link RepoSource} elements.\r
+         * The input {@link RepoSourcesAdapter} is the parent of all {@link RepoSource} elements.\r
          */\r
         public Object getParent(Object element) {\r
+\r
             if (element instanceof RepoSource) {\r
                 return mInput;\r
+\r
+            } else if (element instanceof Package) {\r
+                return ((Package) element).getParentSource();\r
             }\r
             return null;\r
         }\r
@@ -152,7 +146,8 @@ class RepoSources {
         /**\r
          * Returns true if a given element has children, which is used to display a\r
          * "+/expand" box next to the tree node.\r
-         * All {@link RepoSource} are expandable, whether they actually have any childre or not.\r
+         * All {@link RepoSource} and {@link Package} are expandable, whether they actually\r
+         * have any children or not.\r
          */\r
         public boolean hasChildren(Object element) {\r
             return element instanceof RepoSource || element instanceof Package;\r
index 920769b..384ff57 100755 (executable)
 \r
 package com.android.sdkuilib.internal.repository;\r
 \r
+import com.android.sdklib.internal.repository.LocalSdkParser;\r
+import com.android.sdklib.internal.repository.RepoSources;\r
+\r
 /**\r
  * Data shared between {@link UpdaterWindowImpl} and its pages.\r
  */\r
 class UpdaterData {\r
     private String mOsSdkRoot;\r
     private boolean mUserCanChangeSdkRoot;\r
-    private RepoSources mSources = new RepoSources();\r
+\r
+    private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();\r
+    private final RepoSources mSources = new RepoSources();\r
+\r
+    private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(mLocalSdkParser);\r
+    private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(mSources);\r
 \r
     public void setOsSdkRoot(String osSdkRoot) {\r
         mOsSdkRoot = osSdkRoot;\r
@@ -40,12 +48,20 @@ class UpdaterData {
         return mUserCanChangeSdkRoot;\r
     }\r
 \r
-    public void setSources(RepoSources sources) {\r
-        mSources = sources;\r
-    }\r
-\r
     public RepoSources getSources() {\r
         return mSources;\r
     }\r
 \r
+    public RepoSourcesAdapter getSourcesAdapter() {\r
+        return mSourcesAdapter;\r
+    }\r
+\r
+    public LocalSdkParser getLocalSdkParser() {\r
+        return mLocalSdkParser;\r
+    }\r
+\r
+    public LocalSdkAdapter getLocalSdkAdapter() {\r
+        return mLocalSdkAdapter;\r
+    }\r
+\r
 }\r
index 50ebdb6..5613bfd 100755 (executable)
 package com.android.sdkuilib.internal.repository;\r
 \r
 \r
+import com.android.sdklib.internal.repository.Archive;\r
+import com.android.sdklib.internal.repository.ITask;\r
+import com.android.sdklib.internal.repository.ITaskMonitor;\r
+import com.android.sdklib.internal.repository.Package;\r
 import com.android.sdklib.internal.repository.RepoSource;\r
+import com.android.sdklib.repository.SdkRepository;\r
 \r
 import org.eclipse.swt.SWT;\r
 import org.eclipse.swt.SWTException;\r
@@ -36,14 +41,22 @@ import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.List;\r
 import org.eclipse.swt.widgets.Shell;\r
 \r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
 import java.io.InputStream;\r
+import java.net.URL;\r
+import java.security.MessageDigest;\r
 import java.util.ArrayList;\r
+import java.util.Collection;\r
 \r
 /**\r
  * This is the private implementation of the UpdateWindow.\r
  */\r
 public class UpdaterWindowImpl {\r
 \r
+    private static final int NUM_FETCH_URL_MONITOR_INC = 10;\r
+\r
     private final UpdaterData mUpdaterData = new UpdaterData();\r
     private ArrayList<Composite> mPages = new ArrayList<Composite>();\r
     private boolean mInternalPageChange;\r
@@ -54,10 +67,11 @@ public class UpdaterWindowImpl {
     private SashForm mSashForm;\r
     private List mPageList;\r
     private Composite mPagesRootComposite;\r
-    private InstalledPackagesPage mInstalledPackagePage;\r
-    private AvailablePackagesPage mAvailablePackagesPage;\r
+    private LocalPackagesPage mLocalPackagePage;\r
+    private RemotePackagesPage mRemotePackagesPage;\r
     private StackLayout mStackLayout;\r
     private Image mIconImage;\r
+    private ProgressTaskFactory mTaskFactory;\r
 \r
     public UpdaterWindowImpl(String osSdkRoot, boolean userCanChangeSdkRoot) {\r
         mUpdaterData.setOsSdkRoot(osSdkRoot);\r
@@ -114,8 +128,8 @@ public class UpdaterWindowImpl {
         mStackLayout = new StackLayout();\r
         mPagesRootComposite.setLayout(mStackLayout);\r
 \r
-        mInstalledPackagePage = new InstalledPackagesPage(mPagesRootComposite, mUpdaterData);\r
-        mAvailablePackagesPage = new AvailablePackagesPage(mPagesRootComposite, mUpdaterData);\r
+        mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);\r
+        mRemotePackagesPage = new RemotePackagesPage(this, mPagesRootComposite, mUpdaterData);\r
         mSashForm.setWeights(new int[] {150, 576});\r
     }\r
 \r
@@ -158,8 +172,10 @@ public class UpdaterWindowImpl {
      * Once the UI has been created, initialize the content\r
      */\r
     private void firstInit() {\r
-        addPage(mInstalledPackagePage, "Installed Packages");\r
-        addPage(mAvailablePackagesPage, "Available Packages");\r
+        mTaskFactory = new ProgressTaskFactory(getShell());\r
+\r
+        addPage(mLocalPackagePage, "Installed Packages");\r
+        addPage(mRemotePackagesPage, "Available Packages");\r
         displayPage(0);\r
         mPageList.setSelection(0);\r
 \r
@@ -199,23 +215,214 @@ public class UpdaterWindowImpl {
     }\r
 \r
     private void setupSources() {\r
-        mUpdaterData.getSources().setTaskFactory(new ProgressTaskFactory(getShell()));\r
+        mUpdaterData.getSources().setTaskFactory(mTaskFactory);\r
 \r
-        mUpdaterData.getSources().add(new RepoSource(\r
-                "https://dl.google.com/android/eclipse/repository/index.xml",          //$NON-NLS-1$\r
-                false /* addonOnly */));\r
+        mUpdaterData.getSources().add(\r
+                new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /* addonOnly */));\r
 \r
         String url = System.getenv("TEMP_SDK_URL"); // TODO STOPSHIP temporary remove before shipping\r
         if (url != null) {\r
             mUpdaterData.getSources().add(new RepoSource(url, false /* addonOnly */));\r
         }\r
 \r
-        mAvailablePackagesPage.setInput(mUpdaterData.getSources());\r
+        mRemotePackagesPage.setInput(mUpdaterData.getSourcesAdapter());\r
     }\r
 \r
     private void scanLocalSdkFolders() {\r
-        // TODO Auto-generated method stub\r
+        mUpdaterData.getLocalSdkAdapter().setSdkRoot(mUpdaterData.getOsSdkRoot());\r
+\r
+        mLocalPackagePage.setInput(mUpdaterData.getLocalSdkAdapter());\r
+    }\r
+\r
+    public void installArchives(final Collection<Archive> archives) {\r
+        // TODO move most parts to SdkLib, maybe as part of Archive, making archives self-installing.\r
+        mTaskFactory.start("Installing Archives", new ITask() {\r
+            public void run(ITaskMonitor monitor) {\r
+\r
+                monitor.setProgressMax(archives.size() * (NUM_FETCH_URL_MONITOR_INC + 3));\r
+                monitor.setDescription("Preparing to install archives");\r
+\r
+                int num_installed = 0;\r
+                for (Archive archive : archives) {\r
+\r
+                    if (!archive.isCompatible()) {\r
+                        monitor.setResult("Skipping incompatible archive: %1$s",\r
+                                archive.getShortDescription());\r
+                        monitor.incProgress(3);\r
+                        continue;\r
+                    }\r
+\r
+                    File archiveFile = null;\r
+                    try {\r
+                        archiveFile = downloadArchive(archive, monitor);\r
+                        monitor.incProgress(1);\r
+                        if (archiveFile != null) {\r
+                            if (installArchive(archive, archiveFile, monitor)) {\r
+                                num_installed++;\r
+                            }\r
+                        }\r
+                        monitor.incProgress(1);\r
+                    } finally {\r
+                        if (archiveFile != null) {\r
+                            if (!archiveFile.delete()) {\r
+                                archiveFile.deleteOnExit();\r
+                            }\r
+                        }\r
+                    }\r
+                }\r
+\r
+                if (num_installed == 0) {\r
+                    monitor.setResult("Nothing was installed.");\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Downloads an archive and returns the temp file with it.\r
+     * Caller is responsible with deleting the temp file when done.\r
+     */\r
+    private File downloadArchive(Archive archive, ITaskMonitor monitor) {\r
+\r
+        try {\r
+            File tmpFile = File.createTempFile("sdkupload", "bin"); //$NON-NLS-1$ //$NON-NLS-2$\r
+\r
+            monitor.setDescription("Downloading %1$s", archive.getShortDescription());\r
+\r
+            String link = archive.getUrl();\r
+            if (!link.startsWith("http://")                          //$NON-NLS-1$\r
+                    && !link.startsWith("https://")                  //$NON-NLS-1$\r
+                    && !link.startsWith("ftp://")) {                 //$NON-NLS-1$\r
+                // Make the URL absolute by prepending the source\r
+                Package pkg = archive.getParentPackage();\r
+                RepoSource src = pkg.getParentSource();\r
+                if (src == null) {\r
+                    monitor.setResult("Internal error: no source for archive %1$s",\r
+                            archive.getShortDescription());\r
+                    return null;\r
+                }\r
+\r
+                String base = src.getUrl();\r
+                if (!base.endsWith("/") && !link.startsWith("/")) {  //$NON-NLS-1$ //$NON-NLS-2$\r
+                    base += "/";                                     //$NON-NLS-1$\r
+                }\r
+\r
+                link = base + link;\r
+            }\r
+\r
+            fetchUrl(tmpFile, archive, link, monitor);\r
+\r
+        } catch (IOException e) {\r
+            monitor.setResult(e.getMessage());\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Actually performs the download.\r
+     * Also computes the SHA1 of the file on the fly.\r
+     * <p/>\r
+     * Success is defined as downloading as many bytes as was expected and having the same\r
+     * SHA1 as expected. Returns true on success or false if any of those checks fail.\r
+     * <p/>\r
+     * Increments the monitor by {@link #NUM_FETCH_URL_MONITOR_INC} (which is 10).\r
+     */\r
+    private boolean fetchUrl(File tmpFile, Archive archive, String urlString, ITaskMonitor monitor) {\r
+        URL url;\r
+\r
+        FileOutputStream os = null;\r
+        InputStream is = null;\r
+        try {\r
+            url = new URL(urlString);\r
+            is = url.openStream();\r
+            os = new FileOutputStream(tmpFile);\r
+\r
+            MessageDigest digester = archive.getChecksumType().getMessageDigest();\r
+\r
+            byte[] buf = new byte[65536];\r
+            int n;\r
+\r
+            long total = 0;\r
+            long size = archive.getSize();\r
+            long inc = size / NUM_FETCH_URL_MONITOR_INC;\r
+            long next_inc = inc;\r
+\r
+            while ((n = is.read(buf)) >= 0) {\r
+                if (n > 0) {\r
+                    os.write(buf, 0, n);\r
+                    digester.update(buf, 0, n);\r
+                }\r
+\r
+                total += n;\r
+                if (total >= next_inc) {\r
+                    monitor.incProgress(1);\r
+                    next_inc += inc;\r
+                }\r
+\r
+                if (monitor.cancelRequested()) {\r
+                    monitor.setResult("Download aborted by user at %1$d bytes.", total);\r
+                    return false;\r
+                }\r
+\r
+            }\r
+\r
+            if (total != size) {\r
+                monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",\r
+                        size, total);\r
+                return false;\r
+            }\r
+\r
+            // Create an hex string from the digest\r
+            byte[] digest = digester.digest();\r
+            n = digest.length;\r
+            String hex = "0123456789abcdef";                     //$NON-NLS-1$\r
+            char[] hexDigest = new char[n * 2];\r
+            for (int i = 0; i < n; i++) {\r
+                byte b = digest[i];\r
+                hexDigest[i*2 + 0] = hex.charAt(b >>> 4);\r
+                hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);\r
+            }\r
+\r
+            String expected = archive.getChecksum();\r
+            String actual   = new String(hexDigest);\r
+            if (!actual.equalsIgnoreCase(expected)) {\r
+                monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",\r
+                        expected, actual);\r
+                return false;\r
+            }\r
+\r
+            return true;\r
+\r
+        } catch (Exception e) {\r
+            monitor.setResult(e.getMessage());\r
+\r
+        } finally {\r
+            if (os != null) {\r
+                try {\r
+                    os.close();\r
+                } catch (IOException e) {\r
+                    // pass\r
+                }\r
+            }\r
+\r
+            if (is != null) {\r
+                try {\r
+                    is.close();\r
+                } catch (IOException e) {\r
+                    // pass\r
+                }\r
+            }\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    private boolean installArchive(Archive archive, File archiveFile, ITaskMonitor monitor) {\r
+        monitor.setDescription("Installing %1$s", archive.getShortDescription());\r
+\r
+        File destFolder = archive.getParentPackage().getInstallFolder(mUpdaterData.getOsSdkRoot());\r
 \r
+        return false;\r
     }\r
 \r
     // End of hiding from SWT Designer\r