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