aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager/libs/sdklib/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdkmanager/libs/sdklib/src')
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java2
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java37
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java8
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java85
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java2
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/NullSdkLog.java46
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java10
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java497
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java476
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/StdSdkLog.java87
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java1
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java17
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/build/JarListSanitizer.java21
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Abi.java46
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/BluetoothProfile.java77
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ButtonType.java42
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Camera.java107
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/CameraLocation.java42
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java295
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java485
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceParser.java373
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceWriter.java293
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Hardware.java331
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Meta.java170
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Multitouch.java44
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Network.java43
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/PowerType.java42
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Screen.java189
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ScreenType.java43
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Sensor.java47
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Software.java148
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java145
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Storage.java123
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/devices/devices.xml1412
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java37
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java151
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/HardwareProperties.java182
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/KeystoreHelper.java5
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/MakeIdentity.java105
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolLoader.java90
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolWriter.java104
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/IPropertySource.java1
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java34
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java121
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java12
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java304
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java1094
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/CanceledByUserException.java30
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java213
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IDescription.java80
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITask.java52
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java104
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java292
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java1318
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java263
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java1211
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java291
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/Archive.java1008
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java2192
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java224
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java1396
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java402
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java609
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java1501
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java243
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java170
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java (renamed from sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java)73
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java43
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java84
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java5
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java6
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java8
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java264
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java56
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java132
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java271
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java1620
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java (renamed from sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java)315
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java698
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java43
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java1069
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java678
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java756
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java710
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkAddonSource.java209
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java1038
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java1947
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java178
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java498
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java842
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java109
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java728
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/io/IFileOp.java25
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java104
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java192
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/RepoConstants.java344
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java180
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonsListConstants.java206
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java282
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkStatsConstants.java182
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkSysImgConstants.java83
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd3
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd442
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-1.xsd2
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-2.xsd106
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd2
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd612
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-sys-img-1.xsd229
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java46
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/util/FormatUtils.java52
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/util/SparseArray.java1
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java359
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java670
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidXPathFactory.java113
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java747
115 files changed, 21537 insertions, 15450 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
index b30e0cc..12d4a49 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
@@ -16,6 +16,8 @@
package com.android.sdklib;
+import com.android.SdkConstants;
+
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
index 996aee4..44ffa63 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
@@ -16,6 +16,8 @@
package com.android.sdklib;
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
import com.android.sdklib.repository.PkgProps;
import java.util.Properties;
@@ -61,7 +63,7 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
*/
public AndroidVersion(int apiLevel, String codename) {
mApiLevel = apiLevel;
- mCodename = codename;
+ mCodename = sanitizeCodename(codename);
}
/**
@@ -73,11 +75,12 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
public AndroidVersion(Properties properties, int defaultApiLevel, String defaultCodeName) {
if (properties == null) {
mApiLevel = defaultApiLevel;
- mCodename = defaultCodeName;
+ mCodename = sanitizeCodename(defaultCodeName);
} else {
mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL,
- Integer.toString(defaultApiLevel)));
- mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName);
+ Integer.toString(defaultApiLevel)));
+ mCodename = sanitizeCodename(
+ properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName));
}
}
@@ -95,7 +98,8 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
if (apiLevel != null) {
try {
mApiLevel = Integer.parseInt(apiLevel);
- mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, null/*defaultValue*/);
+ mCodename = sanitizeCodename(properties.getProperty(PkgProps.VERSION_CODENAME,
+ null/*defaultValue*/));
return;
} catch (NumberFormatException e) {
error = e;
@@ -264,7 +268,7 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
return compareTo(o.mApiLevel, o.mCodename);
}
- private int compareTo(int apiLevel, String codename) {
+ public int compareTo(int apiLevel, String codename) {
if (mCodename == null) {
if (codename == null) {
return mApiLevel - apiLevel;
@@ -298,4 +302,25 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
public boolean isGreaterOrEqualThan(int api) {
return compareTo(api, null /*codename*/) >= 0;
}
+
+ /**
+ * Sanitizes the codename string according to the following rules:
+ * - A codename should be {@code null} for a release version or it should be a non-empty
+ * string for an actual preview.
+ * - In input, spacing is trimmed since it is irrelevant.
+ * - An empty string or the special codename "REL" means a release version
+ * and is converted to {@code null}.
+ *
+ * @param codename A possible-null codename.
+ * @return Null for a release version or a non-empty codename.
+ */
+ private @Nullable String sanitizeCodename(@Nullable String codename) {
+ if (codename != null) {
+ codename = codename.trim();
+ if (codename.length() == 0 || SdkConstants.CODENAME_RELEASE.equals(codename)) {
+ codename = null;
+ }
+ }
+ return codename;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
index f3236ab..18577cf 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
@@ -25,6 +25,12 @@ import java.util.Map;
*/
public interface IAndroidTarget extends Comparable<IAndroidTarget> {
+ /**
+ * Prefix used to build hash strings for platform targets
+ * @see SdkManager#getTargetFromHashString(String)
+ */
+ public static final String PLATFORM_HASH_PREFIX = "android-";
+
/** OS Path to the "android.jar" file. */
public final static int ANDROID_JAR = 1;
/** OS Path to the "framework.aidl" file. */
@@ -87,6 +93,8 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
* This is deprecated as this is now in the platform tools and not in the platform. */
@Deprecated
public final static int ANDROID_RS_CLANG = 26;
+ /** OS Path to the "uiautomator.jar" file. */
+ public final static int UI_AUTOMATOR_JAR = 27;
/**
* Return value for {@link #getUsbVendorId()} meaning no USB vendor IDs are defined by the
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
deleted file mode 100644
index df7d8ac..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import java.util.Formatter;
-
-/**
- * Interface used to display warnings/errors while parsing the SDK content.
- * <p/>
- * There are a few default implementations available:
- * <ul>
- * <li> {@link NullSdkLog} is an implementation that does <em>nothing</em> with the log.
- * Useful for limited cases where you need to call a class that requires a non-null logging
- * yet the calling code does not have any mean of reporting logs itself. It can be
- * acceptable for use a temporary implementation but most of the time that means the caller
- * code needs to be reworked to take a logger object from its own caller.
- * </li>
- * <li> {@link StdSdkLog} is an implementation that dumps the log to {@link System#out} or
- * {@link System#err}. This is useful for unit tests or code that does not have any GUI.
- * Apps based on Eclipse or SWT should not use it and should provide a better way to report
- * to the user.
- * </li>
- * <li> ADT has a <code>AdtPlugin</code> which implements a similar interface called
- * <code>ILogger</code>, useful in case we don't want to pull the whole SdkLib.
- * </ul>
- */
-public interface ISdkLog {
-
- /**
- * Prints a warning message on stdout.
- * <p/>
- * The message will be tagged with "Warning" on the output so the caller does not
- * need to put such a prefix in the format string.
- * <p/>
- * Implementations should only display warnings in verbose mode.
- *
- * @param warningFormat is an optional error format. If non-null, it will be printed
- * using a {@link Formatter} with the provided arguments.
- * @param args provides the arguments for warningFormat.
- */
- void warning(String warningFormat, Object... args);
-
- /**
- * Prints an error message on stderr.
- * <p/>
- * The message will be tagged with "Error" on the output so the caller does not
- * need to put such a prefix in the format string.
- * <p/>
- * Implementation should always display errors, independent of verbose mode.
- *
- * @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's
- * message will be printed out.
- * @param errorFormat is an optional error format. If non-null, it will be printed
- * using a {@link Formatter} with the provided arguments.
- * @param args provides the arguments for errorFormat.
- */
- void error(Throwable t, String errorFormat, Object... args);
-
- /**
- * Prints a message on stdout.
- * This does <em>not</em> automatically end the line with \n.
- * <p/>
- * Implementation can omit printing such messages when not in verbose mode.
- * No prefix is used, the message is printed as-is after formatting.
- *
- * @param msgFormat is an optional error format. If non-null, it will be printed
- * using a {@link Formatter} with the provided arguments.
- * @param args provides the arguments for msgFormat.
- */
- void printf(String msgFormat, Object... args);
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java
index 59ed9c6..7a69030 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java
@@ -16,6 +16,8 @@
package com.android.sdklib;
+import com.android.SdkConstants;
+
import java.io.File;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/NullSdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/NullSdkLog.java
deleted file mode 100644
index 09f49e2..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/NullSdkLog.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-/**
- * Dummy implementation of an {@link ISdkLog}.
- * <p/>
- * Use {@link #getLogger()} to get a default instance of this {@link NullSdkLog}.
- */
-public class NullSdkLog implements ISdkLog {
-
- private static final ISdkLog sThis = new NullSdkLog();
-
- public static ISdkLog getLogger() {
- return sThis;
- }
-
- @Override
- public void error(Throwable t, String errorFormat, Object... args) {
- // ignore
- }
-
- @Override
- public void printf(String msgFormat, Object... args) {
- // ignore
- }
-
- @Override
- public void warning(String warningFormat, Object... args) {
- // ignore
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index 02688c0..7c2b4aa 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -16,6 +16,7 @@
package com.android.sdklib;
+import com.android.SdkConstants;
import com.android.sdklib.SdkManager.LayoutlibVersion;
import com.android.sdklib.util.SparseArray;
@@ -53,8 +54,7 @@ final class PlatformTarget implements IAndroidTarget {
*
* @param sdkOsPath the root folder of the SDK
* @param platformOSPath the root folder of the platform component
- * @param apiLevel the API Level
- * @param codeName the codename. can be null.
+ * @param apiVersion the API Level + codename.
* @param versionName the version name of the platform.
* @param revision the revision of the platform component.
* @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
@@ -65,8 +65,7 @@ final class PlatformTarget implements IAndroidTarget {
PlatformTarget(
String sdkOsPath,
String platformOSPath,
- int apiLevel,
- String codeName,
+ AndroidVersion apiVersion,
String versionName,
int revision,
LayoutlibVersion layoutlibVersion,
@@ -77,7 +76,7 @@ final class PlatformTarget implements IAndroidTarget {
}
mRootFolderOsPath = platformOSPath;
mProperties = Collections.unmodifiableMap(properties);
- mVersion = new AndroidVersion(apiLevel, codeName);
+ mVersion = apiVersion;
mVersionName = versionName;
mRevision = revision;
mLayoutlibVersion = layoutlibVersion;
@@ -92,6 +91,7 @@ final class PlatformTarget implements IAndroidTarget {
// pre-build the path to the platform components
mPaths.put(ANDROID_JAR, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(UI_AUTOMATOR_JAR, mRootFolderOsPath + SdkConstants.FN_UI_AUTOMATOR_LIBRARY);
mPaths.put(SOURCES, mRootFolderOsPath + SdkConstants.FD_ANDROID_SOURCES);
mPaths.put(ANDROID_AIDL, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_AIDL);
mPaths.put(SAMPLES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER);
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
deleted file mode 100644
index 6e6c657..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.AndroidConstants;
-
-import java.io.File;
-
-/**
- * Constant definition class.<br>
- * <br>
- * Most constants have a prefix defining the content.
- * <ul>
- * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
- * <li><code>FN_</code> File name constant.</li>
- * <li><code>FD_</code> Folder name constant.</li>
- * </ul>
- *
- */
-public final class SdkConstants {
- public final static int PLATFORM_UNKNOWN = 0;
- public final static int PLATFORM_LINUX = 1;
- public final static int PLATFORM_WINDOWS = 2;
- public final static int PLATFORM_DARWIN = 3;
-
- /**
- * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
- * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
- */
- public final static int CURRENT_PLATFORM = currentPlatform();
-
- /**
- * Charset for the ini file handled by the SDK.
- */
- public final static String INI_CHARSET = "UTF-8"; //$NON-NLS-1$
-
- /** An SDK Project's AndroidManifest.xml file */
- public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml"; //$NON-NLS-1$
- /** pre-dex jar filename. i.e. "classes.jar" */
- public final static String FN_CLASSES_JAR = "classes.jar"; //$NON-NLS-1$
- /** Dex filename inside the APK. i.e. "classes.dex" */
- public final static String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
-
- /** An SDK Project's build.xml file */
- public final static String FN_BUILD_XML = "build.xml"; //$NON-NLS-1$
-
- /** Name of the framework library, i.e. "android.jar" */
- public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$
- /** Name of the layout attributes, i.e. "attrs.xml" */
- public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$
- /** Name of the layout attributes, i.e. "attrs_manifest.xml" */
- public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$
- /** framework aidl import file */
- public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$
- /** framework renderscript folder */
- public static final String FN_FRAMEWORK_RENDERSCRIPT = "renderscript"; //$NON-NLS-1$
- /** framework include folder */
- public static final String FN_FRAMEWORK_INCLUDE = "include"; //$NON-NLS-1$
- /** framework include (clang) folder */
- public static final String FN_FRAMEWORK_INCLUDE_CLANG = "clang-include"; //$NON-NLS-1$
- /** layoutlib.jar file */
- public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$
- /** widget list file */
- public static final String FN_WIDGETS = "widgets.txt"; //$NON-NLS-1$
- /** Intent activity actions list file */
- public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt"; //$NON-NLS-1$
- /** Intent broadcast actions list file */
- public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt"; //$NON-NLS-1$
- /** Intent service actions list file */
- public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt"; //$NON-NLS-1$
- /** Intent category list file */
- public static final String FN_INTENT_CATEGORIES = "categories.txt"; //$NON-NLS-1$
-
- /** annotations support jar */
- public static final String FN_ANNOTATIONS_JAR = "annotations.jar"; //$NON-NLS-1$
-
- /** platform build property file */
- public final static String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$
- /** plugin properties file */
- public final static String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$
- /** add-on manifest file */
- public final static String FN_MANIFEST_INI = "manifest.ini"; //$NON-NLS-1$
- /** add-on layout device XML file. */
- public final static String FN_DEVICES_XML = "devices.xml"; //$NON-NLS-1$
- /** hardware properties definition file */
- public final static String FN_HARDWARE_INI = "hardware-properties.ini"; //$NON-NLS-1$
-
- /** project property file */
- public final static String FN_PROJECT_PROPERTIES = "project.properties"; //$NON-NLS-1$
-
- /** project local property file */
- public final static String FN_LOCAL_PROPERTIES = "local.properties"; //$NON-NLS-1$
-
- /** project ant property file */
- public final static String FN_ANT_PROPERTIES = "ant.properties"; //$NON-NLS-1$
-
- /** Skin layout file */
- public final static String FN_SKIN_LAYOUT = "layout"; //$NON-NLS-1$
-
- /** dx.jar file */
- public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
-
- /** dx executable (with extension for the current OS) */
- public final static String FN_DX =
- "dx" + ext(".bat", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** aapt executable (with extension for the current OS) */
- public final static String FN_AAPT =
- "aapt" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** aidl executable (with extension for the current OS) */
- public final static String FN_AIDL =
- "aidl" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** renderscript executable (with extension for the current OS) */
- public final static String FN_RENDERSCRIPT =
- "llvm-rs-cc" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** adb executable (with extension for the current OS) */
- public final static String FN_ADB =
- "adb" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** emulator executable for the current OS */
- public final static String FN_EMULATOR =
- "emulator" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** zipalign executable (with extension for the current OS) */
- public final static String FN_ZIPALIGN =
- "zipalign" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** dexdump executable (with extension for the current OS) */
- public final static String FN_DEXDUMP =
- "dexdump" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** proguard executable (with extension for the current OS) */
- public final static String FN_PROGUARD =
- "proguard" + ext(".bat", ".sh"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** find_lock for Windows (with extension for the current OS) */
- public final static String FN_FIND_LOCK =
- "find_lock" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- /** properties file for SDK Updater packages */
- public final static String FN_SOURCE_PROP = "source.properties"; //$NON-NLS-1$
- /** properties file for content hash of installed packages */
- public final static String FN_CONTENT_HASH_PROP = "content_hash.properties"; //$NON-NLS-1$
- /** properties file for the SDK */
- public final static String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$
-
- /**
- * filename for gdbserver.
- */
- public final static String FN_GDBSERVER = "gdbserver"; //$NON-NLS-1$
-
- /** global Android proguard config file */
- public final static String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$
- /** default proguard config file with new file extension (for project specific stuff) */
- public final static String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; //$NON-NLS-1$
-
- /* Folder Names for Android Projects . */
-
- /** Resources folder name, i.e. "res". */
- public final static String FD_RESOURCES = "res"; //$NON-NLS-1$
- /** Assets folder name, i.e. "assets" */
- public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
- /** Default source folder name in an SDK project, i.e. "src".
- * <p/>
- * Note: this is not the same as {@link #FD_PKG_SOURCES}
- * which is an SDK sources folder for packages. */
- public final static String FD_SOURCES = "src"; //$NON-NLS-1$
- /** Default generated source folder name, i.e. "gen" */
- public final static String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$
- /** Default native library folder name inside the project, i.e. "libs"
- * While the folder inside the .apk is "lib", we call that one libs because
- * that's what we use in ant for both .jar and .so and we need to make the 2 development ways
- * compatible. */
- public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
- /** Native lib folder inside the APK: "lib" */
- public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
- /** Default output folder name, i.e. "bin" */
- public final static String FD_OUTPUT = "bin"; //$NON-NLS-1$
- /** Classes output folder name, i.e. "classes" */
- public final static String FD_CLASSES_OUTPUT = "classes"; //$NON-NLS-1$
- /** proguard output folder for mapping, etc.. files */
- public final static String FD_PROGUARD = "proguard"; //$NON-NLS-1$
-
- /* Folder Names for the Android SDK */
-
- /** Name of the SDK platforms folder. */
- public final static String FD_PLATFORMS = "platforms"; //$NON-NLS-1$
- /** Name of the SDK addons folder. */
- public final static String FD_ADDONS = "add-ons"; //$NON-NLS-1$
- /** Name of the SDK system-images folder. */
- public final static String FD_SYSTEM_IMAGES = "system-images"; //$NON-NLS-1$
- /** Name of the SDK sources folder where source packages are installed.
- * <p/>
- * Note this is not the same as {@link #FD_SOURCES} which is the folder name where sources
- * are installed inside a project. */
- public final static String FD_PKG_SOURCES = "sources"; //$NON-NLS-1$
- /** Name of the SDK tools folder. */
- public final static String FD_TOOLS = "tools"; //$NON-NLS-1$
- /** Name of the SDK tools/support folder. */
- public final static String FD_SUPPORT = "support"; //$NON-NLS-1$
- /** Name of the SDK platform tools folder. */
- public final static String FD_PLATFORM_TOOLS = "platform-tools"; //$NON-NLS-1$
- /** Name of the SDK tools/lib folder. */
- public final static String FD_LIB = "lib"; //$NON-NLS-1$
- /** Name of the SDK docs folder. */
- public final static String FD_DOCS = "docs"; //$NON-NLS-1$
- /** Name of the doc folder containing API reference doc (javadoc) */
- public static final String FD_DOCS_REFERENCE = "reference"; //$NON-NLS-1$
- /** Name of the SDK images folder. */
- public final static String FD_IMAGES = "images"; //$NON-NLS-1$
- /** Name of the ABI to support. */
- public final static String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$
- public final static String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$
- public final static String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public final static String ABI_MIPS = "mips"; //$NON-NLS-1$
- /** Name of the CPU arch to support. */
- public final static String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$
- public final static String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$
- public final static String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$
- /** Name of the CPU model to support. */
- public final static String CPU_MODEL_CORTEX_A8 = "cortex-a8"; //$NON-NLS-1$
-
- /** Name of the SDK skins folder. */
- public final static String FD_SKINS = "skins"; //$NON-NLS-1$
- /** Name of the SDK samples folder. */
- public final static String FD_SAMPLES = "samples"; //$NON-NLS-1$
- /** Name of the SDK extras folder. */
- public final static String FD_EXTRAS = "extras"; //$NON-NLS-1$
- /** Name of the SDK templates folder, i.e. "templates" */
- public final static String FD_TEMPLATES = "templates"; //$NON-NLS-1$
- /** Name of the SDK Ant folder, i.e. "ant" */
- public final static String FD_ANT = "ant"; //$NON-NLS-1$
- /** Name of the SDK data folder, i.e. "data" */
- public final static String FD_DATA = "data"; //$NON-NLS-1$
- /** Name of the SDK renderscript folder, i.e. "rs" */
- public final static String FD_RENDERSCRIPT = "rs"; //$NON-NLS-1$
- /** Name of the SDK resources folder, i.e. "res" */
- public final static String FD_RES = "res"; //$NON-NLS-1$
- /** Name of the SDK font folder, i.e. "fonts" */
- public final static String FD_FONTS = "fonts"; //$NON-NLS-1$
- /** Name of the android sources directory */
- public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
- /** Name of the addon libs folder. */
- public final static String FD_ADDON_LIBS = "libs"; //$NON-NLS-1$
-
- /** Name of the cache folder in the $HOME/.android. */
- public final static String FD_CACHE = "cache"; //$NON-NLS-1$
-
- /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
- public final static String NS_RESOURCES =
- "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
-
- /** The name of the uses-library that provides "android.test.runner" */
- public final static String ANDROID_TEST_RUNNER_LIB =
- "android.test.runner"; //$NON-NLS-1$
-
- /* Folder path relative to the SDK root */
- /** Path of the documentation directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator;
-
- /** Path of the tools directory relative to the sdk folder, or to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator;
-
- /** Path of the lib directory relative to the sdk folder, or to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_TOOLS_LIB_FOLDER =
- OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator;
-
- /**
- * Path of the lib directory relative to the sdk folder, or to a platform
- * folder. This is an OS path, ending with a separator.
- */
- public final static String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = OS_SDK_TOOLS_LIB_FOLDER
- + "emulator" + File.separator; //$NON-NLS-1$
-
- /** Path of the platform tools directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator;
-
- /** Path of the Platform tools Lib directory relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_PLATFORM_TOOLS_LIB_FOLDER =
- OS_SDK_PLATFORM_TOOLS_FOLDER + FD_LIB + File.separator;
-
- /** Path of the bin folder of proguard folder relative to the sdk folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SDK_TOOLS_PROGUARD_BIN_FOLDER =
- SdkConstants.OS_SDK_TOOLS_FOLDER +
- "proguard" + File.separator + //$NON-NLS-1$
- "bin" + File.separator; //$NON-NLS-1$
-
- /* Folder paths relative to a platform or add-on folder */
-
- /** Path of the images directory relative to a platform or addon folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_IMAGES_FOLDER = FD_IMAGES + File.separator;
-
- /** Path of the skin directory relative to a platform or addon folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_SKINS_FOLDER = FD_SKINS + File.separator;
-
- /* Folder paths relative to a Platform folder */
-
- /** Path of the data directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator;
-
- /** Path of the renderscript directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_RENDERSCRIPT_FOLDER = FD_RENDERSCRIPT + File.separator;
-
-
- /** Path of the samples directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator;
-
- /** Path of the resources directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_RESOURCES_FOLDER =
- OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator;
-
- /** Path of the fonts directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_FONTS_FOLDER =
- OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator;
-
- /** Path of the android source directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator;
-
- /** Path of the android templates directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator;
-
- /** Path of the Ant build rules directory relative to a platform folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_PLATFORM_ANT_FOLDER = FD_ANT + File.separator;
-
- /** Path of the attrs.xml file relative to a platform folder. */
- public final static String OS_PLATFORM_ATTRS_XML =
- OS_PLATFORM_RESOURCES_FOLDER + AndroidConstants.FD_RES_VALUES + File.separator +
- FN_ATTRS_XML;
-
- /** Path of the attrs_manifest.xml file relative to a platform folder. */
- public final static String OS_PLATFORM_ATTRS_MANIFEST_XML =
- OS_PLATFORM_RESOURCES_FOLDER + AndroidConstants.FD_RES_VALUES + File.separator +
- FN_ATTRS_MANIFEST_XML;
-
- /** Path of the layoutlib.jar file relative to a platform folder. */
- public final static String OS_PLATFORM_LAYOUTLIB_JAR =
- OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR;
-
- /** Path of the renderscript include folder relative to a platform folder. */
- public final static String OS_FRAMEWORK_RS =
- FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE;
- /** Path of the renderscript (clang) include folder relative to a platform folder. */
- public final static String OS_FRAMEWORK_RS_CLANG =
- FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG;
-
- /* Folder paths relative to a addon folder */
-
- /** Path of the images directory relative to a folder folder.
- * This is an OS path, ending with a separator. */
- public final static String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
-
- /** Skin default **/
- public final static String SKIN_DEFAULT = "default"; //$NON-NLS-1$
-
- /** SDK property: ant templates revision */
- public final static String PROP_SDK_ANT_TEMPLATES_REVISION =
- "sdk.ant.templates.revision"; //$NON-NLS-1$
-
- /** SDK property: default skin */
- public final static String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; //$NON-NLS-1$
-
- /* Android Class Constants */
- public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
- public final static String CLASS_APPLICATION = "android.app.Application"; //$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 =
- "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
- public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
- public final static String CLASS_R = "android.R"; //$NON-NLS-1$
- public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
- public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
- public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
- public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
- public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
- public final static String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
- public final static String CLASS_VIEWGROUP_LAYOUTPARAMS =
- CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; //$NON-NLS-1$
- public final static String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
- public final static String CLASS_FRAMELAYOUT =
- "android.widget." + CLASS_NAME_FRAMELAYOUT; //$NON-NLS-1$
- public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
- public final static String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
- public final static String CLASS_PREFERENCES =
- "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 static final String CLASS_FRAGMENT = "android.app.Fragment"; //$NON-NLS-1$
- public static final String CLASS_V4_FRAGMENT = "android.support.v4.app.Fragment"; //$NON-NLS-1$
- /** MockView is part of the layoutlib bridge and used to display classes that have
- * no rendering in the graphical layout editor. */
- public final static String CLASS_MOCK_VIEW = "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$
-
-
-
- /** Returns the appropriate name for the 'android' command, which is 'android.exe' for
- * Windows and 'android' for all other platforms. */
- public static String androidCmdName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- String cmd = "android"; //$NON-NLS-1$
- if (os.startsWith("Windows")) { //$NON-NLS-1$
- cmd += ".bat"; //$NON-NLS-1$
- }
- return cmd;
- }
-
- /** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for
- * Windows and 'mkdsdcard' for all other platforms. */
- public static String mkSdCardCmdName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- String cmd = "mksdcard"; //$NON-NLS-1$
- if (os.startsWith("Windows")) { //$NON-NLS-1$
- cmd += ".exe"; //$NON-NLS-1$
- }
- return cmd;
- }
-
- /**
- * Returns current platform
- *
- * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
- * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
- */
- public static int currentPlatform() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- if (os.startsWith("Mac OS")) { //$NON-NLS-1$
- return PLATFORM_DARWIN;
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- return PLATFORM_WINDOWS;
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- return PLATFORM_LINUX;
- }
-
- return PLATFORM_UNKNOWN;
- }
-
- /**
- * Returns current platform's UI name
- *
- * @return one of "Windows", "Mac OS X", "Linux" or "other".
- */
- public static String currentPlatformName() {
- String os = System.getProperty("os.name"); //$NON-NLS-1$
- if (os.startsWith("Mac OS")) { //$NON-NLS-1$
- return "Mac OS X"; //$NON-NLS-1$
- } else if (os.startsWith("Windows")) { //$NON-NLS-1$
- return "Windows"; //$NON-NLS-1$
- } else if (os.startsWith("Linux")) { //$NON-NLS-1$
- return "Linux"; //$NON-NLS-1$
- }
-
- return "Other";
- }
-
- private static String ext(String windowsExtension, String nonWindowsExtension) {
- if (CURRENT_PLATFORM == PLATFORM_WINDOWS) {
- return windowsExtension;
- } else {
- return nonWindowsExtension;
- }
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index 8284054..0bca185 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -16,7 +16,9 @@
package com.android.sdklib;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.io.FileWrapper;
@@ -30,8 +32,11 @@ import com.android.sdklib.internal.repository.NullTaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.repository.PkgProps;
-import com.android.util.Pair;
+import com.android.utils.ILogger;
+import com.android.utils.NullLogger;
+import com.android.utils.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -45,9 +50,11 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.zip.Adler32;
/**
* The SDK manager parses the SDK folder and gives access to the content.
@@ -56,6 +63,8 @@ import java.util.regex.Pattern;
*/
public class SdkManager {
+ private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG") != null; //$NON-NLS-1$
+
public final static String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
public final static String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
public final static String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
@@ -97,38 +106,12 @@ public class SdkManager {
private final String mOsSdkPath;
/** Valid targets that have been loaded. Can be empty but not null. */
private IAndroidTarget[] mTargets = new IAndroidTarget[0];
-
- public static class LayoutlibVersion implements Comparable<LayoutlibVersion> {
- private final int mApi;
- private final int mRevision;
-
- public static final int NOT_SPECIFIED = 0;
-
- public LayoutlibVersion(int api, int revision) {
- mApi = api;
- mRevision = revision;
- }
-
- public int getApi() {
- return mApi;
- }
-
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public int compareTo(LayoutlibVersion rhs) {
- boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED;
- int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0);
- int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0);
- return lhsValue - rhsValue;
- }
- }
+ /** A map to keep information on directories to see if they change later. */
+ private final Map<File, DirInfo> mTargetDirs = new HashMap<File, SdkManager.DirInfo>();
/**
* Create a new {@link SdkManager} instance.
- * External users should use {@link #createManager(String, ISdkLog)}.
+ * External users should use {@link #createManager(String, ILogger)}.
*
* @param osSdkPath the location of the SDK.
*/
@@ -140,23 +123,13 @@ public class SdkManager {
/**
* Creates an {@link SdkManager} for a given sdk location.
* @param osSdkPath the location of the SDK.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
* @return the created {@link SdkManager} or null if the location is not valid.
*/
- public static SdkManager createManager(String osSdkPath, ISdkLog log) {
+ public static SdkManager createManager(String osSdkPath, ILogger log) {
try {
SdkManager manager = new SdkManager(osSdkPath);
- ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
- loadPlatforms(osSdkPath, list, log);
- loadAddOns(osSdkPath, list, log);
-
- // sort the targets/add-ons
- Collections.sort(list);
-
- manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
-
- // Initialize the targets' sample paths, after the targets have been set.
- manager.initializeSamplePaths(log);
+ manager.reloadSdk(log);
return manager;
} catch (IllegalArgumentException e) {
@@ -167,6 +140,112 @@ public class SdkManager {
}
/**
+ * Reloads the content of the SDK.
+ *
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
+ */
+ public void reloadSdk(ILogger log) {
+ // get the current target list.
+ mTargetDirs.clear();
+ ArrayList<IAndroidTarget> targets = new ArrayList<IAndroidTarget>();
+ loadPlatforms(mOsSdkPath, targets, mTargetDirs, log);
+ loadAddOns(mOsSdkPath, targets, mTargetDirs, log);
+
+ // For now replace the old list with the new one.
+ // In the future we may want to keep the current objects, so that ADT doesn't have to deal
+ // with new IAndroidTarget objects when a target didn't actually change.
+
+ // sort the targets/add-ons
+ Collections.sort(targets);
+ setTargets(targets.toArray(new IAndroidTarget[targets.size()]));
+
+ // load the samples, after the targets have been set.
+ initializeSamplePaths(log);
+ }
+
+ /**
+ * Checks whether any of the SDK platforms/add-ons have changed on-disk
+ * since we last loaded the SDK. This does not reload the SDK nor does it
+ * change the underlying targets.
+ *
+ * @return True if at least one directory or source.prop has changed.
+ */
+ public boolean hasChanged() {
+ Set<File> visited = new HashSet<File>();
+ boolean changed = false;
+
+ File platformFolder = new File(mOsSdkPath, SdkConstants.FD_PLATFORMS);
+ if (platformFolder.isDirectory()) {
+ File[] platforms = platformFolder.listFiles();
+ if (platforms != null) {
+ for (File platform : platforms) {
+ if (!platform.isDirectory()) {
+ continue;
+ }
+ visited.add(platform);
+ DirInfo dirInfo = mTargetDirs.get(platform);
+ if (dirInfo == null) {
+ // This is a new platform directory.
+ changed = true;
+ } else {
+ changed = dirInfo.hasChanged();
+ }
+ if (changed) {
+ if (DEBUG) {
+ System.out.println("SDK changed due to " + //$NON-NLS-1$
+ (dirInfo != null ? dirInfo.toString() : platform.getPath()));
+ }
+ }
+ }
+ }
+ }
+
+ File addonFolder = new File(mOsSdkPath, SdkConstants.FD_ADDONS);
+
+ if (!changed && addonFolder.isDirectory()) {
+ File[] addons = addonFolder.listFiles();
+ if (addons != null) {
+ for (File addon : addons) {
+ if (!addon.isDirectory()) {
+ continue;
+ }
+ visited.add(addon);
+ DirInfo dirInfo = mTargetDirs.get(addon);
+ if (dirInfo == null) {
+ // This is a new add-on directory.
+ changed = true;
+ } else {
+ changed = dirInfo.hasChanged();
+ }
+ if (changed) {
+ if (DEBUG) {
+ System.out.println("SDK changed due to " + //$NON-NLS-1$
+ (dirInfo != null ? dirInfo.toString() : addon.getPath()));
+ }
+ }
+ }
+ }
+ }
+
+ if (!changed) {
+ // Check whether some pre-existing target directories have vanished.
+ for (File previousDir : mTargetDirs.keySet()) {
+ if (!visited.contains(previousDir)) {
+ // This directory is no longer present.
+ changed = true;
+ if (DEBUG) {
+ System.out.println("SDK changed: " + //$NON-NLS-1$
+ previousDir.getPath() + " removed"); //$NON-NLS-1$
+ }
+ break;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ /**
* Returns the location of the SDK.
*/
public String getLocation() {
@@ -247,29 +326,6 @@ public class SdkManager {
}
/**
- * Reloads the content of the SDK.
- *
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
- */
- public void reloadSdk(ISdkLog log) {
- // get the current target list.
- ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
- loadPlatforms(mOsSdkPath, list, log);
- loadAddOns(mOsSdkPath, list, log);
-
- // For now replace the old list with the new one.
- // In the future we may want to keep the current objects, so that ADT doesn't have to deal
- // with new IAndroidTarget objects when a target didn't actually change.
-
- // sort the targets/add-ons
- Collections.sort(list);
- setTargets(list.toArray(new IAndroidTarget[list.size()]));
-
- // load the samples, after the targets have been set.
- initializeSamplePaths(log);
- }
-
- /**
* Returns the greatest {@link LayoutlibVersion} found amongst all platform
* targets currently loaded in the SDK.
* <p/>
@@ -312,7 +368,7 @@ public class SdkManager {
Package[] packages = parser.parseSdk(mOsSdkPath,
this,
LocalSdkParser.PARSE_EXTRAS,
- new NullTaskMonitor(new NullSdkLog()));
+ new NullTaskMonitor(NullLogger.getLogger()));
Map<File, String> samples = new HashMap<File, String>();
@@ -325,6 +381,14 @@ public class SdkManager {
File path = new File(a.getLocalOsPath(), SdkConstants.FD_SAMPLES);
if (path.isDirectory()) {
samples.put(path, pkg.getListDescription());
+ continue;
+ }
+ // Some old-style extras simply have a single "sample" directory.
+ // Accept it if it contains an AndroidManifest.xml.
+ path = new File(a.getLocalOsPath(), SdkConstants.FD_SAMPLE);
+ if (path.isDirectory() &&
+ new File(path, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
+ samples.put(path, pkg.getListDescription());
}
}
}
@@ -332,6 +396,52 @@ public class SdkManager {
return samples;
}
+ /**
+ * Returns a map of all the extras found in the <em>local</em> SDK with their major revision.
+ * <p/>
+ * Map keys are in the form "vendor-id/path-id". These ids uniquely identify an extra package.
+ * The version is the incremental integer major revision of the package.
+ *
+ * @return A non-null possibly empty map of { string "vendor/path" => integer major revision }
+ */
+ public @NonNull Map<String, Integer> getExtrasVersions() {
+ LocalSdkParser parser = new LocalSdkParser();
+ Package[] packages = parser.parseSdk(mOsSdkPath,
+ this,
+ LocalSdkParser.PARSE_EXTRAS,
+ new NullTaskMonitor(NullLogger.getLogger()));
+
+ Map<String, Integer> extraVersions = new TreeMap<String, Integer>();
+
+ for (Package pkg : packages) {
+ if (pkg instanceof ExtraPackage && pkg.isLocal()) {
+ ExtraPackage ep = (ExtraPackage) pkg;
+ String vendor = ep.getVendorId();
+ String path = ep.getPath();
+ int majorRev = ep.getRevision().getMajor();
+
+ extraVersions.put(vendor + '/' + path, majorRev);
+ }
+ }
+
+ return extraVersions;
+ }
+
+ /** Returns the platform tools version if installed, null otherwise. */
+ public @Nullable String getPlatformToolsVersion() {
+ LocalSdkParser parser = new LocalSdkParser();
+ Package[] packages = parser.parseSdk(mOsSdkPath, this, LocalSdkParser.PARSE_PLATFORM_TOOLS,
+ new NullTaskMonitor(NullLogger.getLogger()));
+
+ for (Package pkg : packages) {
+ if (pkg instanceof PlatformToolPackage && pkg.isLocal()) {
+ return pkg.getRevision().toShortString();
+ }
+ }
+
+ return null;
+ }
+
// -------- private methods ----------
@@ -340,23 +450,30 @@ public class SdkManager {
* Creates the "platforms" folder if necessary.
*
* @param sdkOsPath Location of the SDK
- * @param list the list to fill with the platforms.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param targets the list to fill with the platforms.
+ * @param dirInfos a map to keep information on directories to see if they change later.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
* @throws RuntimeException when the "platforms" folder is missing and cannot be created.
*/
- private static void loadPlatforms(String sdkOsPath, ArrayList<IAndroidTarget> list,
- ISdkLog log) {
+ private static void loadPlatforms(
+ String sdkOsPath,
+ ArrayList<IAndroidTarget> targets,
+ Map<File, DirInfo> dirInfos, ILogger log) {
File platformFolder = new File(sdkOsPath, SdkConstants.FD_PLATFORMS);
if (platformFolder.isDirectory()) {
File[] platforms = platformFolder.listFiles();
for (File platform : platforms) {
+ PlatformTarget target = null;
if (platform.isDirectory()) {
- PlatformTarget target = loadPlatform(sdkOsPath, platform, log);
+ target = loadPlatform(sdkOsPath, platform, log);
if (target != null) {
- list.add(target);
+ targets.add(target);
}
+ // Remember we visited this file/directory,
+ // even if we failed to load anything from it.
+ dirInfos.put(platform, new DirInfo(platform));
} else {
log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
}
@@ -383,10 +500,12 @@ public class SdkManager {
* Loads a specific Platform at a given location.
* @param sdkOsPath Location of the SDK
* @param platformFolder the root folder of the platform.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
*/
- private static PlatformTarget loadPlatform(String sdkOsPath, File platformFolder,
- ISdkLog log) {
+ private static PlatformTarget loadPlatform(
+ String sdkOsPath,
+ File platformFolder,
+ ILogger log) {
FileWrapper buildProp = new FileWrapper(platformFolder, SdkConstants.FN_BUILD_PROP);
FileWrapper sourcePropFile = new FileWrapper(platformFolder, SdkConstants.FN_SOURCE_PROP);
@@ -437,12 +556,10 @@ public class SdkManager {
}
}
- // codename (optional)
- String apiCodename = platformProp.get(PROP_VERSION_CODENAME);
- if (apiCodename != null && apiCodename.equals("REL")) {
- apiCodename = null; // REL means it's a release version and therefore the
- // codename is irrelevant at this point.
- }
+ // Codename must be either null or a platform codename.
+ // REL means it's a release version and therefore the codename should be null.
+ AndroidVersion apiVersion =
+ new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
// version string
String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
@@ -489,14 +606,13 @@ public class SdkManager {
}
ISystemImage[] systemImages =
- getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename);
+ getPlatformSystemImages(sdkOsPath, platformFolder, apiVersion);
// create the target.
PlatformTarget target = new PlatformTarget(
sdkOsPath,
platformFolder.getAbsolutePath(),
- apiNumber,
- apiCodename,
+ apiVersion,
apiName,
revision,
layoutlibVersion,
@@ -574,16 +690,14 @@ public class SdkManager {
*
* @param sdkOsPath The path to the SDK.
* @param root Root of the platform target being loaded.
- * @param apiNumber API level of platform being loaded
- * @param apiCodename Optional codename of platform being loaded
+ * @param version API level + codename of platform being loaded.
* @return an array of ISystemImage containing all the system images for the target.
* The list can be empty.
*/
private static ISystemImage[] getPlatformSystemImages(
String sdkOsPath,
File root,
- int apiNumber,
- String apiCodename) {
+ AndroidVersion version) {
Set<ISystemImage> found = new TreeSet<ISystemImage>();
Set<String> abiFound = new HashSet<String>();
@@ -592,8 +706,6 @@ public class SdkManager {
// The actual directory names are irrelevant.
// If we find multiple occurrences of the same platform/abi, the first one read wins.
- AndroidVersion version = new AndroidVersion(apiNumber, apiCodename);
-
File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles();
if (firstLevelFiles != null) {
for (File firstLevel : firstLevelFiles) {
@@ -678,24 +790,34 @@ public class SdkManager {
* Creates the "add-ons" folder if necessary.
*
* @param osSdkPath Location of the SDK
- * @param list the list to fill with the add-ons.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param targets the list to fill with the add-ons.
+ * @param dirInfos a map to keep information on directories to see if they change later.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
* @throws RuntimeException when the "add-ons" folder is missing and cannot be created.
*/
- private static void loadAddOns(String osSdkPath, ArrayList<IAndroidTarget> list, ISdkLog log) {
+ private static void loadAddOns(
+ String osSdkPath,
+ ArrayList<IAndroidTarget> targets,
+ Map<File, DirInfo> dirInfos, ILogger log) {
File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS);
if (addonFolder.isDirectory()) {
File[] addons = addonFolder.listFiles();
- IAndroidTarget[] targetList = list.toArray(new IAndroidTarget[list.size()]);
+ IAndroidTarget[] targetList = targets.toArray(new IAndroidTarget[targets.size()]);
- for (File addon : addons) {
- // Add-ons have to be folders. Ignore files and no need to warn about them.
- if (addon.isDirectory()) {
- AddOnTarget target = loadAddon(addon, targetList, log);
- if (target != null) {
- list.add(target);
+ if (addons != null) {
+ for (File addon : addons) {
+ // Add-ons have to be folders. Ignore files and no need to warn about them.
+ AddOnTarget target = null;
+ if (addon.isDirectory()) {
+ target = loadAddon(addon, targetList, log);
+ if (target != null) {
+ targets.add(target);
+ }
+ // Remember we visited this file/directory,
+ // even if we failed to load anything from it.
+ dirInfos.put(addon, new DirInfo(addon));
}
}
}
@@ -721,11 +843,11 @@ public class SdkManager {
* Loads a specific Add-on at a given location.
* @param addonDir the location of the add-on directory.
* @param targetList The list of Android target that were already loaded from the SDK.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
*/
private static AddOnTarget loadAddon(File addonDir,
IAndroidTarget[] targetList,
- ISdkLog log) {
+ ILogger log) {
// Parse the addon properties to ensure we can load it.
Pair<Map<String, String>, String> infos = parseAddonProperties(addonDir, targetList, log);
@@ -867,7 +989,7 @@ public class SdkManager {
*
* @param addonDir the location of the addon directory.
* @param targetList The list of Android target that were already loaded from the SDK.
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
* @return A pair with the property map and an error string. Both can be null but not at the
* same time. If a non-null error is present then the property map must be ignored. The error
* should be translatable as it might show up in the SdkManager UI.
@@ -875,7 +997,7 @@ public class SdkManager {
public static Pair<Map<String, String>, String> parseAddonProperties(
File addonDir,
IAndroidTarget[] targetList,
- ISdkLog log) {
+ ILogger log) {
Map<String, String> propertyMap = null;
String error = null;
@@ -990,7 +1112,7 @@ public class SdkManager {
* @param platform The folder containing the platform.
* @param log Logger. Cannot be null.
*/
- private static boolean checkPlatformContent(File platform, ISdkLog log) {
+ private static boolean checkPlatformContent(File platform, ILogger log) {
for (String relativePath : sPlatformContentList) {
File f = new File(platform, relativePath);
if (!f.exists()) {
@@ -1045,7 +1167,7 @@ public class SdkManager {
*
* @param log Logger. Cannot be null.
*/
- private void initializeSamplePaths(ISdkLog log) {
+ private void initializeSamplePaths(ILogger log) {
File sampleFolder = new File(mOsSdkPath, SdkConstants.FD_SAMPLES);
if (sampleFolder.isDirectory()) {
File[] platforms = sampleFolder.listFiles();
@@ -1076,7 +1198,7 @@ public class SdkManager {
* @param log Logger for errors. Cannot be null.
* @return An {@link AndroidVersion} or null on error.
*/
- private AndroidVersion getSamplesVersion(File folder, ISdkLog log) {
+ private AndroidVersion getSamplesVersion(File folder, ILogger log) {
File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP);
try {
Properties p = new Properties();
@@ -1105,4 +1227,142 @@ public class SdkManager {
return null;
}
+ // -------------
+
+ public static class LayoutlibVersion implements Comparable<LayoutlibVersion> {
+ private final int mApi;
+ private final int mRevision;
+
+ public static final int NOT_SPECIFIED = 0;
+
+ public LayoutlibVersion(int api, int revision) {
+ mApi = api;
+ mRevision = revision;
+ }
+
+ public int getApi() {
+ return mApi;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public int compareTo(LayoutlibVersion rhs) {
+ boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED;
+ int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0);
+ int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0);
+ return lhsValue - rhsValue;
+ }
+ }
+
+ // -------------
+
+ private static class DirInfo {
+ private final @NonNull File mDir;
+ private final long mDirModifiedTS;
+ private final long mPropsModifedTS;
+ private final long mPropsChecksum;
+
+ /**
+ * Creates a new immutable {@link DirInfo}.
+ *
+ * @param dir The platform/addon directory of the target. It should be a directory.
+ */
+ public DirInfo(@NonNull File dir) {
+ mDir = dir;
+ mDirModifiedTS = dir.lastModified();
+
+ // Capture some info about the source.properties file if it exists.
+ // We use propsModifedTS == 0 to mean there is no props file.
+ long propsChecksum = 0;
+ long propsModifedTS = 0;
+ File props = new File(dir, SdkConstants.FN_SOURCE_PROP);
+ if (props.isFile()) {
+ propsModifedTS = props.lastModified();
+ propsChecksum = getFileChecksum(props);
+ }
+ mPropsModifedTS = propsModifedTS;
+ mPropsChecksum = propsChecksum;
+ }
+
+ /**
+ * Checks whether the directory/source.properties attributes have changed.
+ *
+ * @return True if the directory modified timestampd or
+ * its source.property files have changed.
+ */
+ public boolean hasChanged() {
+ // Does platform directory still exist?
+ if (!mDir.isDirectory()) {
+ return true;
+ }
+ // Has platform directory modified-timestamp changed?
+ if (mDirModifiedTS != mDir.lastModified()) {
+ return true;
+ }
+
+ File props = new File(mDir, SdkConstants.FN_SOURCE_PROP);
+
+ // The directory did not have a props file if target was null or
+ // if mPropsModifedTS is 0.
+ boolean hadProps = mPropsModifedTS != 0;
+
+ // Was there a props file and it vanished, or there wasn't and there's one now?
+ if (hadProps != props.isFile()) {
+ return true;
+ }
+
+ if (hadProps) {
+ // Has source.props file modified-timestampd changed?
+ if (mPropsModifedTS != props.lastModified()) {
+ return true;
+ }
+ // Had the content of source.props changed?
+ if (mPropsChecksum != getFileChecksum(props)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes an adler32 checksum (source.props are small files, so this
+ * should be OK with an acceptable collision rate.)
+ */
+ private static long getFileChecksum(File file) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ Adler32 a = new Adler32();
+ byte[] buf = new byte[1024];
+ int n;
+ while ((n = fis.read(buf)) > 0) {
+ a.update(buf, 0, n);
+ }
+ return a.getValue();
+ } catch (Exception ignore) {
+ } finally {
+ try {
+ if (fis != null) {
+ fis.close();
+ }
+ } catch(Exception ignore) {};
+ }
+ return 0;
+ }
+
+ /** Returns a visual representation of this object for debugging. */
+ @Override
+ public String toString() {
+ String s = String.format("<DirInfo %1$s TS=%2$d", mDir, mDirModifiedTS); //$NON-NLS-1$
+ if (mPropsModifedTS != 0) {
+ s += String.format(" | Props TS=%1$d, Chksum=%2$s", //$NON-NLS-1$
+ mPropsModifedTS, mPropsChecksum);
+ }
+ return s + ">"; //$NON-NLS-1$
+ }
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/StdSdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/StdSdkLog.java
deleted file mode 100644
index 5b1e237..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/StdSdkLog.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-
-/**
- * An implementation of {@link ISdkLog} that prints to {@link System#out} and {@link System#err}.
- * <p/>
- * This is mostly useful for unit tests. It should not be used by GUI-based tools (e.g.
- * Eclipse plugin or SWT-based apps) which should have a better way to expose their logging
- * error and warnings.
- */
-public class StdSdkLog implements ISdkLog {
-
- @Override
- public void error(Throwable t, String errorFormat, Object... args) {
- if (errorFormat != null) {
- String msg = String.format("Error: " + errorFormat, args);
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS &&
- !msg.endsWith("\r\n") &&
- msg.endsWith("\n")) {
- // remove last \n so that println can use \r\n as needed.
- msg = msg.substring(0, msg.length() - 1);
- }
-
- System.err.print(msg);
-
- if (!msg.endsWith("\n")) {
- System.err.println();
- }
- }
- if (t != null) {
- System.err.println(String.format("Error: %1$s%2$s", t.getMessage()));
- }
- }
-
- @Override
- public void warning(String warningFormat, Object... args) {
- String msg = String.format("Warning: " + warningFormat, args);
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS &&
- !msg.endsWith("\r\n") &&
- msg.endsWith("\n")) {
- // remove last \n so that println can use \r\n as needed.
- msg = msg.substring(0, msg.length() - 1);
- }
-
- System.out.print(msg);
-
- if (!msg.endsWith("\n")) {
- System.out.println();
- }
- }
-
- @Override
- public void printf(String msgFormat, Object... args) {
- String msg = String.format(msgFormat, args);
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS &&
- !msg.endsWith("\r\n") &&
- msg.endsWith("\n")) {
- // remove last \n so that println can use \r\n as needed.
- msg = msg.substring(0, msg.length() - 1);
- }
-
- System.out.print(msg);
-
- if (!msg.endsWith("\n")) {
- System.out.println();
- }
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java
index e9a8c57..afc11c7 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java
@@ -16,6 +16,7 @@
package com.android.sdklib;
+import com.android.SdkConstants;
import com.android.sdklib.io.FileOp;
import java.io.File;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
index 765ec3c..f5abe9e 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
@@ -16,7 +16,7 @@
package com.android.sdklib.build;
-import com.android.sdklib.SdkConstants;
+import com.android.SdkConstants;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
@@ -51,6 +51,8 @@ public final class ApkBuilder implements IArchiveBuilder {
private final static Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
Pattern.CASE_INSENSITIVE);
+ private final static Pattern PATTERN_BITCODELIB_EXT = Pattern.compile("^.+\\.bc$",
+ Pattern.CASE_INSENSITIVE);
/**
* A No-op zip filter. It's used to detect conflicts.
@@ -125,7 +127,7 @@ public final class ApkBuilder implements IArchiveBuilder {
mAddedFiles.put(archivePath, mInputFile);
}
- if (archivePath.endsWith(".so")) {
+ if (archivePath.endsWith(".so") || archivePath.endsWith(".bc")) {
mNativeLibs.add(archivePath);
// only .so located in lib/ will interfere with the installation
@@ -456,10 +458,14 @@ public final class ApkBuilder implements IArchiveBuilder {
}
} catch (ApkCreationException e) {
- mBuilder.cleanUp();
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
throw e;
} catch (Exception e) {
- mBuilder.cleanUp();
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
throw new ApkCreationException(e);
}
}
@@ -601,7 +607,6 @@ public final class ApkBuilder implements IArchiveBuilder {
* Adds the resources from a source folder to a given {@link IArchiveBuilder}
* @param sourceFolder the source folder.
* @throws ApkCreationException if an error occurred
- * @throws SealedApkException if the APK is already sealed.
* @throws DuplicateFileException if a file conflicts with another already added to the APK
* at the same location inside the APK archive.
*/
@@ -674,6 +679,7 @@ public final class ApkBuilder implements IArchiveBuilder {
// are gdbserver executables
if (lib.isFile() &&
(PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
+ PATTERN_BITCODELIB_EXT.matcher(lib.getName()).matches() ||
(mDebugMode &&
SdkConstants.FN_GDBSERVER.equals(
lib.getName())))) {
@@ -748,6 +754,7 @@ public final class ApkBuilder implements IArchiveBuilder {
// are gdbserver executables
if (lib.isFile() &&
(PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
+ PATTERN_BITCODELIB_EXT.matcher(lib.getName()).matches() ||
(debugMode &&
SdkConstants.FN_GDBSERVER.equals(
lib.getName())))) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/JarListSanitizer.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/JarListSanitizer.java
index fa7b00b..4d1dcdb 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/JarListSanitizer.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/JarListSanitizer.java
@@ -435,10 +435,11 @@ public class JarListSanitizer {
*/
private static String getSha1(File f) throws Sha1Exception {
synchronized (sBuffer) {
+ FileInputStream fis = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
- FileInputStream fis = new FileInputStream(f);
+ fis = new FileInputStream(f);
while (true) {
int length = fis.read(sBuffer);
if (length > 0) {
@@ -452,15 +453,27 @@ public class JarListSanitizer {
} catch (Exception e) {
throw new Sha1Exception(f, e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
}
}
}
private static String byteArray2Hex(final byte[] hash) {
Formatter formatter = new Formatter();
- for (byte b : hash) {
- formatter.format("%02x", b);
+ try {
+ for (byte b : hash) {
+ formatter.format("%02x", b);
+ }
+ return formatter.toString();
+ } finally {
+ formatter.close();
}
- return formatter.toString();
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Abi.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Abi.java
new file mode 100644
index 0000000..080ae75
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Abi.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.SdkConstants;
+
+public enum Abi {
+ ARMEABI(SdkConstants.ABI_ARMEABI),
+ ARMEABI_V7A(SdkConstants.ABI_ARMEABI_V7A),
+ X86(SdkConstants.ABI_INTEL_ATOM),
+ MIPS(SdkConstants.ABI_MIPS);
+
+ private final String mValue;
+
+ private Abi(String value) {
+ mValue = value;
+ }
+
+ public static Abi getEnum(String value) {
+ for (Abi a : values()) {
+ if (a.mValue.equals(value)) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/BluetoothProfile.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/BluetoothProfile.java
new file mode 100644
index 0000000..536dcd8
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/BluetoothProfile.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum BluetoothProfile {
+ A2DP("A2DP"),
+ ATT("ATT"),
+ AVRCP("AVRCP"),
+ AVDTP("AVDTP"),
+ BIP("BIP"),
+ BPP("BPP"),
+ CIP("CIP"),
+ CTP("CTP"),
+ DIP("DIP"),
+ DUN("DUN"),
+ FAX("FAX"),
+ FTP("FTP"),
+ GAVDP("GAVDP"),
+ GAP("GAP"),
+ GATT("GATT"),
+ GOEP("GOEP"),
+ HCRP("HCRP"),
+ HDP("HDP"),
+ HFP("HFP"),
+ HID("HID"),
+ HSP("HSP"),
+ ICP("ICP"),
+ LAP("LAP"),
+ MAP("MAP"),
+ OPP("OPP"),
+ PAN("PAN"),
+ PBA("PBA"),
+ PBAP("PBAP"),
+ SPP("SPP"),
+ SDAP("SDAP"),
+ SAP("SAP"),
+ SIM("SIM"),
+ rSAP("rSAP"),
+ SYNCH("SYNCH"),
+ VDP("VDP"),
+ WAPB("WAPB");
+
+
+ private final String mValue;
+
+ private BluetoothProfile(String value) {
+ mValue = value;
+ }
+
+ public static BluetoothProfile getEnum(String value) {
+ for (BluetoothProfile bp : values()) {
+ if (bp.mValue.equals(value)) {
+ return bp;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ButtonType.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ButtonType.java
new file mode 100644
index 0000000..6ab67d4
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ButtonType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum ButtonType {
+ HARD("hard"),
+ SOFT("soft");
+
+ private final String mValue;
+
+ private ButtonType(String value) {
+ mValue = value;
+ }
+
+ public static ButtonType getEnum(String value) {
+ for (ButtonType n : values()) {
+ if (n.mValue.equals(value)) {
+ return n;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Camera.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Camera.java
new file mode 100644
index 0000000..d7d33fe
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Camera.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public class Camera {
+ private CameraLocation mLocation;
+ private boolean mAutofocus;
+ private boolean mFlash;
+
+ /**
+ * Creates a {@link Camera} with reasonable defaults.
+ *
+ * The resulting {@link Camera} with be on the {@link CameraLocation#BACK} with both autofocus
+ * and flash.
+ */
+ public Camera() {
+ this(CameraLocation.BACK, true, true);
+ }
+
+ /**
+ * Creates a new {@link Camera} which describes an on device camera and it's features.
+ * @param location The location of the {@link Camera} on the device. Either
+ * {@link CameraLocation#FRONT} or {@link CameraLocation#BACK}.
+ * @param autofocus Whether the {@link Camera} can auto-focus.
+ * @param flash Whether the {@link Camera} has flash.
+ */
+ public Camera(CameraLocation location, boolean autofocus, boolean flash) {
+ mLocation = location;
+ mAutofocus = autofocus;
+ mFlash = flash;
+ }
+
+ public CameraLocation getLocation() {
+ return mLocation;
+ }
+
+ public void setLocation(CameraLocation cl) {
+ mLocation = cl;
+ }
+
+ public boolean hasAutofocus() {
+ return mAutofocus;
+ }
+
+ public void setAutofocus(boolean hasAutofocus) {
+ mAutofocus = hasAutofocus;
+ }
+
+ public boolean hasFlash() {
+ return mFlash;
+ }
+
+ public void setFlash(boolean flash) {
+ mFlash = flash;
+ }
+
+ /**
+ * Returns a copy of the object that shares no state with it,
+ * but is initialized to equivalent values.
+ *
+ * @return A copy of the object.
+ */
+ public Camera deepCopy() {
+ Camera c = new Camera();
+ c.mLocation = mLocation;
+ c.mAutofocus = mAutofocus;
+ c.mFlash = mFlash;
+ return c;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Camera)) {
+ return false;
+ }
+ Camera c = (Camera) o;
+ return mLocation == c.mLocation
+ && mAutofocus == c.hasAutofocus()
+ && mFlash == c.hasFlash();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + mLocation.ordinal();
+ hash = 31 * hash + (mAutofocus ? 1 : 0);
+ hash = 31 * hash + (mFlash ? 1 : 0);
+ return hash;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/CameraLocation.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/CameraLocation.java
new file mode 100644
index 0000000..9a8554d
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/CameraLocation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum CameraLocation {
+ FRONT("front"),
+ BACK("back");
+
+ private final String mValue;
+
+ private CameraLocation(String value) {
+ mValue = value;
+ }
+
+ public static CameraLocation getEnum(String value) {
+ for (CameraLocation l : values()) {
+ if (l.mValue.equals(value)) {
+ return l;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java
new file mode 100644
index 0000000..cb712f0
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Device.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.dvlib.DeviceSchema;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Instances of this class contain the specifications for a device. Use the
+ * {@link Builder} class to construct a Device object, or the
+ * {@link DeviceParser} if constructing device objects from XML conforming to
+ * the {@link DeviceSchema} standards.
+ */
+public final class Device {
+ /** Name of the device */
+ private final String mName;
+ /** Manufacturer of the device */
+ private final String mManufacturer;
+ /** A list of software capabilities, one for each API level range */
+ private final List<Software> mSoftware;
+ /** A list of phone states (landscape, portrait with keyboard out, etc.) */
+ private final List<State> mState;
+ /** Meta information such as icon files and device frames */
+ private final Meta mMeta;
+ /** Default state of the device */
+ private final State mDefaultState;
+
+ /**
+ * Returns the name of the {@link Device}.
+ *
+ * @return The name of the {@link Device}.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the manufacturer of the {@link Device}.
+ *
+ * @return The name of the manufacturer of the {@link Device}.
+ */
+ public String getManufacturer() {
+ return mManufacturer;
+ }
+
+ /**
+ * Returns all of the {@link Software} configurations of the {@link Device}.
+ *
+ * @return A list of all the {@link Software} configurations.
+ */
+ public List<Software> getAllSoftware() {
+ return mSoftware;
+ }
+
+ /**
+ * Returns all of the {@link State}s the {@link Device} can be in.
+ *
+ * @return A list of all the {@link State}s.
+ */
+ public List<State> getAllStates() {
+ return mState;
+ }
+
+ /**
+ * Returns the default {@link Hardware} configuration for the device. This
+ * is really just a shortcut for getting the {@link Hardware} on the default
+ * {@link State}
+ *
+ * @return The default {@link Hardware} for the device.
+ */
+ public Hardware getDefaultHardware() {
+ return mDefaultState.getHardware();
+ }
+
+ /**
+ * Returns the {@link Meta} object for the device, which contains meta
+ * information about the device, such as the location of icons.
+ *
+ * @return The {@link Meta} object for the {@link Device}.
+ */
+ public Meta getMeta() {
+ return mMeta;
+ }
+
+ /**
+ * Returns the default {@link State} of the {@link Device}.
+ *
+ * @return The default {@link State} of the {@link Device}.
+ */
+ public State getDefaultState() {
+ return mDefaultState;
+ }
+
+ /**
+ * Returns the software configuration for the given API version.
+ *
+ * @param apiVersion
+ * The API version requested.
+ * @return The Software instance for the requested API version or null if
+ * the API version is unsupported for this device.
+ */
+ public Software getSoftware(int apiVersion) {
+ for (Software s : mSoftware) {
+ if (apiVersion >= s.getMinSdkLevel() && apiVersion <= s.getMaxSdkLevel()) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the state of the device with the given name.
+ *
+ * @param name
+ * The name of the state requested.
+ * @return The State object requested or null if there's no state with the
+ * given name.
+ */
+ public State getState(String name) {
+ for (State s : getAllStates()) {
+ if (s.getName().equals(name)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ public static class Builder {
+ private String mName;
+ private String mManufacturer;
+ private final List<Software> mSoftware = new ArrayList<Software>();
+ private final List<State> mState = new ArrayList<State>();
+ private Meta mMeta;
+ private State mDefaultState;
+
+ public Builder() { }
+
+ public Builder(Device d) {
+ mName = d.getName();
+ mManufacturer = d.getManufacturer();
+ for (Software s : d.getAllSoftware()) {
+ mSoftware.add(s.deepCopy());
+ }
+ for (State s : d.getAllStates()) {
+ mState.add(s.deepCopy());
+ }
+ mSoftware.addAll(d.getAllSoftware());
+ mState.addAll(d.getAllStates());
+ mMeta = d.getMeta();
+ mDefaultState = d.getDefaultState();
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public void setManufacturer(String manufacturer) {
+ mManufacturer = manufacturer;
+ }
+
+ public void addSoftware(Software sw) {
+ mSoftware.add(sw);
+ }
+
+ public void addAllSoftware(Collection<? extends Software> sw) {
+ mSoftware.addAll(sw);
+ }
+
+ public void addState(State state) {
+ mState.add(state);
+ }
+
+ public void addAllState(Collection<? extends State> states) {
+ mState.addAll(states);
+ }
+
+ /**
+ * Removes the first {@link State} with the given name
+ * @param stateName The name of the {@link State} to remove.
+ * @return Whether a {@link State} was removed or not.
+ */
+ public boolean removeState(String stateName) {
+ for (int i = 0; i < mState.size(); i++) {
+ if (stateName != null && stateName.equals(mState.get(i).getName())) {
+ mState.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setMeta(Meta meta) {
+ mMeta = meta;
+ }
+
+ public Device build() {
+ if (mSoftware.size() <= 0) {
+ throw generateBuildException("Device software not configured");
+ } else if (mState.size() <= 0) {
+ throw generateBuildException("Device states not configured");
+ }
+
+ if (mMeta == null) {
+ mMeta = new Meta();
+ }
+ for (State s : mState) {
+ if (s.isDefaultState()) {
+ mDefaultState = s;
+ break;
+ }
+ }
+ return new Device(this);
+ }
+
+ private IllegalStateException generateBuildException(String err) {
+ String device = "";
+ if (mManufacturer != null) {
+ device = mManufacturer + " ";
+ }
+ if (mName != null) {
+ device += mName;
+ } else {
+ device = "Unknown " + device +"Device";
+ }
+
+ return new IllegalStateException("Error building " + device + ": " +err);
+ }
+ }
+
+ protected Device(Builder b) {
+ mName = b.mName;
+ mManufacturer = b.mManufacturer;
+ mSoftware = Collections.unmodifiableList(b.mSoftware);
+ mState = Collections.unmodifiableList(b.mState);
+ mMeta = b.mMeta;
+ mDefaultState = b.mDefaultState;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Device)) {
+ return false;
+ }
+ Device d = (Device) o;
+ return mName.equals(d.getName())
+ && mManufacturer.equals(d.getManufacturer())
+ && mSoftware.equals(d.getAllSoftware())
+ && mState.equals(d.getAllStates())
+ && mMeta.equals(d.getMeta())
+ && mDefaultState.equals(d.getDefaultState());
+ }
+
+ @Override
+ /** A hash that's stable across JVM instances */
+ public int hashCode() {
+ int hash = 17;
+ for (Character c : mName.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ for (Character c : mManufacturer.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ hash = 31 * hash + mSoftware.hashCode();
+ hash = 31 * hash + mState.hashCode();
+ hash = 31 * hash + mMeta.hashCode();
+ hash = 31 * hash + mDefaultState.hashCode();
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java
new file mode 100644
index 0000000..3662c26
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceManager.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.resources.Keyboard;
+import com.android.resources.KeyboardState;
+import com.android.resources.Navigation;
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.avd.HardwareProperties;
+import com.android.sdklib.repository.PkgProps;
+import com.android.utils.ILogger;
+
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+
+/**
+ * Manager class for interacting with {@link Device}s within the SDK
+ */
+public class DeviceManager {
+
+ private final static String sDeviceProfilesProp = "DeviceProfiles";
+ private final static Pattern sPathPropertyPattern = Pattern.compile("^" + PkgProps.EXTRA_PATH
+ + "=" + sDeviceProfilesProp + "$");
+ private ILogger mLog;
+ // Vendor devices can't be a static list since they change based on the SDK
+ // Location
+ private List<Device> mVendorDevices;
+ // Keeps track of where the currently loaded vendor devices were loaded from
+ private String mVendorDevicesLocation = "";
+ private static List<Device> mUserDevices;
+ private static List<Device> mDefaultDevices;
+ private static final Object sLock = new Object();
+ private static final List<DevicesChangeListener> sListeners =
+ new ArrayList<DevicesChangeListener>();
+
+ public static enum DeviceStatus {
+ /**
+ * The device exists unchanged from the given configuration
+ */
+ EXISTS,
+ /**
+ * A device exists with the given name and manufacturer, but has a different configuration
+ */
+ CHANGED,
+ /**
+ * There is no device with the given name and manufacturer
+ */
+ MISSING;
+ }
+
+ // TODO: Refactor this to look more like AvdManager so that we don't have
+ // multiple instances in the same application, which forces us to parse
+ // the XML multiple times when we don't have to.
+ public DeviceManager(ILogger log) {
+ mLog = log;
+ }
+
+ /**
+ * Interface implemented by objects which want to know when changes occur to the {@link Device}
+ * lists.
+ */
+ public static interface DevicesChangeListener {
+ /**
+ * Called after one of the {@link Device} lists has been updated.
+ */
+ public void onDevicesChange();
+ }
+
+ /**
+ * Register a listener to be notified when the device lists are modified.
+ *
+ * @param listener The listener to add. Ignored if already registered.
+ */
+ public void registerListener(DevicesChangeListener listener) {
+ if (listener != null) {
+ synchronized (sListeners) {
+ if (!sListeners.contains(listener)) {
+ sListeners.add(listener);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from the notification list such that it will no longer receive
+ * notifications when modifications to the {@link Device} list occur.
+ *
+ * @param listener The listener to remove.
+ */
+ public boolean unregisterListener(DevicesChangeListener listener) {
+ synchronized (sListeners) {
+ return sListeners.remove(listener);
+ }
+ }
+
+ public DeviceStatus getDeviceStatus(
+ @Nullable String sdkLocation, String name, String manufacturer, int hashCode) {
+ Device d = getDevice(sdkLocation, name, manufacturer);
+ if (d == null) {
+ return DeviceStatus.MISSING;
+ } else {
+ return d.hashCode() == hashCode ? DeviceStatus.EXISTS : DeviceStatus.CHANGED;
+ }
+ }
+
+ public Device getDevice(@Nullable String sdkLocation, String name, String manufacturer) {
+ List<Device> devices;
+ if (sdkLocation != null) {
+ devices = getDevices(sdkLocation);
+ } else {
+ devices = new ArrayList<Device>(getDefaultDevices());
+ devices.addAll(getUserDevices());
+ }
+ for (Device d : devices) {
+ if (d.getName().equals(name) && d.getManufacturer().equals(manufacturer)) {
+ return d;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns both vendor provided and user created {@link Device}s.
+ *
+ * @param sdkLocation Location of the Android SDK
+ * @return A list of both vendor and user provided {@link Device}s
+ */
+ public List<Device> getDevices(String sdkLocation) {
+ List<Device> devices = new ArrayList<Device>(getVendorDevices(sdkLocation));
+ devices.addAll(getDefaultDevices());
+ devices.addAll(getUserDevices());
+ return Collections.unmodifiableList(devices);
+ }
+
+ /**
+ * Gets the {@link List} of {@link Device}s packaged with the SDK.
+ *
+ * @return The {@link List} of default {@link Device}s
+ */
+ public List<Device> getDefaultDevices() {
+ synchronized (sLock) {
+ if (mDefaultDevices == null) {
+ try {
+ mDefaultDevices = DeviceParser.parse(
+ DeviceManager.class.getResourceAsStream(SdkConstants.FN_DEVICES_XML));
+ } catch (IllegalStateException e) {
+ // The device builders can throw IllegalStateExceptions if
+ // build gets called before everything is properly setup
+ mLog.error(e, null);
+ mDefaultDevices = new ArrayList<Device>();
+ } catch (Exception e) {
+ mLog.error(null, "Error reading default devices");
+ mDefaultDevices = new ArrayList<Device>();
+ }
+ notifyListeners();
+ }
+ }
+ return Collections.unmodifiableList(mDefaultDevices);
+ }
+
+ /**
+ * Returns all vendor-provided {@link Device}s
+ *
+ * @param sdkLocation Location of the Android SDK
+ * @return A list of vendor-provided {@link Device}s
+ */
+ public List<Device> getVendorDevices(String sdkLocation) {
+ synchronized (sLock) {
+ if (mVendorDevices == null || !mVendorDevicesLocation.equals(sdkLocation)) {
+ mVendorDevicesLocation = sdkLocation;
+ List<Device> devices = new ArrayList<Device>();
+
+ // Load devices from tools folder
+ File toolsDevices = new File(sdkLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER +
+ File.separator + SdkConstants.FN_DEVICES_XML);
+ if (toolsDevices.isFile()) {
+ devices.addAll(loadDevices(toolsDevices));
+ }
+
+ // Load devices from vendor extras
+ File extrasFolder = new File(sdkLocation, SdkConstants.FD_EXTRAS);
+ List<File> deviceDirs = getExtraDirs(extrasFolder);
+ for (File deviceDir : deviceDirs) {
+ File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML);
+ if (deviceXml.isFile()) {
+ devices.addAll(loadDevices(deviceXml));
+ }
+ }
+ mVendorDevices = devices;
+ notifyListeners();
+ }
+ }
+ return Collections.unmodifiableList(mVendorDevices);
+ }
+
+ /**
+ * Returns all user-created {@link Device}s
+ *
+ * @return All user-created {@link Device}s
+ */
+ public List<Device> getUserDevices() {
+ synchronized (sLock) {
+ if (mUserDevices == null) {
+ // User devices should be saved out to
+ // $HOME/.android/devices.xml
+ mUserDevices = new ArrayList<Device>();
+ File userDevicesFile = null;
+ try {
+ userDevicesFile = new File(AndroidLocation.getFolder(),
+ SdkConstants.FN_DEVICES_XML);
+ if (userDevicesFile.exists()) {
+ mUserDevices.addAll(DeviceParser.parse(userDevicesFile));
+ notifyListeners();
+ }
+ } catch (AndroidLocationException e) {
+ mLog.warning("Couldn't load user devices: %1$s", e.getMessage());
+ } catch (SAXException e) {
+ // Probably an old config file which we don't want to overwrite.
+ if (userDevicesFile != null) {
+ String base = userDevicesFile.getAbsoluteFile() + ".old";
+ File renamedConfig = new File(base);
+ int i = 0;
+ while (renamedConfig.exists()) {
+ renamedConfig = new File(base + '.' + (i++));
+ }
+ mLog.error(null, "Error parsing %1$s, backing up to %2$s",
+ userDevicesFile.getAbsolutePath(), renamedConfig.getAbsolutePath());
+ userDevicesFile.renameTo(renamedConfig);
+ }
+ } catch (ParserConfigurationException e) {
+ mLog.error(null, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ } catch (IOException e) {
+ mLog.error(null, "Error parsing %1$s",
+ userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath());
+ }
+ }
+ }
+ return Collections.unmodifiableList(mUserDevices);
+ }
+
+ public void addUserDevice(Device d) {
+ synchronized (sLock) {
+ if (mUserDevices == null) {
+ getUserDevices();
+ }
+ mUserDevices.add(d);
+ }
+ notifyListeners();
+ }
+
+ public void removeUserDevice(Device d) {
+ synchronized (sLock) {
+ if (mUserDevices == null) {
+ getUserDevices();
+ }
+ Iterator<Device> it = mUserDevices.iterator();
+ while (it.hasNext()) {
+ Device userDevice = it.next();
+ if (userDevice.getName().equals(d.getName())
+ && userDevice.getManufacturer().equals(d.getManufacturer())) {
+ it.remove();
+ notifyListeners();
+ break;
+ }
+
+ }
+ }
+ }
+
+ public void replaceUserDevice(Device d) {
+ synchronized (sLock) {
+ if (mUserDevices == null) {
+ getUserDevices();
+ }
+ removeUserDevice(d);
+ addUserDevice(d);
+ }
+ }
+
+ /**
+ * Saves out the user devices to {@link SdkConstants#FN_DEVICES_XML} in
+ * {@link AndroidLocation#getFolder()}.
+ */
+ public void saveUserDevices() {
+ synchronized (sLock) {
+ if (mUserDevices != null && mUserDevices.size() != 0) {
+ File userDevicesFile;
+ try {
+ userDevicesFile = new File(AndroidLocation.getFolder(),
+ SdkConstants.FN_DEVICES_XML);
+ DeviceWriter.writeToXml(new FileOutputStream(userDevicesFile), mUserDevices);
+ } catch (AndroidLocationException e) {
+ mLog.warning("Couldn't find user directory: %1$s", e.getMessage());
+ } catch (FileNotFoundException e) {
+ mLog.warning("Couldn't open file: %1$s", e.getMessage());
+ } catch (ParserConfigurationException e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ } catch (TransformerFactoryConfigurationError e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ } catch (TransformerException e) {
+ mLog.warning("Error writing file: %1$s", e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns hardware properties (defined in hardware.ini) as a {@link Map}.
+ *
+ * @param s The {@link State} from which to derive the hardware properties.
+ * @return A {@link Map} of hardware properties.
+ */
+ public static Map<String, String> getHardwareProperties(State s) {
+ Hardware hw = s.getHardware();
+ Map<String, String> props = new HashMap<String, String>();
+ props.put(HardwareProperties.HW_MAINKEYS,
+ getBooleanVal(hw.getButtonType().equals(ButtonType.HARD)));
+ props.put(HardwareProperties.HW_TRACKBALL,
+ getBooleanVal(hw.getNav().equals(Navigation.TRACKBALL)));
+ props.put(HardwareProperties.HW_KEYBOARD,
+ getBooleanVal(hw.getKeyboard().equals(Keyboard.QWERTY)));
+ props.put(HardwareProperties.HW_DPAD,
+ getBooleanVal(hw.getNav().equals(Navigation.DPAD)));
+
+ Set<Sensor> sensors = hw.getSensors();
+ props.put(HardwareProperties.HW_GPS, getBooleanVal(sensors.contains(Sensor.GPS)));
+ props.put(HardwareProperties.HW_BATTERY,
+ getBooleanVal(hw.getChargeType().equals(PowerType.BATTERY)));
+ props.put(HardwareProperties.HW_ACCELEROMETER,
+ getBooleanVal(sensors.contains(Sensor.ACCELEROMETER)));
+ props.put(HardwareProperties.HW_ORIENTATION_SENSOR,
+ getBooleanVal(sensors.contains(Sensor.GYROSCOPE)));
+ props.put(HardwareProperties.HW_AUDIO_INPUT, getBooleanVal(hw.hasMic()));
+ props.put(HardwareProperties.HW_SDCARD, getBooleanVal(hw.getRemovableStorage().size() > 0));
+ props.put(HardwareProperties.HW_LCD_DENSITY,
+ Integer.toString(hw.getScreen().getPixelDensity().getDpiValue()));
+ props.put(HardwareProperties.HW_PROXIMITY_SENSOR,
+ getBooleanVal(sensors.contains(Sensor.PROXIMITY_SENSOR)));
+ return props;
+ }
+
+ /**
+ * Returns the hardware properties defined in
+ * {@link AvdManager#HARDWARE_INI} as a {@link Map}.
+ *
+ * @param d The {@link Device} from which to derive the hardware properties.
+ * @return A {@link Map} of hardware properties.
+ */
+ public static Map<String, String> getHardwareProperties(Device d) {
+ Map<String, String> props = getHardwareProperties(d.getDefaultState());
+ for (State s : d.getAllStates()) {
+ if (s.getKeyState().equals(KeyboardState.HIDDEN)) {
+ props.put("hw.keyboard.lid", getBooleanVal(true));
+ }
+ }
+ props.put(AvdManager.AVD_INI_DEVICE_HASH, Integer.toString(d.hashCode()));
+ props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getName());
+ props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer());
+ return props;
+ }
+
+ /**
+ * Takes a boolean and returns the appropriate value for
+ * {@link HardwareProperties}
+ *
+ * @param bool The boolean value to turn into the appropriate
+ * {@link HardwareProperties} value.
+ * @return {@code HardwareProperties#BOOLEAN_VALUES[0]} if true,
+ * {@code HardwareProperties#BOOLEAN_VALUES[1]} otherwise.
+ */
+ private static String getBooleanVal(boolean bool) {
+ if (bool) {
+ return HardwareProperties.BOOLEAN_VALUES[0];
+ }
+ return HardwareProperties.BOOLEAN_VALUES[1];
+ }
+
+ private Collection<Device> loadDevices(File deviceXml) {
+ try {
+ return DeviceParser.parse(deviceXml);
+ } catch (SAXException e) {
+ mLog.error(null, "Error parsing %1$s", deviceXml.getAbsolutePath());
+ } catch (ParserConfigurationException e) {
+ mLog.error(null, "Error parsing %1$s", deviceXml.getAbsolutePath());
+ } catch (IOException e) {
+ mLog.error(null, "Error reading %1$s", deviceXml.getAbsolutePath());
+ } catch (IllegalStateException e) {
+ // The device builders can throw IllegalStateExceptions if
+ // build gets called before everything is properly setup
+ mLog.error(e, null);
+ }
+ return new ArrayList<Device>();
+ }
+
+ private void notifyListeners() {
+ synchronized (sListeners) {
+ for (DevicesChangeListener listener : sListeners) {
+ listener.onDevicesChange();
+ }
+ }
+ }
+
+ /* Returns all of DeviceProfiles in the extras/ folder */
+ private List<File> getExtraDirs(File extrasFolder) {
+ List<File> extraDirs = new ArrayList<File>();
+ // All OEM provided device profiles are in
+ // $SDK/extras/$VENDOR/$ITEM/devices.xml
+ if (extrasFolder != null && extrasFolder.isDirectory()) {
+ for (File vendor : extrasFolder.listFiles()) {
+ if (vendor.isDirectory()) {
+ for (File item : vendor.listFiles()) {
+ if (item.isDirectory() && isDevicesExtra(item)) {
+ extraDirs.add(item);
+ }
+ }
+ }
+ }
+ }
+
+ return extraDirs;
+ }
+
+ /*
+ * Returns whether a specific folder for a specific vendor is a
+ * DeviceProfiles folder
+ */
+ private boolean isDevicesExtra(File item) {
+ File properties = new File(item, SdkConstants.FN_SOURCE_PROP);
+ try {
+ BufferedReader propertiesReader = new BufferedReader(new FileReader(properties));
+ try {
+ String line;
+ while ((line = propertiesReader.readLine()) != null) {
+ Matcher m = sPathPropertyPattern.matcher(line);
+ if (m.matches()) {
+ return true;
+ }
+ }
+ } finally {
+ propertiesReader.close();
+ }
+ } catch (IOException ignore) {
+ }
+ return false;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceParser.java
new file mode 100644
index 0000000..dd63af2
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceParser.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.annotations.Nullable;
+import com.android.dvlib.DeviceSchema;
+import com.android.resources.Density;
+import com.android.resources.Keyboard;
+import com.android.resources.KeyboardState;
+import com.android.resources.Navigation;
+import com.android.resources.NavigationState;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRatio;
+import com.android.resources.ScreenSize;
+import com.android.resources.TouchScreen;
+import com.android.resources.UiMode;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.awt.Point;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+public class DeviceParser {
+
+ private static class DeviceHandler extends DefaultHandler {
+ private final static String sSpaceRegex = "[\\s]+";
+ private final List<Device> mDevices = new ArrayList<Device>();
+ private final StringBuilder mStringAccumulator = new StringBuilder();
+ private final File mParentFolder;
+ private Meta mMeta;
+ private Hardware mHardware;
+ private Software mSoftware;
+ private State mState;
+ private Device.Builder mBuilder;
+ private Camera mCamera;
+ private Storage.Unit mUnit;
+
+ public DeviceHandler(@Nullable File parentFolder) {
+ mParentFolder = parentFolder;
+ }
+
+ public List<Device> getDevices() {
+ return mDevices;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes)
+ throws SAXException {
+
+ if (DeviceSchema.NODE_DEVICE.equals(localName)) {
+ // Reset everything
+ mMeta = null;
+ mHardware = null;
+ mSoftware = null;
+ mState = null;
+ mCamera = null;
+ mBuilder = new Device.Builder();
+ } else if (DeviceSchema.NODE_META.equals(localName)) {
+ mMeta = new Meta();
+ } else if (DeviceSchema.NODE_HARDWARE.equals(localName)) {
+ mHardware = new Hardware();
+ } else if (DeviceSchema.NODE_SOFTWARE.equals(localName)) {
+ mSoftware = new Software();
+ } else if (DeviceSchema.NODE_STATE.equals(localName)) {
+ mState = new State();
+ // mState can embed a Hardware instance
+ mHardware = mHardware.deepCopy();
+ String defaultState = attributes.getValue(DeviceSchema.ATTR_DEFAULT);
+ if ("true".equals(defaultState) || "1".equals(defaultState)) {
+ mState.setDefaultState(true);
+ }
+ mState.setName(attributes.getValue(DeviceSchema.ATTR_NAME).trim());
+ } else if (DeviceSchema.NODE_CAMERA.equals(localName)) {
+ mCamera = new Camera();
+ } else if (DeviceSchema.NODE_RAM.equals(localName)
+ || DeviceSchema.NODE_INTERNAL_STORAGE.equals(localName)
+ || DeviceSchema.NODE_REMOVABLE_STORAGE.equals(localName)) {
+ mUnit = Storage.Unit.getEnum(attributes.getValue(DeviceSchema.ATTR_UNIT));
+ } else if (DeviceSchema.NODE_FRAME.equals(localName)) {
+ mMeta.setFrameOffsetLandscape(new Point());
+ mMeta.setFrameOffsetPortrait(new Point());
+ } else if (DeviceSchema.NODE_SCREEN.equals(localName)) {
+ mHardware.setScreen(new Screen());
+ }
+ mStringAccumulator.setLength(0);
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) {
+ mStringAccumulator.append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String name) throws SAXException {
+ if (DeviceSchema.NODE_DEVICE.equals(localName)) {
+ mDevices.add(mBuilder.build());
+ } else if (DeviceSchema.NODE_NAME.equals(localName)) {
+ mBuilder.setName(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_MANUFACTURER.equals(localName)) {
+ mBuilder.setManufacturer(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_META.equals(localName)) {
+ mBuilder.setMeta(mMeta);
+ } else if (DeviceSchema.NODE_SOFTWARE.equals(localName)) {
+ mBuilder.addSoftware(mSoftware);
+ } else if (DeviceSchema.NODE_STATE.equals(localName)) {
+ mState.setHardware(mHardware);
+ mBuilder.addState(mState);
+ } else if (DeviceSchema.NODE_SIXTY_FOUR.equals(localName)) {
+ mMeta.setIconSixtyFour(new File(mParentFolder, getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_SIXTEEN.equals(localName)) {
+ mMeta.setIconSixteen(new File(mParentFolder, getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_PATH.equals(localName)) {
+ mMeta.setFrame(new File(mParentFolder, mStringAccumulator.toString().trim()));
+ } else if (DeviceSchema.NODE_PORTRAIT_X_OFFSET.equals(localName)) {
+ mMeta.getFrameOffsetPortrait().x = getInteger(mStringAccumulator);
+ } else if (DeviceSchema.NODE_PORTRAIT_Y_OFFSET.equals(localName)) {
+ mMeta.getFrameOffsetPortrait().y = getInteger(mStringAccumulator);
+ } else if (DeviceSchema.NODE_LANDSCAPE_X_OFFSET.equals(localName)) {
+ mMeta.getFrameOffsetLandscape().x = getInteger(mStringAccumulator);
+ } else if (DeviceSchema.NODE_LANDSCAPE_Y_OFFSET.equals(localName)) {
+ mMeta.getFrameOffsetLandscape().y = getInteger(mStringAccumulator);
+ } else if (DeviceSchema.NODE_SCREEN_SIZE.equals(localName)) {
+ mHardware.getScreen().setSize(ScreenSize.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_DIAGONAL_LENGTH.equals(localName)) {
+ mHardware.getScreen().setDiagonalLength(getDouble(mStringAccumulator));
+ } else if (DeviceSchema.NODE_PIXEL_DENSITY.equals(localName)) {
+ mHardware.getScreen().setPixelDensity(
+ Density.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_SCREEN_RATIO.equals(localName)) {
+ mHardware.getScreen().setRatio(
+ ScreenRatio.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_X_DIMENSION.equals(localName)) {
+ mHardware.getScreen().setXDimension(getInteger(mStringAccumulator));
+ } else if (DeviceSchema.NODE_Y_DIMENSION.equals(localName)) {
+ mHardware.getScreen().setYDimension(getInteger(mStringAccumulator));
+ } else if (DeviceSchema.NODE_XDPI.equals(localName)) {
+ mHardware.getScreen().setXdpi(getDouble(mStringAccumulator));
+ } else if (DeviceSchema.NODE_YDPI.equals(localName)) {
+ mHardware.getScreen().setYdpi(getDouble(mStringAccumulator));
+ } else if (DeviceSchema.NODE_MULTITOUCH.equals(localName)) {
+ mHardware.getScreen().setMultitouch(
+ Multitouch.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_MECHANISM.equals(localName)) {
+ mHardware.getScreen().setMechanism(
+ TouchScreen.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_SCREEN_TYPE.equals(localName)) {
+ mHardware.getScreen().setScreenType(
+ ScreenType.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_NETWORKING.equals(localName)) {
+ for (String n : getStringList(mStringAccumulator)) {
+ Network net = Network.getEnum(n);
+ if (net != null) {
+ mHardware.addNetwork(net);
+ }
+ }
+ } else if (DeviceSchema.NODE_SENSORS.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ Sensor sens = Sensor.getEnum(s);
+ if (sens != null) {
+ mHardware.addSensor(sens);
+ }
+ }
+ } else if (DeviceSchema.NODE_MIC.equals(localName)) {
+ mHardware.setHasMic(getBool(mStringAccumulator));
+ } else if (DeviceSchema.NODE_CAMERA.equals(localName)) {
+ mHardware.addCamera(mCamera);
+ mCamera = null;
+ } else if (DeviceSchema.NODE_LOCATION.equals(localName)) {
+ mCamera.setLocation(CameraLocation.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_AUTOFOCUS.equals(localName)) {
+ mCamera.setFlash(getBool(mStringAccumulator));
+ } else if (DeviceSchema.NODE_FLASH.equals(localName)) {
+ mCamera.setFlash(getBool(mStringAccumulator));
+ } else if (DeviceSchema.NODE_KEYBOARD.equals(localName)) {
+ mHardware.setKeyboard(Keyboard.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_NAV.equals(localName)) {
+ mHardware.setNav(Navigation.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_RAM.equals(localName)) {
+ int val = getInteger(mStringAccumulator);
+ mHardware.setRam(new Storage(val, mUnit));
+ } else if (DeviceSchema.NODE_BUTTONS.equals(localName)) {
+ mHardware.setButtonType(ButtonType.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_INTERNAL_STORAGE.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ int val = Integer.parseInt(s);
+ mHardware.addInternalStorage(new Storage(val, mUnit));
+ }
+ } else if (DeviceSchema.NODE_REMOVABLE_STORAGE.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ if (s != null && !s.isEmpty()) {
+ int val = Integer.parseInt(s);
+ mHardware.addRemovableStorage(new Storage(val, mUnit));
+ }
+ }
+ } else if (DeviceSchema.NODE_CPU.equals(localName)) {
+ mHardware.setCpu(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_GPU.equals(localName)) {
+ mHardware.setGpu(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_ABI.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ Abi abi = Abi.getEnum(s);
+ if (abi != null) {
+ mHardware.addSupportedAbi(abi);
+ }
+ }
+ } else if (DeviceSchema.NODE_DOCK.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ UiMode d = UiMode.getEnum(s);
+ if (d != null) {
+ mHardware.addSupportedUiMode(d);
+ }
+ }
+ } else if (DeviceSchema.NODE_POWER_TYPE.equals(localName)) {
+ mHardware.setChargeType(PowerType.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_API_LEVEL.equals(localName)) {
+ String val = getString(mStringAccumulator);
+ // Can be one of 5 forms:
+ // 1
+ // 1-2
+ // 1-
+ // -2
+ // -
+ int index;
+ if (val.charAt(0) == '-') {
+ if (val.length() == 1) { // -
+ mSoftware.setMinSdkLevel(0);
+ mSoftware.setMaxSdkLevel(Integer.MAX_VALUE);
+ } else { // -2
+ // Remove the front dash and any whitespace between it
+ // and the upper bound.
+ val = val.substring(1).trim();
+ mSoftware.setMinSdkLevel(0);
+ mSoftware.setMaxSdkLevel(Integer.parseInt(val));
+ }
+ } else if ((index = val.indexOf('-')) > 0) {
+ if (index == val.length() - 1) { // 1-
+ // Strip the last dash and any whitespace between it and
+ // the lower bound.
+ val = val.substring(0, val.length() - 1).trim();
+ mSoftware.setMinSdkLevel(Integer.parseInt(val));
+ mSoftware.setMaxSdkLevel(Integer.MAX_VALUE);
+ } else { // 1-2
+ String min = val.substring(0, index).trim();
+ String max = val.substring(index + 1);
+ mSoftware.setMinSdkLevel(Integer.parseInt(min));
+ mSoftware.setMaxSdkLevel(Integer.parseInt(max));
+ }
+ } else { // 1
+ int apiLevel = Integer.parseInt(val);
+ mSoftware.setMinSdkLevel(apiLevel);
+ mSoftware.setMaxSdkLevel(apiLevel);
+ }
+ } else if (DeviceSchema.NODE_LIVE_WALLPAPER_SUPPORT.equals(localName)) {
+ mSoftware.setLiveWallpaperSupport(getBool(mStringAccumulator));
+ } else if (DeviceSchema.NODE_BLUETOOTH_PROFILES.equals(localName)) {
+ for (String s : getStringList(mStringAccumulator)) {
+ BluetoothProfile profile = BluetoothProfile.getEnum(s);
+ if (profile != null) {
+ mSoftware.addBluetoothProfile(profile);
+ }
+ }
+ } else if (DeviceSchema.NODE_GL_VERSION.equals(localName)) {
+ // Guaranteed to be in the form [\d]\.[\d]
+ mSoftware.setGlVersion(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_GL_EXTENSIONS.equals(localName)) {
+ mSoftware.addAllGlExtensions(getStringList(mStringAccumulator));
+ } else if (DeviceSchema.NODE_DESCRIPTION.equals(localName)) {
+ mState.setDescription(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_SCREEN_ORIENTATION.equals(localName)) {
+ mState.setOrientation(ScreenOrientation.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_KEYBOARD_STATE.equals(localName)) {
+ mState.setKeyState(KeyboardState.getEnum(getString(mStringAccumulator)));
+ } else if (DeviceSchema.NODE_NAV_STATE.equals(localName)) {
+ // We have an extra state in our XML for nonav that
+ // NavigationState doesn't contain
+ String navState = getString(mStringAccumulator);
+ if (navState.equals("nonav")) {
+ mState.setNavState(NavigationState.HIDDEN);
+ } else {
+ mState.setNavState(NavigationState.getEnum(getString(mStringAccumulator)));
+ }
+ } else if (DeviceSchema.NODE_STATUS_BAR.equals(localName)) {
+ mSoftware.setStatusBar(getBool(mStringAccumulator));
+ }
+ }
+
+ @Override
+ public void error(SAXParseException e) throws SAXParseException {
+ throw e;
+ }
+
+ private List<String> getStringList(StringBuilder stringAccumulator) {
+ List<String> filteredStrings = new ArrayList<String>();
+ for (String s : getString(mStringAccumulator).split(sSpaceRegex)) {
+ if (s != null && !s.isEmpty()) {
+ filteredStrings.add(s.trim());
+ }
+ }
+ return filteredStrings;
+ }
+
+ private Boolean getBool(StringBuilder stringAccumulator) {
+ String b = getString(stringAccumulator);
+ return b.equalsIgnoreCase("true") || b.equalsIgnoreCase("1");
+ }
+
+ private double getDouble(StringBuilder stringAccumulator) {
+ return Double.parseDouble(getString(stringAccumulator));
+ }
+
+ private String getString(StringBuilder stringAccumulator) {
+ return stringAccumulator.toString().trim();
+ }
+
+ private int getInteger(StringBuilder stringAccumulator) {
+ return Integer.parseInt(getString(stringAccumulator));
+ }
+
+ }
+
+ private final static SAXParserFactory sParserFactory;
+
+ static {
+ sParserFactory = SAXParserFactory.newInstance();
+ sParserFactory.setNamespaceAware(true);
+ }
+
+ public static List<Device> parse(File devicesFile) throws SAXException,
+ ParserConfigurationException, IOException {
+ SAXParser parser = getParser();
+ DeviceHandler dHandler = new DeviceHandler(devicesFile.getAbsoluteFile().getParentFile());
+ parser.parse(devicesFile, dHandler);
+ return dHandler.getDevices();
+ }
+
+ public static List<Device> parse(InputStream devices) throws SAXException, IOException,
+ ParserConfigurationException {
+ SAXParser parser = getParser();
+ DeviceHandler dHandler = new DeviceHandler(null);
+ parser.parse(devices, dHandler);
+ return dHandler.getDevices();
+ }
+
+ private static SAXParser getParser() throws ParserConfigurationException, SAXException {
+ sParserFactory.setSchema(DeviceSchema.getSchema());
+ return sParserFactory.newSAXParser();
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceWriter.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceWriter.java
new file mode 100644
index 0000000..feed6d4
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/DeviceWriter.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.SdkConstants;
+import com.android.dvlib.DeviceSchema;
+import com.android.resources.UiMode;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.awt.Point;
+import java.io.OutputStream;
+import java.util.Collection;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class DeviceWriter {
+
+ public static final String LOCAL_NS = "d";
+ public static final String PREFIX = LOCAL_NS + ":";
+
+ /**
+ * Writes the XML definition of the given {@link Collection} of {@link Device}s according to
+ * {@value SdkConstants#NS_DEVICES_XSD} to the {@link OutputStream}.
+ * Note that it is up to the caller to close the {@link OutputStream}.
+ * @param out The {@link OutputStream} to write the resulting XML to.
+ * @param devices The {@link Device}s from which to generate the XML.
+ * @throws ParserConfigurationException
+ * @throws TransformerFactoryConfigurationError
+ * @throws TransformerException
+ */
+ public static void writeToXml(OutputStream out, Collection<Device> devices) throws
+ ParserConfigurationException,
+ TransformerFactoryConfigurationError,
+ TransformerException {
+ Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ Element root = doc.createElement(PREFIX + DeviceSchema.NODE_DEVICES);
+ root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":xsi",
+ XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
+ root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + LOCAL_NS, SdkConstants.NS_DEVICES_XSD);
+ doc.appendChild(root);
+
+ for (Device device : devices) {
+ Element deviceNode = doc.createElement(PREFIX + DeviceSchema.NODE_DEVICE);
+ root.appendChild(deviceNode);
+
+ Element name = doc.createElement(PREFIX + DeviceSchema.NODE_NAME);
+ name.appendChild(doc.createTextNode(device.getName()));
+ deviceNode.appendChild(name);
+
+ Element manufacturer = doc.createElement(PREFIX + DeviceSchema.NODE_MANUFACTURER);
+ manufacturer.appendChild(doc.createTextNode(device.getManufacturer()));
+ deviceNode.appendChild(manufacturer);
+
+ deviceNode.appendChild(generateMetaNode(device.getMeta(), doc));
+ deviceNode.appendChild(generateHardwareNode(device.getDefaultHardware(), doc));
+ for (Software sw : device.getAllSoftware()) {
+ deviceNode.appendChild(generateSoftwareNode(sw, doc));
+ }
+ for (State s : device.getAllStates()) {
+ deviceNode.appendChild(generateStateNode(s, doc, device.getDefaultHardware()));
+ }
+ }
+
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.INDENT, "yes");
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ DOMSource source = new DOMSource(doc);
+ StreamResult result = new StreamResult(out);
+ tf.transform(source, result);
+ }
+
+ /* This returns the XML Element for the given instance of Meta */
+ private static Node generateMetaNode(Meta meta, Document doc) {
+ Element m = doc.createElement(PREFIX + DeviceSchema.NODE_META);
+ if (meta.hasIconSixtyFour() || meta.hasIconSixteen()) {
+ Element icons = doc.createElement(PREFIX + DeviceSchema.NODE_ICONS);
+ m.appendChild(icons);
+ if (meta.hasIconSixtyFour()) {
+ addElement(doc, icons, DeviceSchema.NODE_SIXTY_FOUR,
+ meta.getIconSixtyFour().getPath());
+ }
+ if (meta.hasIconSixteen()) {
+ addElement(doc, icons, DeviceSchema.NODE_SIXTEEN, meta.getIconSixteen().getPath());
+ }
+ }
+
+ if (meta.hasFrame()) {
+ Element frame = doc.createElement(PREFIX + DeviceSchema.NODE_FRAME);
+ addElement(doc, frame, DeviceSchema.NODE_PATH, meta.getFrame().getPath());
+ Point offset = meta.getFrameOffsetPortrait();
+ addElement(doc, frame, DeviceSchema.NODE_PORTRAIT_X_OFFSET, Integer.toString(offset.x));
+ addElement(doc, frame, DeviceSchema.NODE_PORTRAIT_Y_OFFSET, Integer.toString(offset.y));
+ offset = meta.getFrameOffsetLandscape();
+ addElement(doc, frame, DeviceSchema.NODE_LANDSCAPE_X_OFFSET,
+ Integer.toString(offset.x));
+ addElement(doc, frame, DeviceSchema.NODE_LANDSCAPE_Y_OFFSET,
+ Integer.toString(offset.y));
+ }
+
+ return m;
+ }
+
+ /* This returns the XML Element for the given instance of Hardware */
+ private static Element generateHardwareNode(Hardware hw, Document doc) {
+ Screen s = hw.getScreen();
+ Element hardware = doc.createElement(PREFIX + DeviceSchema.NODE_HARDWARE);
+ Element screen = doc.createElement(PREFIX + DeviceSchema.NODE_SCREEN);
+ hardware.appendChild(screen);
+
+ addElement(doc, screen, DeviceSchema.NODE_SCREEN_SIZE, s.getSize().getResourceValue());
+ addElement(doc, screen, DeviceSchema.NODE_DIAGONAL_LENGTH,
+ String.format("%.2f",s.getDiagonalLength()));
+ addElement(doc, screen, DeviceSchema.NODE_PIXEL_DENSITY,
+ s.getPixelDensity().getResourceValue());
+ addElement(doc, screen, DeviceSchema.NODE_SCREEN_RATIO, s.getRatio().getResourceValue());
+
+ Element dimensions = doc.createElement(PREFIX + DeviceSchema.NODE_DIMENSIONS);
+ screen.appendChild(dimensions);
+
+ addElement(doc, dimensions, DeviceSchema.NODE_X_DIMENSION,
+ Integer.toString(s.getXDimension()));
+ addElement(doc, dimensions, DeviceSchema.NODE_Y_DIMENSION,
+ Integer.toString(s.getYDimension()));
+ addElement(doc, screen, DeviceSchema.NODE_XDPI, String.format("%.2f", s.getXdpi()));
+ addElement(doc, screen, DeviceSchema.NODE_YDPI, String.format("%.2f", s.getYdpi()));
+
+ Element touch = doc.createElement(PREFIX + DeviceSchema.NODE_TOUCH);
+ screen.appendChild(touch);
+
+ addElement(doc, touch, DeviceSchema.NODE_MULTITOUCH, s.getMultitouch().toString());
+ addElement(doc, touch, DeviceSchema.NODE_MECHANISM, s.getMechanism().getResourceValue());
+ addElement(doc, touch, DeviceSchema.NODE_SCREEN_TYPE, s.getScreenType().toString());
+
+ addElement(doc, hardware, DeviceSchema.NODE_NETWORKING, hw.getNetworking());
+ addElement(doc, hardware, DeviceSchema.NODE_SENSORS, hw.getSensors());
+ addElement(doc, hardware, DeviceSchema.NODE_MIC, Boolean.toString(hw.hasMic()));
+
+ for(Camera c : hw.getCameras()) {
+ Element camera = doc.createElement(PREFIX + DeviceSchema.NODE_CAMERA);
+ hardware.appendChild(camera);
+ addElement(doc, camera, DeviceSchema.NODE_LOCATION, c.getLocation().toString());
+ addElement(doc, camera, DeviceSchema.NODE_AUTOFOCUS,
+ Boolean.toString(c.hasAutofocus()));
+ addElement(doc, camera, DeviceSchema.NODE_FLASH, Boolean.toString(c.hasFlash()));
+ }
+
+ addElement(doc, hardware, DeviceSchema.NODE_KEYBOARD, hw.getKeyboard().getResourceValue());
+ addElement(doc, hardware, DeviceSchema.NODE_NAV, hw.getNav().getResourceValue());
+
+ Storage.Unit unit = hw.getRam().getApproriateUnits();
+ Element ram = addElement(doc, hardware, DeviceSchema.NODE_RAM,
+ Long.toString(hw.getRam().getSizeAsUnit(unit)));
+ ram.setAttribute(DeviceSchema.ATTR_UNIT, unit.toString());
+
+ addElement(doc, hardware, DeviceSchema.NODE_BUTTONS, hw.getButtonType().toString());
+ addStorageElement(doc, hardware, DeviceSchema.NODE_INTERNAL_STORAGE,
+ hw.getInternalStorage());
+ addStorageElement(doc, hardware, DeviceSchema.NODE_REMOVABLE_STORAGE,
+ hw.getRemovableStorage());
+ addElement(doc, hardware, DeviceSchema.NODE_CPU, hw.getCpu());
+ addElement(doc, hardware, DeviceSchema.NODE_GPU, hw.getGpu());
+ addElement(doc, hardware, DeviceSchema.NODE_ABI, hw.getSupportedAbis());
+
+ StringBuilder sb = new StringBuilder();
+ for (UiMode u : hw.getSupportedUiModes()) {
+ sb.append("\n" + u.getResourceValue());
+ }
+ addElement(doc, hardware, DeviceSchema.NODE_DOCK, sb.toString());
+
+ addElement(doc, hardware, DeviceSchema.NODE_POWER_TYPE, hw.getChargeType().toString());
+
+ return hardware;
+ }
+
+ /* This returns the XML Element for the given instance of Software */
+ private static Element generateSoftwareNode(Software sw, Document doc) {
+ Element software = doc.createElement(PREFIX + DeviceSchema.NODE_SOFTWARE);
+
+ String apiVersion = "";
+ if (sw.getMinSdkLevel() != 0) {
+ apiVersion += Integer.toString(sw.getMinSdkLevel());
+ }
+ apiVersion += "-";
+ if (sw.getMaxSdkLevel() != Integer.MAX_VALUE) {
+ apiVersion += Integer.toString(sw.getMaxSdkLevel());
+ }
+ addElement(doc, software, DeviceSchema.NODE_API_LEVEL, apiVersion);
+ addElement(doc, software, DeviceSchema.NODE_LIVE_WALLPAPER_SUPPORT,
+ Boolean.toString(sw.hasLiveWallpaperSupport()));
+ addElement(doc, software, DeviceSchema.NODE_BLUETOOTH_PROFILES, sw.getBluetoothProfiles());
+ addElement(doc, software, DeviceSchema.NODE_GL_VERSION, sw.getGlVersion());
+ addElement(doc, software, DeviceSchema.NODE_GL_EXTENSIONS, sw.getGlExtensions());
+ addElement(doc, software, DeviceSchema.NODE_STATUS_BAR,
+ Boolean.toString(sw.hasStatusBar()));
+
+ return software;
+ }
+
+ /* This returns the XML Element for the given instance of State */
+ private static Element generateStateNode(State s, Document doc, Hardware defaultHardware) {
+ Element state = doc.createElement(PREFIX + DeviceSchema.NODE_STATE);
+ state.setAttribute(DeviceSchema.ATTR_NAME, s.getName());
+ if (s.isDefaultState()) {
+ state.setAttribute(DeviceSchema.ATTR_DEFAULT, Boolean.toString(s.isDefaultState()));
+ }
+ addElement(doc, state, DeviceSchema.NODE_DESCRIPTION, s.getDescription());
+ addElement(doc, state, DeviceSchema.NODE_SCREEN_ORIENTATION,
+ s.getOrientation().getResourceValue());
+ addElement(doc, state, DeviceSchema.NODE_KEYBOARD_STATE,
+ s.getKeyState().getResourceValue());
+ addElement(doc, state, DeviceSchema.NODE_NAV_STATE, s.getNavState().getResourceValue());
+
+ // Only if the hardware is different do we want to append hardware values
+ if (!s.getHardware().equals(defaultHardware)){
+ // TODO: Only append nodes which are different from the default hardware
+ Element hardware = generateHardwareNode(s.getHardware(), doc);
+ NodeList children = hardware.getChildNodes();
+ for (int i = 0 ; i < children.getLength(); i++) {
+ Node child = children.item(i);
+ state.appendChild(child);
+ }
+ }
+ return state;
+ }
+
+ private static Element addElement(Document doc, Element parent, String tag, String content) {
+ Element child = doc.createElement(PREFIX + tag);
+ child.appendChild(doc.createTextNode(content));
+ parent.appendChild(child);
+ return child;
+ }
+
+ private static Element addElement(Document doc, Element parent, String tag,
+ Collection<? extends Object> content) {
+ StringBuilder sb = new StringBuilder();
+ for (Object o : content) {
+ sb.append("\n" + o.toString());
+ }
+ return addElement(doc, parent, tag, sb.toString());
+ }
+
+ /* This adds generates the XML for a Collection<Storage> and appends it to the parent. Note
+ * that it picks the proper unit for the unit attribute and sets it on the node.
+ */
+ private static Element addStorageElement(Document doc, Element parent, String tag,
+ Collection<Storage> content){
+ Storage.Unit unit = Storage.Unit.TiB;
+
+ // Get the lowest common unit (so if one piece of storage is 128KiB and another is 1MiB,
+ // use KiB for units)
+ for(Storage storage : content) {
+ if(storage.getApproriateUnits().getNumberOfBytes() < unit.getNumberOfBytes()) {
+ unit = storage.getApproriateUnits();
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for(Storage storage : content) {
+ sb.append("\n" + storage.getSizeAsUnit(unit));
+ }
+ Element storage = addElement(doc, parent, tag, sb.toString());
+ storage.setAttribute(DeviceSchema.ATTR_UNIT, unit.toString());
+ return storage;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Hardware.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Hardware.java
new file mode 100644
index 0000000..b12f11d
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Hardware.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.resources.Keyboard;
+import com.android.resources.Navigation;
+import com.android.resources.UiMode;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Hardware {
+ private Screen mScreen;
+ private Set<Network> mNetworking = new HashSet<Network>();
+ private Set<Sensor> mSensors = new HashSet<Sensor>();
+ private boolean mMic;
+ private List<Camera> mCameras = new ArrayList<Camera>();
+ private Keyboard mKeyboard;
+ private Navigation mNav;
+ private Storage mRam;
+ private ButtonType mButtons;
+ private List<Storage> mInternalStorage = new ArrayList<Storage>();
+ private List<Storage> mRemovableStorage = new ArrayList<Storage>();
+ private String mCpu;
+ private String mGpu;
+ private Set<Abi> mAbis = new HashSet<Abi>();
+ private Set<UiMode> mUiModes = new HashSet<UiMode>();
+ private PowerType mPluggedIn;
+
+ public Set<Network> getNetworking() {
+ return mNetworking;
+ }
+
+ public void addNetwork(Network n) {
+ mNetworking.add(n);
+ }
+
+ public void addAllNetworks(Collection<Network> ns) {
+ mNetworking.addAll(ns);
+ }
+
+ public Set<Sensor> getSensors() {
+ return mSensors;
+ }
+
+ public void addSensor(Sensor sensor) {
+ mSensors.add(sensor);
+ }
+
+ public void addAllSensors(Collection<Sensor> sensors) {
+ mSensors.addAll(sensors);
+ }
+
+ public boolean hasMic() {
+ return mMic;
+ }
+
+ public void setHasMic(boolean hasMic) {
+ mMic = hasMic;
+ }
+
+ public List<Camera> getCameras() {
+ return mCameras;
+ }
+
+ public void addCamera(Camera c) {
+ mCameras.add(c);
+ }
+
+ public void addAllCameras(Collection<Camera> cs) {
+ mCameras.addAll(cs);
+ }
+
+ public Camera getCamera(int i) {
+ return mCameras.get(i);
+ }
+
+ public Camera getCamera(CameraLocation location) {
+ for (Camera c : mCameras) {
+ if (location.equals(c.getLocation())) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ public Keyboard getKeyboard() {
+ return mKeyboard;
+ }
+
+ public void setKeyboard(Keyboard k) {
+ mKeyboard = k;
+ }
+
+ public Navigation getNav() {
+ return mNav;
+ }
+
+ public void setNav(Navigation n) {
+ mNav = n;
+ }
+
+ public Storage getRam() {
+ return mRam;
+ }
+
+ public void setRam(Storage ram) {
+ mRam = ram;
+ }
+
+ public ButtonType getButtonType() {
+ return mButtons;
+ }
+
+ public void setButtonType(ButtonType bt) {
+ mButtons = bt;
+ }
+
+ public List<Storage> getInternalStorage() {
+ return mInternalStorage;
+ }
+
+ public void addInternalStorage(Storage is) {
+ mInternalStorage.add(is);
+ }
+
+ public void addAllInternalStorage(Collection<Storage> is) {
+ mInternalStorage.addAll(is);
+ }
+
+ public List<Storage> getRemovableStorage() {
+ return mRemovableStorage;
+ }
+
+ public void addRemovableStorage(Storage rs) {
+ mRemovableStorage.add(rs);
+ }
+
+ public void addAllRemovableStorage(Collection<Storage> rs) {
+ mRemovableStorage.addAll(rs);
+ }
+
+ public String getCpu() {
+ return mCpu;
+ }
+
+ public void setCpu(String cpuName) {
+ mCpu = cpuName;
+ }
+
+ public String getGpu() {
+ return mGpu;
+ }
+
+ public void setGpu(String gpuName) {
+ mGpu = gpuName;
+ }
+
+ public Set<Abi> getSupportedAbis() {
+ return mAbis;
+ }
+
+ public void addSupportedAbi(Abi abi) {
+ mAbis.add(abi);
+ }
+
+ public void addAllSupportedAbis(Collection<Abi> abis) {
+ mAbis.addAll(abis);
+ }
+
+ public Set<UiMode> getSupportedUiModes() {
+ return mUiModes;
+ }
+
+ public void addSupportedUiMode(UiMode uiMode) {
+ mUiModes.add(uiMode);
+ }
+
+ public void addAllSupportedUiModes(Collection<UiMode> uiModes) {
+ mUiModes.addAll(uiModes);
+ }
+
+ public PowerType getChargeType() {
+ return mPluggedIn;
+ }
+
+ public void setChargeType(PowerType chargeType) {
+ mPluggedIn = chargeType;
+ }
+
+ public Screen getScreen() {
+ return mScreen;
+ }
+
+ public void setScreen(Screen s) {
+ mScreen = s;
+ }
+
+ /**
+ * Returns a copy of the object that shares no state with it,
+ * but is initialized to equivalent values.
+ *
+ * @return A copy of the object.
+ */
+ public Hardware deepCopy() {
+ Hardware hw = new Hardware();
+ hw.mScreen = mScreen.deepCopy();
+ hw.mNetworking = new HashSet<Network>(mNetworking);
+ hw.mSensors = new HashSet<Sensor>(mSensors);
+ // Get the constant boolean value
+ hw.mMic = mMic;
+ hw.mCameras = new ArrayList<Camera>();
+ for (Camera c : mCameras) {
+ hw.mCameras.add(c.deepCopy());
+ }
+ hw.mKeyboard = mKeyboard;
+ hw.mNav = mNav;
+ hw.mRam = mRam.deepCopy();
+ hw.mButtons = mButtons;
+ hw.mInternalStorage = new ArrayList<Storage>();
+ for (Storage s : mInternalStorage) {
+ hw.mInternalStorage.add(s.deepCopy());
+ }
+ hw.mRemovableStorage = new ArrayList<Storage>();
+ for (Storage s : mRemovableStorage) {
+ hw.mRemovableStorage.add(s.deepCopy());
+ }
+ hw.mCpu = mCpu;
+ hw.mGpu = mGpu;
+ hw.mAbis = new HashSet<Abi>(mAbis);
+ hw.mUiModes = new HashSet<UiMode>(mUiModes);
+ hw.mPluggedIn = mPluggedIn;
+ return hw;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Hardware)) {
+ return false;
+ }
+ Hardware hw = (Hardware) o;
+ return mScreen.equals(hw.getScreen())
+ && mNetworking.equals(hw.getNetworking())
+ && mSensors.equals(hw.getSensors())
+ && mMic == hw.hasMic()
+ && mCameras.equals(hw.getCameras())
+ && mKeyboard == hw.getKeyboard()
+ && mNav == hw.getNav()
+ && mRam.equals(hw.getRam())
+ && mButtons == hw.getButtonType()
+ && mInternalStorage.equals(hw.getInternalStorage())
+ && mRemovableStorage.equals(hw.getRemovableStorage())
+ && mCpu.equals(hw.getCpu())
+ && mGpu.equals(hw.getGpu())
+ && mAbis.equals(hw.getSupportedAbis())
+ && mUiModes.equals(hw.getSupportedUiModes())
+ && mPluggedIn == hw.getChargeType();
+
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + mScreen.hashCode();
+
+ // Since sets have no defined order, we need to hash them in such a way that order doesn't
+ // matter.
+ int temp = 0;
+ for (Network n : mNetworking) {
+ temp |= 1 << n.ordinal();
+ }
+ hash = 31 * hash + temp;
+
+ temp = 0;
+ for (Sensor s : mSensors) {
+ temp |= 1 << s.ordinal();
+ }
+
+ hash = 31 * hash + temp;
+ hash = 31 * hash + (mMic ? 1 : 0);
+ hash = mCameras.hashCode();
+ hash = 31 * hash + mKeyboard.ordinal();
+ hash = 31 * hash + mNav.ordinal();
+ hash = 31 * hash + mRam.hashCode();
+ hash = 31 * hash + mButtons.ordinal();
+ hash = 31 * hash + mInternalStorage.hashCode();
+ hash = 31 * hash + mRemovableStorage.hashCode();
+
+ for (Character c : mCpu.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+
+ for (Character c : mGpu.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+
+ temp = 0;
+ for (Abi a : mAbis) {
+ temp |= 1 << a.ordinal();
+ }
+ hash = 31 * hash + temp;
+
+ temp = 0;
+ for (UiMode ui : mUiModes) {
+ temp |= 1 << ui.ordinal();
+ }
+ hash = 31 * hash + temp;
+
+ return hash;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Meta.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Meta.java
new file mode 100644
index 0000000..4c19f3f
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Meta.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import java.awt.Point;
+import java.io.File;
+
+public class Meta {
+ private File mIconSixtyFour;
+ private File mIconSixteen;
+ private File mFrame;
+ private Point mFrameOffsetLandscape;
+ private Point mFrameOffsetPortrait;
+
+ public File getIconSixtyFour() {
+ return mIconSixtyFour;
+ }
+
+ public void setIconSixtyFour(File iconSixtyFour) {
+ mIconSixtyFour = iconSixtyFour;
+ }
+
+ public boolean hasIconSixtyFour() {
+ if (mIconSixtyFour != null && mIconSixtyFour.isFile()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public File getIconSixteen() {
+ return mIconSixteen;
+ }
+
+ public void setIconSixteen(File iconSixteen) {
+ mIconSixteen = iconSixteen;
+ }
+
+ public boolean hasIconSixteen() {
+ if (mIconSixteen != null && mIconSixteen.isFile()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public File getFrame() {
+ return mFrame;
+ }
+
+ public void setFrame(File frame) {
+ mFrame = frame;
+ }
+
+ public boolean hasFrame() {
+ if (mFrame != null && mFrame.isFile()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public Point getFrameOffsetLandscape() {
+ return mFrameOffsetLandscape;
+ }
+
+ public void setFrameOffsetLandscape(Point offset) {
+ mFrameOffsetLandscape = offset;
+ }
+
+ public Point getFrameOffsetPortrait() {
+ return mFrameOffsetPortrait;
+ }
+
+ public void setFrameOffsetPortrait(Point offset) {
+ mFrameOffsetPortrait = offset;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Meta)) {
+ return false;
+ }
+ Meta m = (Meta) o;
+
+ // Note that any of the fields of either object can be null
+ if (mIconSixtyFour != null && !mIconSixtyFour.equals(m.getIconSixtyFour())){
+ return false;
+ } else if (m.getIconSixtyFour() != null && !m.getIconSixtyFour().equals(mIconSixtyFour)) {
+ return false;
+ }
+
+ if (mIconSixteen != null && !mIconSixteen.equals(m.getIconSixteen())){
+ return false;
+ } else if (m.getIconSixteen() != null && !m.getIconSixteen().equals(mIconSixteen)) {
+ return false;
+ }
+
+ if (mFrame != null && !mFrame.equals(m.getFrame())) {
+ return false;
+ } else if (m.getFrame() != null && !m.getFrame().equals(mFrame)) {
+ return false;
+ }
+
+ if (mFrameOffsetLandscape != null
+ && !mFrameOffsetLandscape.equals(m.getFrameOffsetLandscape())){
+ return false;
+ } else if (m.getFrameOffsetLandscape() != null
+ && !m.getFrameOffsetLandscape().equals(mFrameOffsetLandscape)){
+ return false;
+ }
+
+
+ if (mFrameOffsetPortrait != null
+ && !mFrameOffsetPortrait.equals(m.getFrameOffsetPortrait())){
+ return false;
+ } else if (m.getFrameOffsetPortrait() != null
+ && !m.getFrameOffsetPortrait().equals(mFrameOffsetPortrait)){
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ if(mIconSixteen != null){
+ for (Character c : mIconSixteen.getAbsolutePath().toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ }
+ if(mIconSixtyFour != null){
+ for (Character c : mIconSixtyFour.getAbsolutePath().toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ }
+ if(mFrame != null){
+ for (Character c : mFrame.getAbsolutePath().toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ }
+ if(mFrameOffsetLandscape != null){
+ hash = 31 * hash + mFrameOffsetLandscape.x;
+ hash = 31 * hash + mFrameOffsetLandscape.y;
+ }
+ if(mFrameOffsetPortrait != null){
+ hash = 31 * hash + mFrameOffsetPortrait.x;
+ hash = 31 * hash + mFrameOffsetPortrait.y;
+ }
+ return hash;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Multitouch.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Multitouch.java
new file mode 100644
index 0000000..bfd4618
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Multitouch.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum Multitouch {
+ NONE("none"),
+ BASIC("basic"),
+ DISTINCT("distinct"),
+ JAZZ_HANDS("jazz-hands");
+
+ private final String mValue;
+
+ private Multitouch(String value){
+ mValue = value;
+ }
+
+ public static Multitouch getEnum(String val){
+ for (Multitouch m : values()) {
+ if (m.mValue.equals(val)) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Network.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Network.java
new file mode 100644
index 0000000..df84b44
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Network.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum Network {
+ BLUETOOTH("Bluetooth"),
+ WIFI("Wifi"),
+ NFC("NFC");
+
+ private final String mValue;
+
+ private Network(String value) {
+ mValue = value;
+ }
+
+ public static Network getEnum(String value) {
+ for (Network n : values()) {
+ if (n.mValue.equals(value)) {
+ return n;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString(){
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/PowerType.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/PowerType.java
new file mode 100644
index 0000000..e38ba28
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/PowerType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum PowerType {
+ PLUGGEDIN("plugged-in"),
+ BATTERY("battery");
+
+ private final String mValue;
+
+ private PowerType(String value) {
+ mValue = value;
+ }
+
+ public static PowerType getEnum(String value) {
+ for (PowerType c : values()) {
+ if (c.mValue.equals(value)) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Screen.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Screen.java
new file mode 100644
index 0000000..a7f4334
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Screen.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.resources.Density;
+import com.android.resources.ScreenRatio;
+import com.android.resources.ScreenSize;
+import com.android.resources.TouchScreen;
+
+
+public class Screen {
+ private ScreenSize mScreenSize;
+ private double mDiagonalLength;
+ private Density mPixelDensity;
+ private ScreenRatio mScreenRatio;
+ private int mXDimension;
+ private int mYDimension;
+ private double mXdpi;
+ private double mYdpi;
+ private Multitouch mMultitouch;
+ private TouchScreen mMechanism;
+ private ScreenType mScreenType;
+
+ public ScreenSize getSize() {
+ return mScreenSize;
+ }
+
+ public void setSize(ScreenSize s) {
+ mScreenSize = s;
+ }
+
+ public double getDiagonalLength() {
+ return mDiagonalLength;
+ }
+
+ public void setDiagonalLength(double diagonalLength) {
+ mDiagonalLength = diagonalLength;
+ }
+
+ public Density getPixelDensity() {
+ return mPixelDensity;
+ }
+
+ public void setPixelDensity(Density pDensity) {
+ mPixelDensity = pDensity;
+ }
+
+ public ScreenRatio getRatio() {
+ return mScreenRatio;
+ }
+
+ public void setRatio(ScreenRatio ratio) {
+ mScreenRatio = ratio;
+ }
+
+ public int getXDimension() {
+ return mXDimension;
+ }
+
+ public void setXDimension(int xDimension) {
+ mXDimension = xDimension;
+ }
+
+ public int getYDimension() {
+ return mYDimension;
+ }
+
+ public void setYDimension(int yDimension) {
+ mYDimension = yDimension;
+ }
+
+ public double getXdpi() {
+ return mXdpi;
+ }
+
+ public void setXdpi(double xdpi) {
+ mXdpi = xdpi;
+ }
+
+ public double getYdpi() {
+ return mYdpi;
+ }
+
+ public void setYdpi(double ydpi) {
+ mYdpi = ydpi;
+ }
+
+ public Multitouch getMultitouch() {
+ return mMultitouch;
+ }
+
+ public void setMultitouch(Multitouch m) {
+ mMultitouch = m;
+ }
+
+ public TouchScreen getMechanism() {
+ return mMechanism;
+ }
+
+ public void setMechanism(TouchScreen mechanism) {
+ mMechanism = mechanism;
+ }
+
+ public ScreenType getScreenType() {
+ return mScreenType;
+ }
+
+ public void setScreenType(ScreenType screenType) {
+ mScreenType = screenType;
+ }
+
+ /**
+ * Returns a copy of the object that shares no state with it,
+ * but is initialized to equivalent values.
+ *
+ * @return A copy of the object.
+ */
+ public Screen deepCopy() {
+ Screen s = new Screen();
+ s.mScreenSize = mScreenSize;
+ s.mDiagonalLength = mDiagonalLength;
+ s.mPixelDensity = mPixelDensity;
+ s.mScreenRatio = mScreenRatio;
+ s.mXDimension = mXDimension;
+ s.mYDimension = mYDimension;
+ s.mXdpi = mXdpi;
+ s.mYdpi = mYdpi;
+ s.mMultitouch = mMultitouch;
+ s.mMechanism = mMechanism;
+ s.mScreenType = mScreenType;
+ return s;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Screen)) {
+ return false;
+ }
+ Screen s = (Screen) o;
+ return s.mScreenSize == mScreenSize
+ && s.mDiagonalLength == mDiagonalLength
+ && s.mPixelDensity == mPixelDensity
+ && s.mScreenRatio == mScreenRatio
+ && s.mXDimension == mXDimension
+ && s.mYDimension == mYDimension
+ && s.mXdpi == mXdpi
+ && s.mYdpi == mYdpi
+ && s.mMultitouch == mMultitouch
+ && s.mMechanism == mMechanism
+ && s.mScreenType == mScreenType;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + mScreenSize.ordinal();
+ long f = Double.doubleToLongBits(mDiagonalLength);
+ hash = 31 * hash + (int) (f ^ (f >>> 32));
+ hash = 31 * hash + mPixelDensity.ordinal();
+ hash = 31 * hash + mScreenRatio.ordinal();
+ hash = 31 * hash + mXDimension;
+ hash = 31 * hash + mYDimension;
+ f = Double.doubleToLongBits(mXdpi);
+ hash = 31 * hash + (int) (f ^ (f >>> 32));
+ f = Double.doubleToLongBits(mYdpi);
+ hash = 31 * hash + (int) (f ^ (f >>> 32));
+ hash = 31 * hash + mMultitouch.ordinal();
+ hash = 31 * hash + mMechanism.ordinal();
+ hash = 31 * hash + mScreenType.ordinal();
+ return hash;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ScreenType.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ScreenType.java
new file mode 100644
index 0000000..21d6005
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/ScreenType.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum ScreenType {
+ CAPACITIVE("capacitive"),
+ RESISTIVE("resistive"),
+ NOTOUCH("notouch");
+
+ private final String mValue;
+
+ private ScreenType(String value) {
+ mValue = value;
+ }
+
+ public static ScreenType getEnum(String value) {
+ for (ScreenType s : values()) {
+ if (s.mValue.equals(value)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Sensor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Sensor.java
new file mode 100644
index 0000000..3fc3e14
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Sensor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public enum Sensor {
+ ACCELEROMETER("Accelerometer"),
+ BAROMETER("Barometer"),
+ COMPASS("Compass"),
+ GPS("GPS"),
+ GYROSCOPE("Gyroscope"),
+ LIGHT_SENSOR("LightSensor"),
+ PROXIMITY_SENSOR("ProximitySensor");
+
+ private final String mValue;
+
+ private Sensor(String value) {
+ mValue = value;
+ }
+
+ public static Sensor getEnum(String value) {
+ for (Sensor s : values()) {
+ if (s.mValue.equals(value)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString(){
+ return mValue;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Software.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Software.java
new file mode 100644
index 0000000..58f13b0
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Software.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Software {
+ private int mMinSdkLevel = 0;
+ private int mMaxSdkLevel = Integer.MAX_VALUE;
+ private boolean mLiveWallpaperSupport;
+ private Set<BluetoothProfile> mBluetoothProfiles = new HashSet<BluetoothProfile>();
+ private String mGlVersion;
+ private Set<String> mGlExtensions = new HashSet<String>();
+ private boolean mStatusBar;
+
+ public int getMinSdkLevel() {
+ return mMinSdkLevel;
+ }
+
+ public void setMinSdkLevel(int sdkLevel) {
+ mMinSdkLevel = sdkLevel;
+ }
+
+ public int getMaxSdkLevel() {
+ return mMaxSdkLevel;
+ }
+
+ public void setMaxSdkLevel(int sdkLevel) {
+ mMaxSdkLevel = sdkLevel;
+ }
+
+ public boolean hasLiveWallpaperSupport() {
+ return mLiveWallpaperSupport;
+ }
+
+ public void setLiveWallpaperSupport(boolean liveWallpaperSupport) {
+ mLiveWallpaperSupport = liveWallpaperSupport;
+ }
+
+ public Set<BluetoothProfile> getBluetoothProfiles() {
+ return mBluetoothProfiles;
+ }
+
+ public void addBluetoothProfile(BluetoothProfile bp) {
+ mBluetoothProfiles.add(bp);
+ }
+
+ public void addAllBluetoothProfiles(Collection<BluetoothProfile> bps) {
+ mBluetoothProfiles.addAll(bps);
+ }
+
+ public String getGlVersion() {
+ return mGlVersion;
+ }
+
+ public void setGlVersion(String version) {
+ mGlVersion = version;
+ }
+
+ public Set<String> getGlExtensions() {
+ return mGlExtensions;
+ }
+
+ public void addGlExtension(String extension) {
+ mGlExtensions.add(extension);
+ }
+
+ public void addAllGlExtensions(Collection<String> extensions) {
+ mGlExtensions.addAll(extensions);
+ }
+
+ public void setStatusBar(boolean hasBar) {
+ mStatusBar = hasBar;
+ }
+
+ public boolean hasStatusBar() {
+ return mStatusBar;
+ }
+
+ public Software deepCopy() {
+ Software s = new Software();
+ s.setMinSdkLevel(getMinSdkLevel());
+ s.setMaxSdkLevel(getMaxSdkLevel());
+ s.setLiveWallpaperSupport(hasLiveWallpaperSupport());
+ s.addAllBluetoothProfiles(getBluetoothProfiles());
+ s.setGlVersion(getGlVersion());
+ s.addAllGlExtensions(getGlExtensions());
+ s.setStatusBar(hasStatusBar());
+ return s;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof Software)) {
+ return false;
+ }
+
+ Software sw = (Software) o;
+ return mMinSdkLevel == sw.getMinSdkLevel()
+ && mMaxSdkLevel == sw.getMaxSdkLevel()
+ && mLiveWallpaperSupport == sw.hasLiveWallpaperSupport()
+ && mBluetoothProfiles.equals(sw.getBluetoothProfiles())
+ && mGlVersion.equals(sw.getGlVersion())
+ && mGlExtensions.equals(sw.getGlExtensions())
+ && mStatusBar == sw.hasStatusBar();
+ }
+
+ @Override
+ /** A stable hash across JVM instances */
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + mMinSdkLevel;
+ hash = 31 * hash + mMaxSdkLevel;
+ hash = 31 * hash + (mLiveWallpaperSupport ? 1 : 0);
+ for (BluetoothProfile bp : mBluetoothProfiles) {
+ hash = 31 * hash + bp.ordinal();
+ }
+ for (Character c : mGlVersion.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ for (String glExtension : mGlExtensions) {
+ for (Character c : glExtension.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ }
+ hash = 31 * hash + (mStatusBar ? 1 : 0);
+ return hash;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java
new file mode 100644
index 0000000..27e5448
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/State.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+import com.android.resources.KeyboardState;
+import com.android.resources.NavigationState;
+import com.android.resources.ScreenOrientation;
+
+public class State {
+ private boolean mDefaultState;
+ private String mName;
+ private String mDescription;
+ private ScreenOrientation mOrientation;
+ private KeyboardState mKeyState;
+ private NavigationState mNavState;
+ private Hardware mHardwareOverride;
+
+ public boolean isDefaultState() {
+ return mDefaultState;
+ }
+
+ public void setDefaultState(boolean defaultState) {
+ mDefaultState = defaultState;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public void setDescription(String description) {
+ mDescription = description;
+ }
+
+ public ScreenOrientation getOrientation() {
+ return mOrientation;
+ }
+
+ public void setOrientation(ScreenOrientation orientation) {
+ mOrientation = orientation;
+ }
+
+ public KeyboardState getKeyState() {
+ return mKeyState;
+ }
+
+ public void setKeyState(KeyboardState keyState) {
+ mKeyState = keyState;
+ }
+
+ public NavigationState getNavState() {
+ return mNavState;
+ }
+
+ public void setNavState(NavigationState navState) {
+ mNavState = navState;
+ }
+
+ public Hardware getHardware() {
+ return mHardwareOverride;
+ }
+
+ public void setHardware(Hardware hw) {
+ mHardwareOverride = hw;
+ }
+
+ /**
+ * Returns a copy of the object that shares no state with it,
+ * but is initialized to equivalent values.
+ *
+ * @return A copy of the object.
+ */
+ public State deepCopy() {
+ State s = new State();
+ s.setDefaultState(isDefaultState());
+ s.setName(getName());
+ s.setDescription(getDescription());
+ s.setOrientation(getOrientation());
+ s.setKeyState(getKeyState());
+ s.setNavState(getNavState());
+ s.setHardware(getHardware().deepCopy());
+ return s;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof State)) {
+ return false;
+ }
+ State s = (State) o;
+ return mDefaultState == s.isDefaultState()
+ && mName.equals(s.getName())
+ && mDescription.equals(s.getDescription())
+ && mOrientation.equals(s.getOrientation())
+ && mKeyState.equals(s.getKeyState())
+ && mNavState.equals(s.getNavState())
+ && mHardwareOverride.equals(s.getHardware());
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + (mDefaultState ? 1 : 0);
+ for (Character c : mName.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ for (Character c : mDescription.toCharArray()) {
+ hash = 31 * hash + c;
+ }
+ hash = 31 * hash + mOrientation.ordinal();
+ hash = 31 * hash + mKeyState.ordinal();
+ hash = 31 * hash + mNavState.ordinal();
+ hash = 31 * hash + mHardwareOverride.hashCode();
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Storage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Storage.java
new file mode 100644
index 0000000..b30fe6e
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/Storage.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 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.devices;
+
+public class Storage {
+ private long mNoBytes;
+
+ public Storage(long amount, Unit unit){
+ mNoBytes = amount * unit.getNumberOfBytes();
+ }
+
+ public Storage(long amount){
+ this(amount, Unit.B);
+ }
+
+ /** Returns the amount of storage represented, in Bytes */
+ public long getSize() {
+ return getSizeAsUnit(Unit.B);
+ }
+
+ public Storage deepCopy() {
+ return new Storage(mNoBytes);
+ }
+
+ /**
+ * Return the amount of storage represented by the instance in the given unit
+ * @param unit The unit of the result.
+ * @return The size of the storage in the given unit.
+ */
+ public long getSizeAsUnit(Unit unit) {
+ return mNoBytes / unit.getNumberOfBytes();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this){
+ return true;
+ }
+
+ if (!(o instanceof Storage)) {
+ return false;
+ }
+
+ Storage s = (Storage) o;
+ if (s.getSize() == this.getSize()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ return 31 * result + (int) (mNoBytes^(mNoBytes>>>32));
+ }
+
+ public enum Unit{
+ B("B", 1),
+ KiB("KiB", 1024),
+ MiB("MiB", 1024 * 1024),
+ GiB("GiB", 1024 * 1024 * 1024),
+ TiB("TiB", 1024l * 1024l * 1024l * 1024l);
+
+ private String mValue;
+ /** The number of bytes needed to have one of the given unit */
+ private long mNoBytes;
+
+ private Unit(String val, long noBytes) {
+ mValue = val;
+ mNoBytes = noBytes;
+ }
+
+ public static Unit getEnum(String val) {
+ for(Unit v : values()){
+ if(v.mValue.equals(val)) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ public long getNumberOfBytes() {
+ return mNoBytes;
+ }
+
+ @Override
+ public String toString() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Finds the largest {@link Unit} which can display the storage value as a positive integer
+ * with no loss of accuracy.
+ * @return The most appropriate {@link Unit}.
+ */
+ public Unit getApproriateUnits() {
+ Unit optimalUnit = Unit.B;
+ for(Unit unit : Unit.values()) {
+ if(mNoBytes % unit.getNumberOfBytes() == 0) {
+ optimalUnit = unit;
+ } else {
+ break;
+ }
+ }
+ return optimalUnit;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/devices.xml b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/devices.xml
new file mode 100644
index 0000000..e18280d
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/devices/devices.xml
@@ -0,0 +1,1412 @@
+<?xml version="1.0"?>
+<d:devices
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:d="http://schemas.android.com/sdk/devices/1">
+
+ <d:device>
+ <d:name>
+ 2.7in QVGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>small</d:screen-size>
+ <d:diagonal-length>2.7</d:diagonal-length>
+ <d:pixel-density>ldpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>240</d:x-dimension>
+ <d:y-dimension>320</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>145</d:xdpi>
+ <d:ydpi>145</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 2.7in QVGA slider
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>small</d:screen-size>
+ <d:diagonal-length>2.7</d:diagonal-length>
+ <d:pixel-density>ldpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>240</d:x-dimension>
+ <d:y-dimension>320</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>145</d:xdpi>
+ <d:ydpi>145</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>qwerty</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, closed">
+ <d:description>The phone in landscape view with the keyboard closed</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, open">
+ <d:description>The phone in landscape view with the keyboard open</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keysexposed</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 3.2in HVGA slider (ADP1)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.2</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>320</d:x-dimension>
+ <d:y-dimension>480</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>180.6</d:xdpi>
+ <d:ydpi>182</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>qwerty</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, closed">
+ <d:description>The phone in landscape view with the keyboard closed</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, open">
+ <d:description>The phone in landscape view with the keyboard open</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keysexposed</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 3.2in QVGA (ADP2)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.2</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>320</d:x-dimension>
+ <d:y-dimension>480</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>180.6</d:xdpi>
+ <d:ydpi>182</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>trackball</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 3.3in WQVGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.3</d:diagonal-length>
+ <d:pixel-density>ldpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>240</d:x-dimension>
+ <d:y-dimension>400</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>141</d:xdpi>
+ <d:ydpi>141</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+ <d:device>
+ <d:name>
+ 3.4in WQVGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.4</d:diagonal-length>
+ <d:pixel-density>ldpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>240</d:x-dimension>
+ <d:y-dimension>432</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>145</d:xdpi>
+ <d:ydpi>145</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 3.7in WVGA (Nexus One)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.4</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>254</d:xdpi>
+ <d:ydpi>254</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>trackball</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 3.7 FWVGA slider
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.7</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>854</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>265</d:xdpi>
+ <d:ydpi>265</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>qwerty</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, closed">
+ <d:description>The phone in landscape view with the keyboard closed</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyshidden</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape, open">
+ <d:description>The phone in landscape view with the keyboard open</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keysexposed</d:keyboard-state>
+ <d:nav-state>navexposed</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 4in WVGA (Nexus S)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.0</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>235</d:xdpi>
+ <d:ydpi>235</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 4.65in 720p (Galaxy Nexus)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.65</d:diagonal-length> <!-- In inches -->
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>720</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>316</d:xdpi>
+ <d:ydpi>316</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">1</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="KiB"></d:removable-storage>
+ <d:cpu>OMAP 4460</d:cpu> <!-- cpu type (Tegra3) freeform -->
+ <d:gpu>PowerVR SGX540</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ </d:abi>
+ <!--dock (car, desk, tv, none)-->
+ <d:dock>
+ car
+ desk
+ </d:dock>
+ <!-- plugged in (never, charge, always) -->
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>14-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles>
+ HSP
+ HFP
+ SPP
+ A2DP
+ AVRCP
+ OPP
+ PBAP
+ GAVDP
+ AVDTP
+ HID
+ HDP
+ PAN
+ </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <!--
+ These can be gotten via
+ javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
+ -->
+ <d:gl-extensions>
+ GL_EXT_discard_framebuffer
+ GL_EXT_multi_draw_arrays
+ GL_EXT_shader_texture_lod
+ GL_EXT_texture_format_BGRA8888
+ GL_IMG_multisampled_render_to_texture
+ GL_IMG_program_binary
+ GL_IMG_read_format
+ GL_IMG_shader_binary
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_format_BGRA8888
+ GL_IMG_texture_npot
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_depth_texture
+ GL_OES_depth24
+ GL_OES_EGL_image
+ GL_OES_EGL_image_external
+ GL_OES_egl_sync
+ GL_OES_element_index_uint
+ GL_OES_fragment_precision_high
+ GL_OES_get_program_binary
+ GL_OES_mapbuffer
+ GL_OES_packed_depth_stencil
+ GL_OES_required_internalformat
+ GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives
+ GL_OES_texture_float
+ GL_OES_texture_half_float
+ GL_OES_vertex_array_object
+ GL_OES_vertex_half_float
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 4.7in WXGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.7</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1280</d:x-dimension>
+ <d:y-dimension>720</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>320</d:xdpi>
+ <d:ydpi>320</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 5.1in WVGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>5.1</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>183</d:xdpi>
+ <d:ydpi>183</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 5.4in FWVGA
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>5.4</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>854</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>181</d:xdpi>
+ <d:ydpi>181</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>
+ 7in WSVGA (Tablet)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>7.0</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1024</d:x-dimension>
+ <d:y-dimension>600</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>169</d:xdpi>
+ <d:ydpi>169</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+
+
+ <d:device>
+ <d:name>
+ 10.1in WXGA (Tablet)
+ </d:name>
+ <d:manufacturer>
+ Generic
+ </d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>xlarge</d:screen-size>
+ <d:diagonal-length>10.1</d:diagonal-length>
+ <d:pixel-density>mdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1280</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>149</d:xdpi>
+ <d:ydpi>149</d:ydpi>
+ <d:touch>
+ <d:multitouch>distinct</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>OMAP 9001</d:cpu>
+ <d:gpu>Ultra Nexus 3D S++</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ mips
+ x86
+ </d:abi>
+ <d:dock>
+ car
+ television
+ desk
+ </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles />
+ <d:gl-version>2.1</d:gl-version>
+ <d:gl-extensions />
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>navhidden</d:nav-state>
+ </d:state>
+ </d:device>
+</d:devices>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java
index 463037e..c4b6b18 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java
@@ -16,9 +16,10 @@
package com.android.sdklib.internal.avd;
+import com.android.SdkConstants;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
+import com.android.sdklib.devices.Device;
import java.io.File;
import java.util.Collections;
@@ -46,7 +47,11 @@ public final class AvdInfo implements Comparable<AvdInfo> {
/** Unable to parse config.ini */
ERROR_PROPERTIES,
/** System Image folder in config.ini doesn't exist */
- ERROR_IMAGE_DIR;
+ ERROR_IMAGE_DIR,
+ /** The {@link Device} this AVD is based on has changed from its original configuration*/
+ ERROR_DEVICE_CHANGED,
+ /** The {@link Device} this AVD is based on is no longer available */
+ ERROR_DEVICE_MISSING;
}
private final String mName;
@@ -140,6 +145,24 @@ public final class AvdInfo implements Comparable<AvdInfo> {
return SdkConstants.CPU_ARCH_ARM;
}
+ public String getDeviceManufacturer() {
+ String deviceManufacturer = mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
+ if (deviceManufacturer != null && !deviceManufacturer.isEmpty()) {
+ return deviceManufacturer;
+ }
+
+ return "";
+ }
+
+ public String getDeviceName() {
+ String deviceName = mProperties.get(AvdManager.AVD_INI_DEVICE_NAME);
+ if (deviceName != null && !deviceName.isEmpty()) {
+ return deviceName;
+ }
+
+ return "";
+ }
+
/** Convenience function to return a more user friendly name of the abi type. */
public static String getPrettyAbiType(String raw) {
String s = null;
@@ -153,7 +176,7 @@ public final class AvdInfo implements Comparable<AvdInfo> {
s = "Intel Atom (" + SdkConstants.ABI_INTEL_ATOM + ")";
} else if (raw.equalsIgnoreCase(SdkConstants.ABI_MIPS)) {
- s = "Mips (" + SdkConstants.ABI_MIPS + ")";
+ s = "MIPS (" + SdkConstants.ABI_MIPS + ")";
} else {
s = raw + " (" + raw + ")";
@@ -266,6 +289,14 @@ public final class AvdInfo implements Comparable<AvdInfo> {
return String.format(
"Invalid value in image.sysdir. Run 'android update avd -n %1$s'",
mName);
+ case ERROR_DEVICE_CHANGED:
+ return String.format("%1$s %2$s configuration has changed since AVD creation",
+ mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
+ mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
+ case ERROR_DEVICE_MISSING:
+ return String.format("%1$s %2$s no longer exists as a device",
+ mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
+ mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
case OK:
assert false;
return null;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
index dc19287..d2ba1b6 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
@@ -16,20 +16,23 @@
package com.android.sdklib.internal.avd;
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
import com.android.io.FileWrapper;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISdkLog;
import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.devices.DeviceManager;
+import com.android.sdklib.devices.DeviceManager.DeviceStatus;
import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.util.GrabProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.Wait;
-import com.android.util.Pair;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -40,9 +43,11 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -85,6 +90,15 @@ public class AvdManager {
*/
public final static String AVD_INI_CPU_MODEL = "hw.cpu.model"; //$NON-NLS-1$
+ /**
+ * AVD/config.ini key name representing the manufacturer of the device this avd was based on.
+ */
+ public static final String AVD_INI_DEVICE_MANUFACTURER = "hw.device.manufacturer"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the name of the device this avd was based on.
+ */
+ public static final String AVD_INI_DEVICE_NAME = "hw.device.name"; //$NON-NLS-1$
/**
* AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
@@ -140,6 +154,41 @@ public class AvdManager {
public final static String AVD_INI_SNAPSHOT_PRESENT = "snapshot.present"; //$NON-NLS-1$
/**
+ * AVD/config.ini key name representing whether hardware OpenGLES emulation is enabled
+ */
+ public final static String AVD_INI_GPU_EMULATION = "hw.gpu.enabled"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing how to emulate the front facing camera
+ */
+ public final static String AVD_INI_CAMERA_FRONT = "hw.camera.front"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing how to emulate the rear facing camera
+ */
+ public final static String AVD_INI_CAMERA_BACK = "hw.camera.back"; //$NON-NLS-1$
+
+ /**
+ * AVD/config.ini key name representing the amount of RAM the emulated device should have
+ */
+ public final static String AVD_INI_RAM_SIZE = "hw.ramSize";
+
+ /**
+ * AVD/config.ini key name representing the amount of memory available to applications by default
+ */
+ public final static String AVD_INI_VM_HEAP_SIZE = "vm.heapSize";
+
+ /**
+ * AVD/config.ini key name representing the size of the data partition
+ */
+ public final static String AVD_INI_DATA_PARTITION_SIZE = "disk.dataPartition.size";
+
+ /**
+ * AVD/config.ini key name representing the hash of the device this AVD is based on
+ */
+ public final static String AVD_INI_DEVICE_HASH = "hw.device.hash";
+
+ /**
* Pattern to match pixel-sized skin "names", e.g. "320x480".
*/
public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
@@ -205,6 +254,12 @@ public class AvdManager {
CONFLICT_EXISTING_PATH,
}
+ // A map where the keys are the locations of the SDK and the values are the corresponding
+ // AvdManagers. This prevents us from creating multiple AvdManagers for the same SDK and having
+ // them get out of sync.
+ private static final Map<String, AvdManager> mManagers =
+ Collections.synchronizedMap(new WeakHashMap<String, AvdManager>());
+
private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
private AvdInfo[] mValidAvdList;
private AvdInfo[] mBrokenAvdList;
@@ -220,11 +275,24 @@ public class AvdManager {
* logging needs. Cannot be null.
* @throws AndroidLocationException
*/
- public AvdManager(SdkManager sdkManager, ISdkLog log) throws AndroidLocationException {
+ protected AvdManager(SdkManager sdkManager, ILogger log) throws AndroidLocationException {
mSdkManager = sdkManager;
buildAvdList(mAllAvdList, log);
}
+ public static AvdManager getInstance(SdkManager sdkManager, ILogger log)
+ throws AndroidLocationException {
+ synchronized(mManagers) {
+ AvdManager manager;
+ if ((manager = mManagers.get(sdkManager.getLocation())) != null) {
+ return manager;
+ }
+ manager = new AvdManager(sdkManager, log);
+ mManagers.put(sdkManager.getLocation(), manager);
+ return manager;
+ }
+ }
+
/**
* Returns the base folder where AVDs are created.
*
@@ -443,7 +511,7 @@ public class AvdManager {
* @throws AndroidLocationException if there was an error finding the location of the
* AVD folder.
*/
- public void reloadAvds(ISdkLog log) throws AndroidLocationException {
+ public void reloadAvds(ILogger log) throws AndroidLocationException {
// build the list in a temp list first, in case the method throws an exception.
// It's better than deleting the whole list before reading the new one.
ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
@@ -488,7 +556,7 @@ public class AvdManager {
boolean createSnapshot,
boolean removePrevious,
boolean editExisting,
- ISdkLog log) {
+ ILogger log) {
if (log == null) {
throw new IllegalArgumentException("log cannot be null");
}
@@ -579,7 +647,7 @@ public class AvdManager {
if (createSnapshot) {
File snapshotDest = new File(avdFolder, SNAPSHOTS_IMG);
if (snapshotDest.isFile() && editExisting) {
- log.printf("Snapshot image already present, was not changed.\n");
+ log.info("Snapshot image already present, was not changed.\n");
} else {
String toolsLib = mSdkManager.getLocation() + File.separator
@@ -681,7 +749,7 @@ public class AvdManager {
// There's already an sdcard file with the right size and we're
// not overriding it... so don't remove it.
runMkSdcard = false;
- log.printf("SD Card already present with same size, was not changed.\n");
+ log.info("SD Card already present with same size, was not changed.\n");
}
}
@@ -793,7 +861,7 @@ public class AvdManager {
report.append("\n");
}
- log.printf(report.toString());
+ log.info(report.toString());
// create the AvdInfo object, and add it to the list
AvdInfo newAvdInfo = new AvdInfo(
@@ -934,7 +1002,7 @@ public class AvdManager {
* @param target The target where to find the skin.
* @param log the log object to receive action logs. Cannot be null.
*/
- public String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) {
+ public String getSkinRelativePath(String skinName, IAndroidTarget target, ILogger log) {
if (log == null) {
throw new IllegalArgumentException("log cannot be null");
}
@@ -1050,13 +1118,13 @@ public class AvdManager {
* @param log the log object to receive action logs. Cannot be null.
* @return True if the AVD was deleted with no error.
*/
- public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) {
+ public boolean deleteAvd(AvdInfo avdInfo, ILogger log) {
try {
boolean error = false;
File f = avdInfo.getIniFile();
if (f != null && f.exists()) {
- log.printf("Deleting file %1$s\n", f.getCanonicalPath());
+ log.info("Deleting file %1$s\n", f.getCanonicalPath());
if (!f.delete()) {
log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
error = true;
@@ -1067,7 +1135,7 @@ public class AvdManager {
if (path != null) {
f = new File(path);
if (f.exists()) {
- log.printf("Deleting folder %1$s\n", f.getCanonicalPath());
+ log.info("Deleting folder %1$s\n", f.getCanonicalPath());
if (deleteContentOf(f) == false || f.delete() == false) {
log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
error = true;
@@ -1078,10 +1146,10 @@ public class AvdManager {
removeAvd(avdInfo);
if (error) {
- log.printf("\nAVD '%1$s' deleted with errors. See errors above.\n",
+ log.info("\nAVD '%1$s' deleted with errors. See errors above.\n",
avdInfo.getName());
} else {
- log.printf("\nAVD '%1$s' deleted.\n", avdInfo.getName());
+ log.info("\nAVD '%1$s' deleted.\n", avdInfo.getName());
return true;
}
@@ -1107,7 +1175,7 @@ public class AvdManager {
* @return True if the move succeeded or there was nothing to do.
* If false, this method will have had already output error in the log.
*/
- public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) {
+ public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ILogger log) {
try {
if (paramFolderPath != null) {
@@ -1159,7 +1227,7 @@ public class AvdManager {
replaceAvd(avdInfo, info);
}
- log.printf("AVD '%1$s' moved.\n", avdInfo.getName());
+ log.info("AVD '%1$s' moved.\n", avdInfo.getName());
} catch (AndroidLocationException e) {
log.error(e, null);
@@ -1243,7 +1311,7 @@ public class AvdManager {
*
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
- private void buildAvdList(ArrayList<AvdInfo> allList, ISdkLog log)
+ private void buildAvdList(ArrayList<AvdInfo> allList, ILogger log)
throws AndroidLocationException {
File[] avds = buildAvdFilesList();
if (avds != null) {
@@ -1264,7 +1332,7 @@ public class AvdManager {
* @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
* valid or not.
*/
- private AvdInfo parseAvdInfo(File iniPath, ISdkLog log) {
+ private AvdInfo parseAvdInfo(File iniPath, ILogger log) {
Map<String, String> map = ProjectProperties.parsePropertyFile(
new FileWrapper(iniPath),
log);
@@ -1328,6 +1396,20 @@ public class AvdManager {
}
}
+ // Get the device status if this AVD is associated with a device
+ DeviceStatus deviceStatus = null;
+ if (properties != null) {
+ String deviceName = properties.get(AVD_INI_DEVICE_NAME);
+ String deviceMfctr = properties.get(AVD_INI_DEVICE_MANUFACTURER);
+ String hash = properties.get(AVD_INI_DEVICE_HASH);
+ if (deviceName != null && deviceMfctr != null && hash != null) {
+ int deviceHash = Integer.parseInt(hash);
+ deviceStatus = (new DeviceManager(log)).getDeviceStatus(
+ mSdkManager.getLocation(), deviceName, deviceMfctr, deviceHash);
+ }
+ }
+
+
// TODO: What about missing sdcard, skins, etc?
AvdStatus status;
@@ -1344,6 +1426,10 @@ public class AvdManager {
status = AvdStatus.ERROR_PROPERTIES;
} else if (validImageSysdir == false) {
status = AvdStatus.ERROR_IMAGE_DIR;
+ } else if (deviceStatus == DeviceStatus.CHANGED) {
+ status = AvdStatus.ERROR_DEVICE_CHANGED;
+ } else if (deviceStatus == DeviceStatus.MISSING) {
+ status = AvdStatus.ERROR_DEVICE_MISSING;
} else {
status = AvdStatus.OK;
}
@@ -1388,7 +1474,7 @@ public class AvdManager {
* @param log the log object to receive action logs. Cannot be null.
* @return True if the sdcard could be created.
*/
- private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
+ private boolean createSdCard(String toolLocation, String size, String location, ILogger log) {
try {
String[] command = new String[3];
command[0] = toolLocation;
@@ -1404,14 +1490,14 @@ public class AvdManager {
Wait.WAIT_FOR_READERS,
new IProcessOutput() {
@Override
- public void out(String line) {
+ public void out(@Nullable String line) {
if (line != null) {
stdOutput.add(line);
}
}
@Override
- public void err(String line) {
+ public void err(@Nullable String line) {
if (line != null) {
errorOutput.add(line);
}
@@ -1459,7 +1545,7 @@ public class AvdManager {
* @param log the log object to receive action logs. Cannot be null.
* @throws IOException
*/
- public void updateAvd(String name, ISdkLog log) throws IOException {
+ public void updateAvd(String name, ILogger log) throws IOException {
// find the AVD to update. It should be be in the broken list.
AvdInfo avd = null;
synchronized (mAllAvdList) {
@@ -1487,7 +1573,7 @@ public class AvdManager {
* @param log the log object to receive action logs. Cannot be null.
* @throws IOException
*/
- public void updateAvd(AvdInfo avd, ISdkLog log) throws IOException {
+ public void updateAvd(AvdInfo avd, ILogger log) throws IOException {
// get the properties. This is a unmodifiable Map.
Map<String, String> oldProperties = avd.getProperties();
@@ -1502,12 +1588,12 @@ public class AvdManager {
// create the path to the new system images.
if (setImagePathProperties(avd.getTarget(), avd.getAbiType(), properties, log)) {
if (properties.containsKey(AVD_INI_IMAGES_1)) {
- log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
+ log.info("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
properties.get(AVD_INI_IMAGES_1));
}
if (properties.containsKey(AVD_INI_IMAGES_2)) {
- log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
+ log.info("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
properties.get(AVD_INI_IMAGES_2));
}
@@ -1518,10 +1604,16 @@ public class AvdManager {
//FIXME: display paths to empty image folders?
status = AvdStatus.ERROR_IMAGE_DIR;
}
+ updateAvd(avd, properties, status, log);
+ }
+ public void updateAvd(AvdInfo avd,
+ Map<String, String> newProperties,
+ AvdStatus status,
+ ILogger log) throws IOException {
// now write the config file
File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI);
- writeIniFile(configIniFile, properties);
+ writeIniFile(configIniFile, newProperties);
// finally create a new AvdInfo for this unbroken avd and add it to the list.
// instead of creating the AvdInfo object directly we reparse it, to detect other possible
@@ -1534,8 +1626,7 @@ public class AvdManager {
avd.getTargetHash(),
avd.getTarget(),
avd.getAbiType(),
- properties,
- status);
+ newProperties);
replaceAvd(avd, newAvd);
}
@@ -1553,7 +1644,7 @@ public class AvdManager {
private boolean setImagePathProperties(IAndroidTarget target,
String abiType,
Map<String, String> properties,
- ISdkLog log) {
+ ILogger log) {
properties.remove(AVD_INI_IMAGES_1);
properties.remove(AVD_INI_IMAGES_2);
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/HardwareProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/HardwareProperties.java
index c2c9bed..02241ef 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/HardwareProperties.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/HardwareProperties.java
@@ -16,7 +16,7 @@
package com.android.sdklib.internal.avd;
-import com.android.sdklib.ISdkLog;
+import com.android.utils.ILogger;
import java.io.BufferedReader;
import java.io.File;
@@ -30,39 +30,96 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HardwareProperties {
+ /** AVD/config.ini key for whether hardware buttons are present. */
+ public static final String HW_MAINKEYS = "hw.mainKeys";
+
+ /** AVD/config.ini key indicating whether trackball is present. */
+ public static final String HW_TRACKBALL = "hw.trackBall";
+
+ /** AVD/config.ini key indicating whether qwerty keyboard is present. */
+ public static final String HW_KEYBOARD = "hw.keyboard";
+
+ /** AVD/config.ini key indicating whether dpad is present. */
+ public static final String HW_DPAD = "hw.dPad";
+
+ /** AVD/config.ini key indicating whether gps is present. */
+ public static final String HW_GPS = "hw.gps";
+
+ /** AVD/config.ini key indicating whether the device is running on battery. */
+ public static final String HW_BATTERY = "hw.battery";
+
+ /** AVD/config.ini key indicating whether accelerometer is present. */
+ public static final String HW_ACCELEROMETER = "hw.accelerometer";
+
+ /** AVD/config.ini key indicating whether gyroscope is present. */
+ public static final String HW_ORIENTATION_SENSOR = "hw.sensors.orientation";
+
+ /** AVD/config.ini key indicating whether h/w mic is present. */
+ public static final String HW_AUDIO_INPUT = "hw.audioInput";
+
+ /** AVD/config.ini key indicating whether sdcard is present. */
+ public static final String HW_SDCARD = "hw.sdCard";
+
+ /** AVD/config.ini key for LCD density. */
+ public static final String HW_LCD_DENSITY = "hw.lcd.density";
+
+ /** AVD/config.ini key indicating whether proximity sensor present. */
+ public static final String HW_PROXIMITY_SENSOR = "hw.sensors.proximity";
+
+
private final static Pattern PATTERN_PROP = Pattern.compile(
"^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
- private final static String HW_PROP_NAME = "name";
- private final static String HW_PROP_TYPE = "type";
- private final static String HW_PROP_DEFAULT = "default";
- private final static String HW_PROP_ABSTRACT = "abstract";
- private final static String HW_PROP_DESC = "description";
+ /** Property name in the generated avd config file; String; e.g. "hw.screen" */
+ private final static String HW_PROP_NAME = "name"; //$NON-NLS-1$
+ /** Property type, one of {@link HardwarePropertyType} */
+ private final static String HW_PROP_TYPE = "type"; //$NON-NLS-1$
+ /** Default value of the property. String matching the property type. */
+ private final static String HW_PROP_DEFAULT = "default"; //$NON-NLS-1$
+ /** User-visible name of the property. String. */
+ private final static String HW_PROP_ABSTRACT = "abstract"; //$NON-NLS-1$
+ /** User-visible description of the property. String. */
+ private final static String HW_PROP_DESC = "description"; //$NON-NLS-1$
+ /** Comma-separate values for a property of type "enum" */
+ private final static String HW_PROP_ENUM = "enum"; //$NON-NLS-1$
- private final static String BOOLEAN_YES = "yes";
- private final static String BOOLEAN_NO = "no";
+ public final static String BOOLEAN_YES = "yes";
+ public final static String BOOLEAN_NO = "no";
public final static String[] BOOLEAN_VALUES = new String[] { BOOLEAN_YES, BOOLEAN_NO };
- public final static Pattern DISKSIZE_PATTERN = Pattern.compile("\\d+[MK]B");
+ public final static Pattern DISKSIZE_PATTERN = Pattern.compile("\\d+[MK]B"); //$NON-NLS-1$
+
+ /** Represents the type of a hardware property value. */
+ public enum HardwarePropertyType {
+ INTEGER ("integer", false /*isEnum*/), //$NON-NLS-1$
+ BOOLEAN ("boolean", false /*isEnum*/), //$NON-NLS-1$
+ DISKSIZE ("diskSize", false /*isEnum*/), //$NON-NLS-1$
+ STRING ("string", false /*isEnum*/), //$NON-NLS-1$
+ INTEGER_ENUM("integer", true /*isEnum*/), //$NON-NLS-1$
+ STRING_ENUM ("string", true /*isEnum*/); //$NON-NLS-1$
+
- public enum ValueType {
- INTEGER("integer"),
- BOOLEAN("boolean"),
- DISKSIZE("diskSize"),
- STRING("string");
+ private String mName;
+ private boolean mIsEnum;
- private String mValue;
+ HardwarePropertyType(String name, boolean isEnum) {
+ mName = name;
+ mIsEnum = isEnum;
+ }
- ValueType(String value) {
- mValue = value;
+ /** Returns the name of the type (e.g. "string", "boolean", etc.) */
+ public String getName() {
+ return mName;
}
- public String getValue() {
- return mValue;
+ /** Indicates whether this type is an enum (e.g. "enum of strings"). */
+ public boolean isEnum() {
+ return mIsEnum;
}
- public static ValueType getEnum(String value) {
- for (ValueType type : values()) {
- if (type.mValue.equals(value)) {
+ /** Returns the internal HardwarePropertyType object matching the given type name. */
+ public static HardwarePropertyType getEnum(String name, boolean isEnum) {
+ for (HardwarePropertyType type : values()) {
+ if (type.mName.equals(name) && type.mIsEnum == isEnum) {
return type;
}
}
@@ -73,9 +130,11 @@ public class HardwareProperties {
public static final class HardwareProperty {
private String mName;
- private ValueType mType;
+ private HardwarePropertyType mType;
/** the string representation of the default value. can be null. */
private String mDefault;
+ /** the choices for an enum. Null if not an enum. */
+ private String[] mEnum;
private String mAbstract;
private String mDescription;
@@ -88,42 +147,57 @@ public class HardwareProperties {
mDescription = "";
}
+ /** Returns the hardware config name of the property, e.g. "hw.screen" */
public String getName() {
return mName;
}
- public ValueType getType() {
+ /** Returns the property type, one of {@link HardwarePropertyType} */
+ public HardwarePropertyType getType() {
return mType;
}
+ /**
+ * Returns the default value of the property.
+ * String matching the property type.
+ * Can be null.
+ */
public String getDefault() {
return mDefault;
}
+ /** Returns the user-visible name of the property. */
public String getAbstract() {
return mAbstract;
}
+ /** Returns the user-visible description of the property. */
public String getDescription() {
return mDescription;
}
+ /** Returns the possible values for an enum property. Can be null. */
+ public String[] getEnum() {
+ return mEnum;
+ }
+
public boolean isValidForUi() {
- // don't show display string type for now.
- return mType != ValueType.STRING;
+ // don't display single string type for now.
+ return mType != HardwarePropertyType.STRING || mType.isEnum();
}
}
/**
* Parses the hardware definition file.
* @param file the property file to parse
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing. Cannot be null.
* @return the map of (key,value) pairs, or null if the parsing failed.
*/
- public static Map<String, HardwareProperty> parseHardwareDefinitions(File file, ISdkLog log) {
+ public static Map<String, HardwareProperty> parseHardwareDefinitions(File file, ILogger log) {
+ BufferedReader reader = null;
try {
FileInputStream fis = new FileInputStream(file);
- BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
+ reader = new BufferedReader(new InputStreamReader(fis));
Map<String, HardwareProperty> map = new TreeMap<String, HardwareProperty>();
@@ -133,10 +207,10 @@ public class HardwareProperties {
if (line.length() > 0 && line.charAt(0) != '#') {
Matcher m = PATTERN_PROP.matcher(line);
if (m.matches()) {
- String valueName = m.group(1);
+ String key = m.group(1);
String value = m.group(2);
- if (HW_PROP_NAME.equals(valueName)) {
+ if (HW_PROP_NAME.equals(key)) {
prop = new HardwareProperty();
prop.mName = value;
map.put(prop.mName, prop);
@@ -148,14 +222,42 @@ public class HardwareProperties {
return null;
}
- if (HW_PROP_TYPE.equals(valueName)) {
- prop.mType = ValueType.getEnum(value);
- } else if (HW_PROP_DEFAULT.equals(valueName)) {
+ if (HW_PROP_TYPE.equals(key)) {
+ // Note: we don't know yet whether this type is an enum.
+ // This is indicated by the "enum = value" line that is parsed later.
+ prop.mType = HardwarePropertyType.getEnum(value, false);
+ assert (prop.mType != null);
+ } else if (HW_PROP_DEFAULT.equals(key)) {
prop.mDefault = value;
- } else if (HW_PROP_ABSTRACT.equals(valueName)) {
+ } else if (HW_PROP_ABSTRACT.equals(key)) {
prop.mAbstract = value;
- } else if (HW_PROP_DESC.equals(valueName)) {
+ } else if (HW_PROP_DESC.equals(key)) {
prop.mDescription = value;
+ } else if (HW_PROP_ENUM.equals(key)) {
+ if (!prop.mType.isEnum()) {
+ // Change the type to an enum, if valid.
+ prop.mType = HardwarePropertyType.getEnum(prop.mType.getName(),
+ true);
+ assert (prop.mType != null);
+ }
+
+ // Sanitize input: trim spaces, ignore empty entries.
+ String[] v = value.split(",");
+ int n = 0;
+ for (int i = 0; i < v.length; i++) {
+ String s = v[i] = v[i].trim();
+ if (s.length() > 0) {
+ n++;
+ }
+ }
+ prop.mEnum = new String[n];
+ n = 0;
+ for (int i = 0; i < v.length; i++) {
+ String s = v[i];
+ if (s.length() > 0) {
+ prop.mEnum[n++] = s;
+ }
+ }
}
} else {
log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
@@ -173,6 +275,14 @@ public class HardwareProperties {
} catch (IOException e) {
log.warning("Error parsing '%1$s': %2$s.", file.getAbsolutePath(),
e.getMessage());
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
}
return null;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/KeystoreHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/KeystoreHelper.java
index af5b401..ba4ce8c 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/KeystoreHelper.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/KeystoreHelper.java
@@ -16,6 +16,7 @@
package com.android.sdklib.internal.build;
+import com.android.annotations.Nullable;
import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import com.android.sdklib.util.GrabProcessOutput;
@@ -111,7 +112,7 @@ public final class KeystoreHelper {
Wait.WAIT_FOR_READERS,
new IProcessOutput() {
@Override
- public void out(String line) {
+ public void out(@Nullable String line) {
if (line != null) {
if (output != null) {
output.out(line);
@@ -122,7 +123,7 @@ public final class KeystoreHelper {
}
@Override
- public void err(String line) {
+ public void err(@Nullable String line) {
if (line != null) {
if (output != null) {
output.err(line);
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/MakeIdentity.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/MakeIdentity.java
deleted file mode 100644
index ca1605a..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/MakeIdentity.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2011 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.build;
-
-import com.android.appauth.Certificate;
-import com.android.sdklib.ISdkLog;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.cert.CertificateException;
-
-public class MakeIdentity {
-
- private final String mAccount;
- private final String mKeystorePath;
- private final String mKeystorePass;
- private final String mAliasName;
- private final String mAliasPass;
-
- /**
- * Create a {@link MakeIdentity} object.
- * @param account the google account
- * @param keystorePath the path to the keystore
- * @param keystorePass the password of the keystore
- * @param aliasName the key alias name
- * @param aliasPass the key alias password
- */
- public MakeIdentity(String account, String keystorePath, String keystorePass,
- String aliasName, String aliasPass) {
- mAccount = account;
- mKeystorePath = keystorePath;
- mKeystorePass = keystorePass;
- mAliasName = aliasName;
- mAliasPass = aliasPass;
- }
-
- /**
- * Write the identity file to the given {@link PrintStream} object.
- * @param ps the printstream object to write the identity file to.
- * @return true if success.
- * @throws KeyStoreException
- * @throws NoSuchAlgorithmException
- * @throws CertificateException
- * @throws IOException
- * @throws UnrecoverableEntryException
- */
- public boolean make(PrintStream ps, ISdkLog log)
- throws KeyStoreException, NoSuchAlgorithmException,
- CertificateException, IOException, UnrecoverableEntryException {
-
- KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
- FileInputStream fis = new FileInputStream(mKeystorePath);
- keyStore.load(fis, mKeystorePass.toCharArray());
- fis.close();
- PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
- mAliasName, new KeyStore.PasswordProtection(mAliasPass.toCharArray()));
-
- if (entry == null) {
- return false;
- }
-
- Certificate c = new Certificate();
- c.setVersion(Certificate.VERSION);
- c.setType(Certificate.TYPE_IDENTITY);
- c.setHashAlgo(Certificate.DIGEST_TYPE);
- c.setPublicKey(entry.getCertificate().getPublicKey());
- c.setEntityName(mAccount);
- c.signWith(c, entry.getPrivateKey());
-
- /* sanity check */
- if (!c.isSignedBy(c)) {
- System.err.println("signature failed?!");
- return false;
- }
-
- /* write to the printstream object */
- try {
- c.writeTo(ps);
- } catch (Exception e) {
- log.error(e, null);
- }
-
- return true;
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolLoader.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolLoader.java
new file mode 100644
index 0000000..775c558
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolLoader.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 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.build;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ */
+public class SymbolLoader {
+
+ private final File mSymbolFile;
+ private HashBasedTable<String, String, SymbolEntry> mSymbols;
+
+ public static class SymbolEntry {
+ private final String mName;
+ private final String mType;
+ private final String mValue;
+
+ public SymbolEntry(String name, String type, String value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getType() {
+ return mType;
+ }
+ }
+
+ public SymbolLoader(File symbolFile) {
+ mSymbolFile = symbolFile;
+ }
+
+ public void load() throws IOException {
+ List<String> lines = Files.readLines(mSymbolFile, Charsets.UTF_8);
+
+ mSymbols = HashBasedTable.create();
+
+ try {
+ for (String line : lines) {
+ // format is "<type> <class> <name> <value>"
+ // don't want to split on space as value could contain spaces.
+ int pos = line.indexOf(' ');
+ String type = line.substring(0, pos);
+ int pos2 = line.indexOf(' ', pos + 1);
+ String className = line.substring(pos + 1, pos2);
+ int pos3 = line.indexOf(' ', pos2 + 1);
+ String name = line.substring(pos2 + 1, pos3);
+ String value = line.substring(pos3 + 1);
+
+ mSymbols.put(className, name, new SymbolEntry(name, type, value));
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IOException("File format error reading " + mSymbolFile.getAbsolutePath());
+ }
+ }
+
+ Table<String, String, SymbolEntry> getSymbols() {
+ return mSymbols;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolWriter.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolWriter.java
new file mode 100644
index 0000000..63346c2
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SymbolWriter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 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.build;
+
+import com.android.SdkConstants;
+import com.android.sdklib.internal.build.SymbolLoader.SymbolEntry;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Table;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ */
+public class SymbolWriter {
+
+ private final String mOutFolder;
+ private final String mPackageName;
+ private final SymbolLoader mSymbols;
+ private final SymbolLoader mValues;
+
+ public SymbolWriter(String outFolder, String packageName, SymbolLoader symbols,
+ SymbolLoader values) {
+ mOutFolder = outFolder;
+ mPackageName = packageName;
+ mSymbols = symbols;
+ mValues = values;
+ }
+
+ public void write() throws IOException {
+ Splitter splitter = Splitter.on('.');
+ Iterable<String> folders = splitter.split(mPackageName);
+ File file = new File(mOutFolder);
+ for (String folder : folders) {
+ file = new File(file, folder);
+ }
+ file.mkdirs();
+ file = new File(file, SdkConstants.FN_RESOURCE_CLASS);
+
+ BufferedWriter writer = null;
+ try {
+ writer = Files.newWriter(file, Charsets.UTF_8);
+
+ writer.write("/* AUTO-GENERATED FILE. DO NOT MODIFY.\n");
+ writer.write(" *\n");
+ writer.write(" * This class was automatically generated by the\n");
+ writer.write(" * aapt tool from the resource data it found. It\n");
+ writer.write(" * should not be modified by hand.\n");
+ writer.write(" */\n");
+
+ writer.write("package ");
+ writer.write(mPackageName);
+ writer.write(";\n\npublic final class R {\n");
+
+ Table<String, String, SymbolEntry> symbols = mSymbols.getSymbols();
+ Table<String, String, SymbolEntry> values = mValues.getSymbols();
+
+ for (String row : symbols.rowKeySet()) {
+ writer.write("\tpublic static final class ");
+ writer.write(row);
+ writer.write(" {\n");
+
+ for (Map.Entry<String, SymbolEntry> symbol : symbols.row(row).entrySet()) {
+ // get the matching SymbolEntry from the values Table.
+ SymbolEntry value = values.get(row, symbol.getKey());
+ if (value != null) {
+ writer.write("\t\tpublic static final ");
+ writer.write(value.getType());
+ writer.write(" ");
+ writer.write(value.getName());
+ writer.write(" = ");
+ writer.write(value.getValue());
+ writer.write(";\n");
+ }
+ }
+
+ writer.write("\t}\n");
+ }
+
+ writer.write("}\n");
+ } finally {
+ Closeables.closeQuietly(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/IPropertySource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/IPropertySource.java
index 360c755..92bd6b9 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/IPropertySource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/IPropertySource.java
@@ -22,4 +22,5 @@ package com.android.sdklib.internal.project;
*/
public interface IPropertySource {
String getProperty(String name);
+ void debugPrint();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
index 721d165..fee9472 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
@@ -16,16 +16,15 @@
package com.android.sdklib.internal.project;
-import com.android.AndroidConstants;
+import com.android.SdkConstants;
import com.android.io.FileWrapper;
import com.android.io.FolderWrapper;
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.project.ProjectProperties.PropertyType;
-import com.android.sdklib.xml.AndroidManifest;
-import com.android.sdklib.xml.AndroidXPathFactory;
+import com.android.utils.ILogger;
+import com.android.xml.AndroidManifest;
+import com.android.xml.AndroidXPathFactory;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@@ -143,7 +142,7 @@ public class ProjectCreator {
/** The {@link OutputLevel} verbosity. */
private final OutputLevel mLevel;
/** Logger for errors and output. Cannot be null. */
- private final ISdkLog mLog;
+ private final ILogger mLog;
/** The OS path of the SDK folder. */
private final String mSdkFolder;
/** The {@link SdkManager} instance. */
@@ -157,7 +156,7 @@ public class ProjectCreator {
* @param level The {@link OutputLevel} verbosity.
* @param log Logger for errors and output. Cannot be null.
*/
- public ProjectCreator(SdkManager sdkManager, String sdkFolder, OutputLevel level, ISdkLog log) {
+ public ProjectCreator(SdkManager sdkManager, String sdkFolder, OutputLevel level, ILogger log) {
mSdkManager = sdkManager;
mSdkFolder = sdkFolder;
mLevel = level;
@@ -338,11 +337,11 @@ public class ProjectCreator {
if (isTestProject == false) {
/* Make res files only for non test projects */
- File valueFolder = createDirs(resourceFolder, AndroidConstants.FD_RES_VALUES);
+ File valueFolder = createDirs(resourceFolder, SdkConstants.FD_RES_VALUES);
installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"),
keywords, target);
- File layoutFolder = createDirs(resourceFolder, AndroidConstants.FD_RES_LAYOUT);
+ File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_RES_LAYOUT);
installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"),
keywords, target);
@@ -929,8 +928,9 @@ public class ProjectCreator {
private Matcher checkFileContainsRegexp(File file, String regexp) {
Pattern p = Pattern.compile(regexp);
+ BufferedReader in = null;
try {
- BufferedReader in = new BufferedReader(new FileReader(file));
+ in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
@@ -943,6 +943,14 @@ public class ProjectCreator {
in.close();
} catch (Exception e) {
// ignore
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
}
return null;
@@ -1228,8 +1236,8 @@ public class ProjectCreator {
/**
* Prints a message unless silence is enabled.
* <p/>
- * This is just a convenience wrapper around {@link ISdkLog#printf(String, Object...)} from
- * {@link #mLog} after testing if ouput level is {@link OutputLevel#VERBOSE}.
+ * This is just a convenience wrapper around {@link ILogger#info(String, Object...)} from
+ * {@link #mLog} after testing if output level is {@link OutputLevel#VERBOSE}.
*
* @param format Format for String.format
* @param args Arguments for String.format
@@ -1239,7 +1247,7 @@ public class ProjectCreator {
if (!format.endsWith("\n")) {
format += "\n";
}
- mLog.printf(format, args);
+ mLog.info(format, args);
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java
index 9d578c4..57e6190 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java
@@ -16,17 +16,14 @@
package com.android.sdklib.internal.project;
-import static com.android.sdklib.SdkConstants.FD_PROGUARD;
-import static com.android.sdklib.SdkConstants.FD_TOOLS;
-import static com.android.sdklib.SdkConstants.FN_ANDROID_PROGUARD_FILE;
-import static com.android.sdklib.SdkConstants.FN_PROJECT_PROGUARD_FILE;
-
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.io.FolderWrapper;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.StreamException;
-import com.android.sdklib.ISdkLog;
-import com.android.sdklib.SdkConstants;
+import com.android.utils.ILogger;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
@@ -37,6 +34,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -111,6 +109,15 @@ public class ProjectProperties implements IPropertySource {
private final Set<String> mKnownProps;
private final Set<String> mRemovedProps;
+ /**
+ * Returns the PropertyTypes ordered the same way Ant order them.
+ */
+ public static PropertyType[] getOrderedTypes() {
+ return new PropertyType[] {
+ PropertyType.LOCAL, PropertyType.ANT, PropertyType.PROJECT
+ };
+ }
+
PropertyType(String filename, String header, String[] validProps, String[] removedProps) {
mFilename = filename;
mHeader = header;
@@ -188,8 +195,9 @@ public class ProjectProperties implements IPropertySource {
// Note: always use / separators in the properties paths. Both Ant and
// our ExportHelper will convert them properly according to the platform.
"#" + PROPERTY_PROGUARD_CONFIG + "=${" + PROPERTY_SDK +"}/"
- + FD_TOOLS + '/' + FD_PROGUARD + '/'
- + FN_ANDROID_PROGUARD_FILE + ':' + FN_PROJECT_PROGUARD_FILE +'\n' +
+ + SdkConstants.FD_TOOLS + '/' + SdkConstants.FD_PROGUARD + '/'
+ + SdkConstants.FN_ANDROID_PROGUARD_FILE + ':'
+ + SdkConstants.FN_PROJECT_PROGUARD_FILE +'\n' +
"\n";
private final static String BUILD_HEADER =
@@ -284,9 +292,11 @@ public class ProjectProperties implements IPropertySource {
* <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
* @param projectFolderOsPath the project folder.
* @param type the type of property file to create
+ *
+ * @see #createEmpty(String, PropertyType)
*/
- public static ProjectPropertiesWorkingCopy create(String projectFolderOsPath,
- PropertyType type) {
+ public static ProjectPropertiesWorkingCopy create(@NonNull String projectFolderOsPath,
+ @NonNull PropertyType type) {
// create and return a ProjectProperties with an empty map.
IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
return create(folder, type);
@@ -297,14 +307,47 @@ public class ProjectProperties implements IPropertySource {
* <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
* @param projectFolder the project folder.
* @param type the type of property file to create
+ *
+ * @see #createEmpty(IAbstractFolder, PropertyType)
*/
- public static ProjectPropertiesWorkingCopy create(IAbstractFolder projectFolder,
- PropertyType type) {
+ public static ProjectPropertiesWorkingCopy create(@NonNull IAbstractFolder projectFolder,
+ @NonNull PropertyType type) {
// create and return a ProjectProperties with an empty map.
return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type);
}
/**
+ * Creates a new project properties object, with no properties.
+ * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
+ * first with {@link #makeWorkingCopy()}.
+ * @param projectFolderOsPath the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #create(String, PropertyType)
+ */
+ public static ProjectProperties createEmpty(@NonNull String projectFolderOsPath,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
+ return createEmpty(folder, type);
+ }
+
+ /**
+ * Creates a new project properties object, with no properties.
+ * <p/>Nothing can be added to it, unless a {@link ProjectPropertiesWorkingCopy} is created
+ * first with {@link #makeWorkingCopy()}.
+ * @param projectFolder the project folder.
+ * @param type the type of property file to create
+ *
+ * @see #create(IAbstractFolder, PropertyType)
+ */
+ public static ProjectProperties createEmpty(@NonNull IAbstractFolder projectFolder,
+ @NonNull PropertyType type) {
+ // create and return a ProjectProperties with an empty map.
+ return new ProjectProperties(projectFolder, new HashMap<String, String>(), type);
+ }
+
+ /**
* Creates and returns a copy of the current properties as a
* {@link ProjectPropertiesWorkingCopy} that can be modified and saved.
* @return a new instance of {@link ProjectPropertiesWorkingCopy}
@@ -324,8 +367,7 @@ public class ProjectProperties implements IPropertySource {
*/
public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) {
// copy the current properties in a new map
- HashMap<String, String> propList = new HashMap<String, String>();
- propList.putAll(mProperties);
+ Map<String, String> propList = new HashMap<String, String>(mProperties);
return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type);
}
@@ -378,10 +420,12 @@ public class ProjectProperties implements IPropertySource {
* <p/>If the file is not present, null is returned with no error messages sent to the log.
*
* @param propFile the property file to parse
- * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
+ * @param log the ILogger object receiving warning/error from the parsing.
* @return the map of (key,value) pairs, or null if the parsing failed.
*/
- public static Map<String, String> parsePropertyFile(IAbstractFile propFile, ISdkLog log) {
+ public static Map<String, String> parsePropertyFile(
+ @NonNull IAbstractFile propFile,
+ @Nullable ILogger log) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(propFile.getContents(),
@@ -390,15 +434,18 @@ public class ProjectProperties implements IPropertySource {
String line = null;
Map<String, String> map = new HashMap<String, String>();
while ((line = reader.readLine()) != null) {
+ line = line.trim();
if (line.length() > 0 && line.charAt(0) != '#') {
Matcher m = PATTERN_PROP.matcher(line);
if (m.matches()) {
map.put(m.group(1), unescape(m.group(2)));
} else {
- log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
- propFile.getOsLocation(),
- line);
+ if (log != null) {
+ log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
+ propFile.getOsLocation(),
+ line);
+ }
return null;
}
}
@@ -410,13 +457,17 @@ public class ProjectProperties implements IPropertySource {
// calling the method.
// Return null below.
} catch (IOException e) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
} catch (StreamException e) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
} finally {
if (reader != null) {
try {
@@ -436,8 +487,10 @@ public class ProjectProperties implements IPropertySource {
* Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
* to instantiate.
*/
- protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
- PropertyType type) {
+ protected ProjectProperties(
+ @NonNull IAbstractFolder projectFolder,
+ @NonNull Map<String, String> map,
+ @NonNull PropertyType type) {
mProjectFolder = projectFolder;
mProperties = map;
mType = type;
@@ -450,4 +503,16 @@ public class ProjectProperties implements IPropertySource {
protected static String escape(String value) {
return value.replaceAll("\\\\", "\\\\\\\\");
}
+
+ @Override
+ public void debugPrint() {
+ System.out.println("DEBUG PROJECTPROPERTIES: " + mProjectFolder);
+ System.out.println("type: " + mType);
+ for (Entry<String, String> entry : mProperties.entrySet()) {
+ System.out.println(entry.getKey() + " -> " + entry.getValue());
+ }
+ System.out.println("<<< DEBUG PROJECTPROPERTIES");
+
+ }
+
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
index 2d3f9b7..21dcc36 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java
@@ -16,10 +16,11 @@
package com.android.sdklib.internal.project;
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.StreamException;
-import com.android.sdklib.SdkConstants;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -96,7 +97,7 @@ public class ProjectPropertiesWorkingCopy extends ProjectProperties {
* overridden by the build.properties file.
* </ul>
*
- * @param type One the possible {@link PropertyType}s.
+ * @param type One the possible {@link ProjectProperties.PropertyType}s.
* @return this object, for chaining.
*/
public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) {
@@ -240,4 +241,11 @@ public class ProjectPropertiesWorkingCopy extends ProjectProperties {
super(projectFolder, map, type);
}
+ @NonNull
+ public ProjectProperties makeReadOnlyCopy() {
+ // copy the current properties in a new map
+ Map<String, String> propList = new HashMap<String, String>(mProperties);
+
+ return new ProjectProperties(mProjectFolder, propList, mType);
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java
index eb6a411..8d4a0c2 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java
@@ -1,152 +1,152 @@
-/*
- * 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.SdkConstants;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * A lightweight wrapper to start & stop ADB.
- * This is <b>specific</b> to the SDK Manager install process.
- */
-public class AdbWrapper {
-
- /*
- * Note: we could bring ddmlib in SdkManager for that purpose, however this allows us to
- * specialize the start/stop methods to our needs (e.g. a task monitor, etc.)
- */
-
- private final String mAdbOsLocation;
- private final ITaskMonitor mMonitor;
-
- /**
- * Creates a new lightweight ADB wrapper.
- *
- * @param osSdkPath The root OS path of the SDK. Cannot be null.
- * @param monitor A logger object. Cannot be null.
- */
- public AdbWrapper(String osSdkPath, ITaskMonitor monitor) {
- mMonitor = monitor;
-
- if (!osSdkPath.endsWith(File.separator)) {
- osSdkPath += File.separator;
- }
- mAdbOsLocation = osSdkPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER
- + SdkConstants.FN_ADB;
- }
-
- private void display(String format, Object...args) {
- mMonitor.log(format, args);
- }
-
- private void displayError(String format, Object...args) {
- mMonitor.logError(format, args);
- }
-
- /**
- * Starts the adb host side server.
- * @return true if success
- */
- public synchronized boolean startAdb() {
- if (mAdbOsLocation == null) {
- displayError("Error: missing path to ADB."); //$NON-NLS-1$
- return false;
- }
-
- Process proc;
- int status = -1;
-
- try {
- ProcessBuilder processBuilder = new ProcessBuilder(
- mAdbOsLocation,
- "start-server"); //$NON-NLS-1$
- proc = processBuilder.start();
- status = proc.waitFor();
-
- // Implementation note: normally on Windows we need to capture stderr/stdout
- // to make sure the process isn't blocked if it's output isn't read. However
- // in this case this happens to hang when reading stdout with no proper way
- // to properly close the streams. On the other hand the output from start
- // server is rather short and not very interesting so we just drop it.
-
- } catch (IOException ioe) {
- displayError("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$
- // we'll return false;
- } catch (InterruptedException ie) {
- displayError("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$
- // we'll return false;
- }
-
- if (status != 0) {
- displayError(String.format(
- "Starting ADB server failed (code %d).", //$NON-NLS-1$
- status));
- return false;
- }
-
- display("Starting ADB server succeeded."); //$NON-NLS-1$
-
- return true;
- }
-
- /**
- * Stops the adb host side server.
- * @return true if success
- */
- public synchronized boolean stopAdb() {
- if (mAdbOsLocation == null) {
- displayError("Error: missing path to ADB."); //$NON-NLS-1$
- return false;
- }
-
- Process proc;
- int status = -1;
-
- try {
- String[] command = new String[2];
- command[0] = mAdbOsLocation;
- command[1] = "kill-server"; //$NON-NLS-1$
- proc = Runtime.getRuntime().exec(command);
- status = proc.waitFor();
-
- // See comment in startAdb about not needing/wanting to capture stderr/stdout.
- }
- catch (IOException ioe) {
- // we'll return false;
- }
- catch (InterruptedException ie) {
- // we'll return false;
- }
-
- // adb kill-server returns:
- // 0 if adb was running and was correctly killed.
- // 1 if adb wasn't running and thus wasn't killed.
- // This error case is not worth reporting.
-
- if (status != 0 && status != 1) {
- displayError(String.format(
- "Stopping ADB server failed (code %d).", //$NON-NLS-1$
- status));
- return false;
- }
-
- display("Stopping ADB server succeeded."); //$NON-NLS-1$
- return true;
- }
-}
+/*
+ * 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.SdkConstants;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A lightweight wrapper to start & stop ADB.
+ * This is <b>specific</b> to the SDK Manager install process.
+ */
+public class AdbWrapper {
+
+ /*
+ * Note: we could bring ddmlib in SdkManager for that purpose, however this allows us to
+ * specialize the start/stop methods to our needs (e.g. a task monitor, etc.)
+ */
+
+ private final String mAdbOsLocation;
+ private final ITaskMonitor mMonitor;
+
+ /**
+ * Creates a new lightweight ADB wrapper.
+ *
+ * @param osSdkPath The root OS path of the SDK. Cannot be null.
+ * @param monitor A logger object. Cannot be null.
+ */
+ public AdbWrapper(String osSdkPath, ITaskMonitor monitor) {
+ mMonitor = monitor;
+
+ if (!osSdkPath.endsWith(File.separator)) {
+ osSdkPath += File.separator;
+ }
+ mAdbOsLocation = osSdkPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER
+ + SdkConstants.FN_ADB;
+ }
+
+ private void display(String format, Object...args) {
+ mMonitor.log(format, args);
+ }
+
+ private void displayError(String format, Object...args) {
+ mMonitor.logError(format, args);
+ }
+
+ /**
+ * Starts the adb host side server.
+ * @return true if success
+ */
+ public synchronized boolean startAdb() {
+ if (mAdbOsLocation == null) {
+ displayError("Error: missing path to ADB."); //$NON-NLS-1$
+ return false;
+ }
+
+ Process proc;
+ int status = -1;
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(
+ mAdbOsLocation,
+ "start-server"); //$NON-NLS-1$
+ proc = processBuilder.start();
+ status = proc.waitFor();
+
+ // Implementation note: normally on Windows we need to capture stderr/stdout
+ // to make sure the process isn't blocked if it's output isn't read. However
+ // in this case this happens to hang when reading stdout with no proper way
+ // to properly close the streams. On the other hand the output from start
+ // server is rather short and not very interesting so we just drop it.
+
+ } catch (IOException ioe) {
+ displayError("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$
+ // we'll return false;
+ } catch (InterruptedException ie) {
+ displayError("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$
+ // we'll return false;
+ }
+
+ if (status != 0) {
+ displayError(String.format(
+ "Starting ADB server failed (code %d).", //$NON-NLS-1$
+ status));
+ return false;
+ }
+
+ display("Starting ADB server succeeded."); //$NON-NLS-1$
+
+ return true;
+ }
+
+ /**
+ * Stops the adb host side server.
+ * @return true if success
+ */
+ public synchronized boolean stopAdb() {
+ if (mAdbOsLocation == null) {
+ displayError("Error: missing path to ADB."); //$NON-NLS-1$
+ return false;
+ }
+
+ Process proc;
+ int status = -1;
+
+ try {
+ String[] command = new String[2];
+ command[0] = mAdbOsLocation;
+ command[1] = "kill-server"; //$NON-NLS-1$
+ proc = Runtime.getRuntime().exec(command);
+ status = proc.waitFor();
+
+ // See comment in startAdb about not needing/wanting to capture stderr/stdout.
+ }
+ catch (IOException ioe) {
+ // we'll return false;
+ }
+ catch (InterruptedException ie) {
+ // we'll return false;
+ }
+
+ // adb kill-server returns:
+ // 0 if adb was running and was correctly killed.
+ // 1 if adb wasn't running and thus wasn't killed.
+ // This error case is not worth reporting.
+
+ if (status != 0 && status != 1) {
+ displayError(String.format(
+ "Stopping ADB server failed (code %d).", //$NON-NLS-1$
+ status));
+ return false;
+ }
+
+ display("Stopping ADB server succeeded."); //$NON-NLS-1$
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
index 216e309..ac7b5d0 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
@@ -1,485 +1,609 @@
-/*
- * Copyright (C) 2010 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.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.repository.SdkAddonsListConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-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;
-
-/**
- * Fetches and loads an sdk-addons-list XML.
- * <p/>
- * Such an XML contains a simple list of add-ons site that are to be loaded by default by the
- * SDK Manager. <br/>
- * The XML must conform to the sdk-addons-list-N.xsd. <br/>
- * Constants used in the XML are defined in {@link SdkAddonsListConstants}.
- */
-public class AddonsListFetcher {
-
- /**
- * An immutable structure representing an add-on site.
- */
- public static class Site {
- private final String mUrl;
- private final String mUiName;
-
- private Site(String url, String uiName) {
- mUrl = url.trim();
- mUiName = uiName;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- public String getUiName() {
- return mUiName;
- }
- }
-
- /**
- * Fetches the addons list from the given URL.
- *
- * @param url The URL of an XML file resource that conforms to the latest sdk-addons-list-N.xsd.
- * For the default operation, use {@link SdkAddonsListConstants#URL_ADDON_LIST}.
- * Cannot be null.
- * @param cache The {@link DownloadCache} instance to use. Cannot be null.
- * @param monitor A monitor to report errors. Cannot be null.
- * @return An array of {@link Site} on success (possibly empty), or null on error.
- */
- public Site[] fetch(String url, DownloadCache cache, ITaskMonitor monitor) {
-
- url = url == null ? "" : url.trim();
-
- monitor.setProgressMax(5);
- monitor.setDescription("Fetching %1$s", url);
- monitor.incProgress(1);
-
- Exception[] exception = new Exception[] { null };
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Document validatedDoc = null;
- String validatedUri = null;
-
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
-
- if (xml != null) {
- monitor.setDescription("Validate XML");
-
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= SdkAddonsListConstants.NS_LATEST_VERSION) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- }
- } else if (version > SdkAddonsListConstants.NS_LATEST_VERSION) {
- // The schema used is more recent than what is supported by this tool.
- // We don't have an upgrade-path support yet, so simply ignore the document.
- return null;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- } else if (exception[0] instanceof UnknownHostException &&
- exception[0].getMessage() != null) {
- // This has no useful getMessage yet could really use one
- reason = String.format("Unknown Host %1$s", exception[0].getMessage());
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- } else if (exception[0].getMessage() != null) {
- reason = exception[0].getMessage();
- } else {
- // We don't know what's wrong. Let's give the exception class at least.
- reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- return null;
- }
-
- monitor.incProgress(1);
-
- Site[] result = null;
-
- if (xml != null) {
- monitor.setDescription("Parse XML");
- monitor.incProgress(1);
- result = parseAddonsList(validatedDoc, validatedUri, monitor);
- }
-
- // done
- monitor.incProgress(1);
-
- return result;
- }
-
- /**
- * Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens. References: <br/>
- * URL Connection:
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- * @see UrlOpener UrlOpener, which handles all URL logic.
- */
- private InputStream fetchUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- return cache.openCachedUrl(urlString, monitor);
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
- * declaration.
- *
- * @return 1..{@link SdkAddonsListConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-addons-list
- // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(SdkAddonsListConstants.NS_PATTERN);
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkAddonsListConstants.NODE_SDK_ADDONS_LIST.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return SdkAddonsListConstants.getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkAddonsListConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = SdkAddonsListConstants.getXsdStream(version);
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // 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 == null ? null : schema.newValidator();
-
- return validator;
- }
-
- /**
- * Takes an XML document as a string as parameter and returns a DOM for it.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-
- /**
- * Parse all sites defined in the Addaons list XML and returns an array of sites.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Site[] parseAddonsList(Document doc, String nsUri, ITaskMonitor monitor) {
-
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl != null) {
- if (baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
- baseUrl = null;
- }
- }
-
- Node root = getFirstChild(doc, nsUri, SdkAddonsListConstants.NODE_SDK_ADDONS_LIST);
- if (root != null) {
- ArrayList<Site> sites = new ArrayList<Site>();
-
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- child.getLocalName().equals(SdkAddonsListConstants.NODE_ADDON_SITE)) {
-
- Node url = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_URL);
- Node name = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_NAME);
-
- if (name != null && url != null) {
- String strUrl = url.getTextContent().trim();
- String strName = name.getTextContent().trim();
-
- if (baseUrl != null &&
- strUrl.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
- strUrl = baseUrl +
- strUrl.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
- }
-
- if (strUrl.length() > 0 && strName.length() > 0) {
- sites.add(new Site(strUrl, strName));
- }
- }
- }
- }
-
- return sites.toArray(new Site[sites.size()]);
- }
-
- return null;
- }
-
- /**
- * 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 nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
- return child;
- }
- }
- }
-
- return null;
- }
-
-
-}
+/*
+ * Copyright (C) 2010 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.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
+import com.android.sdklib.repository.SdkAddonsListConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.SSLKeyException;
+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;
+
+/**
+ * Fetches and loads an sdk-addons-list XML.
+ * <p/>
+ * Such an XML contains a simple list of add-ons site that are to be loaded by default by the
+ * SDK Manager. <br/>
+ * The XML must conform to the sdk-addons-list-N.xsd. <br/>
+ * Constants used in the XML are defined in {@link SdkAddonsListConstants}.
+ */
+public class AddonsListFetcher {
+
+ public enum SiteType {
+ ADDON_SITE,
+ SYS_IMG_SITE
+ }
+
+ /**
+ * An immutable structure representing an add-on site.
+ */
+ public static class Site {
+ private final String mUrl;
+ private final String mUiName;
+ private final SiteType mType;
+
+ private Site(String url, String uiName, SiteType type) {
+ mType = type;
+ mUrl = url.trim();
+ mUiName = uiName;
+ }
+
+ public String getUrl() {
+ return mUrl;
+ }
+
+ public String getUiName() {
+ return mUiName;
+ }
+
+ public SiteType getType() {
+ return mType;
+ }
+
+ /** Returns a debug string representation of this object. Not for user display. */
+ @Override
+ public String toString() {
+ return String.format("<%1$s URL='%2$s' Name='%3$s'>", //$NON-NLS-1$
+ mType, mUrl, mUiName);
+ }
+ }
+
+ /**
+ * Fetches the addons list from the given URL.
+ *
+ * @param url The URL of an XML file resource that conforms to the latest sdk-addons-list-N.xsd.
+ * For the default operation, use {@link SdkAddonsListConstants#URL_ADDON_LIST}.
+ * Cannot be null.
+ * @param cache The {@link DownloadCache} instance to use. Cannot be null.
+ * @param monitor A monitor to report errors. Cannot be null.
+ * @return An array of {@link Site} on success (possibly empty), or null on error.
+ */
+ public Site[] fetch(String url, DownloadCache cache, ITaskMonitor monitor) {
+
+ url = url == null ? "" : url.trim();
+
+ monitor.setProgressMax(6);
+ monitor.setDescription("Fetching %1$s", url);
+ monitor.incProgress(1);
+
+ Exception[] exception = new Exception[] { null };
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ Document validatedDoc = null;
+ String validatedUri = null;
+
+ String[] defaultNames = new String[SdkAddonsListConstants.NS_LATEST_VERSION];
+ for (int version = SdkAddonsListConstants.NS_LATEST_VERSION, i = 0;
+ version >= 1;
+ version--, i++) {
+ defaultNames[i] = SdkAddonsListConstants.getDefaultName(version);
+ }
+
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
+ if (xml != null) {
+ int version = getXmlSchemaVersion(xml);
+ if (version == 0) {
+ closeStream(xml);
+ xml = null;
+ }
+ }
+
+ String baseUrl = url;
+ if (!baseUrl.endsWith("/")) { //$NON-NLS-1$
+ int pos = baseUrl.lastIndexOf('/');
+ if (pos > 0) {
+ baseUrl = baseUrl.substring(0, pos + 1);
+ }
+ }
+
+ // If we can't find the latest version, try earlier schema versions.
+ if (xml == null && defaultNames.length > 0) {
+ ITaskMonitor subMonitor = monitor.createSubMonitor(1);
+ subMonitor.setProgressMax(defaultNames.length);
+
+ for (String name : defaultNames) {
+ String newUrl = baseUrl + name;
+ if (newUrl.equals(url)) {
+ continue;
+ }
+ xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
+ if (xml != null) {
+ int version = getXmlSchemaVersion(xml);
+ if (version == 0) {
+ closeStream(xml);
+ xml = null;
+ } else {
+ url = newUrl;
+ subMonitor.incProgress(
+ subMonitor.getProgressMax() - subMonitor.getProgress());
+ break;
+ }
+ }
+ }
+ } else {
+ monitor.incProgress(1);
+ }
+
+ if (xml != null) {
+ monitor.setDescription("Validate XML");
+
+ // Explore the XML to find the potential XML schema version
+ int version = getXmlSchemaVersion(xml);
+
+ if (version >= 1 && version <= SdkAddonsListConstants.NS_LATEST_VERSION) {
+ // This should be a version we can handle. Try to validate it
+ // and report any error as invalid XML syntax,
+
+ String uri = validateXml(xml, url, version, validationError, validatorFound);
+ if (uri != null) {
+ // Validation was successful
+ validatedDoc = getDocument(xml, monitor);
+ validatedUri = uri;
+
+ }
+ } else if (version > SdkAddonsListConstants.NS_LATEST_VERSION) {
+ // The schema used is more recent than what is supported by this tool.
+ // We don't have an upgrade-path support yet, so simply ignore the document.
+ closeStream(xml);
+ return null;
+ }
+ }
+
+ // If any exception was handled during the URL fetch, display it now.
+ if (exception[0] != null) {
+ String reason = null;
+ if (exception[0] instanceof FileNotFoundException) {
+ // FNF has no useful getMessage, so we need to special handle it.
+ reason = "File not found";
+ } else if (exception[0] instanceof UnknownHostException &&
+ exception[0].getMessage() != null) {
+ // This has no useful getMessage yet could really use one
+ reason = String.format("Unknown Host %1$s", exception[0].getMessage());
+ } else if (exception[0] instanceof SSLKeyException) {
+ // That's a common error and we have a pref for it.
+ reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
+ } else if (exception[0].getMessage() != null) {
+ reason = exception[0].getMessage();
+ } else {
+ // We don't know what's wrong. Let's give the exception class at least.
+ reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
+ }
+
+ monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
+ }
+
+ if (validationError[0] != null) {
+ monitor.logError("%s", validationError[0]); //$NON-NLS-1$
+ }
+
+ // Stop here if we failed to validate the XML. We don't want to load it.
+ if (validatedDoc == null) {
+ closeStream(xml);
+ return null;
+ }
+
+ monitor.incProgress(1);
+
+ Site[] result = null;
+
+ if (xml != null) {
+ monitor.setDescription("Parse XML");
+ monitor.incProgress(1);
+ result = parseAddonsList(validatedDoc, validatedUri, baseUrl, monitor);
+ }
+
+ // done
+ monitor.incProgress(1);
+
+ closeStream(xml);
+ return result;
+ }
+
+ /**
+ * Fetches the document at the given URL and returns it as a stream. Returns
+ * null if anything wrong happens.
+ *
+ * @param urlString The URL to load, as a string.
+ * @param monitor {@link ITaskMonitor} related to this URL.
+ * @param outException If non null, where to store any exception that
+ * happens during the fetch.
+ * @see UrlOpener UrlOpener, which handles all URL logic.
+ */
+ private InputStream fetchXmlUrl(String urlString,
+ DownloadCache cache,
+ ITaskMonitor monitor,
+ Exception[] outException) {
+ try {
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml);
+ ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
+ } catch (Exception e) {
+ if (outException != null) {
+ outException[0] = e;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
+ * Manually parses the root element of the XML to extract the schema version
+ * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
+ * declaration.
+ *
+ * @return 1..{@link SdkAddonsListConstants#NS_LATEST_VERSION} for a valid schema version
+ * or 0 if no schema could be found.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected int getXmlSchemaVersion(InputStream xml) {
+ if (xml == null) {
+ return 0;
+ }
+
+ // Get an XML document
+ Document doc = null;
+ try {
+ assert xml.markSupported();
+ xml.reset();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(false);
+ factory.setValidating(false);
+
+ // Parse the old document using a non namespace aware builder
+ factory.setNamespaceAware(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // We don't want the default handler which prints errors to stderr.
+ builder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+ // pass
+ }
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+
+ doc = builder.parse(xml);
+
+ // Prepare a new document using a namespace aware builder
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+
+ } catch (Exception e) {
+ // Failed to reset XML stream
+ // Failed to get builder factor
+ // Failed to create XML document builder
+ // Failed to parse XML document
+ // Failed to read XML document
+ //--For debug--System.err.println("getXmlSchemaVersion exception: " + e.toString());
+ }
+
+ if (doc == null) {
+ return 0;
+ }
+
+ // Check the root element is an XML with at least the following properties:
+ // <sdk:sdk-addons-list
+ // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
+ //
+ // Note that we don't have namespace support enabled, we just do it manually.
+
+ Pattern nsPattern = Pattern.compile(SdkAddonsListConstants.NS_PATTERN);
+
+ String prefix = null;
+ for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ prefix = null;
+ String name = child.getNodeName();
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ prefix = name.substring(0, pos);
+ name = name.substring(pos + 1);
+ }
+ if (SdkAddonsListConstants.NODE_SDK_ADDONS_LIST.equals(name)) {
+ NamedNodeMap attrs = child.getAttributes();
+ String xmlns = "xmlns"; //$NON-NLS-1$
+ if (prefix != null) {
+ xmlns += ":" + prefix; //$NON-NLS-1$
+ }
+ Node attr = attrs.getNamedItem(xmlns);
+ if (attr != null) {
+ String uri = attr.getNodeValue();
+ if (uri != null) {
+ Matcher m = nsPattern.matcher(uri);
+ if (m.matches()) {
+ String version = m.group(1);
+ try {
+ return Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Validates this XML against one of the requested SDK Repository schemas.
+ * If the XML was correctly validated, returns the schema that worked.
+ * If it doesn't validate, returns null and stores the error in outError[0].
+ * If we can't find a validator, returns null and set validatorFound[0] to false.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected String validateXml(InputStream xml, String url, int version,
+ String[] outError, Boolean[] validatorFound) {
+
+ if (xml == null) {
+ return null;
+ }
+
+ try {
+ Validator validator = getValidator(version);
+
+ if (validator == null) {
+ validatorFound[0] = Boolean.FALSE;
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
+ url);
+ return null;
+ }
+
+ validatorFound[0] = Boolean.TRUE;
+
+ // Reset the stream if it supports that operation.
+ assert xml.markSupported();
+ xml.reset();
+
+ // Validation throws a bunch of possible Exceptions on failure.
+ validator.validate(new StreamSource(xml));
+ return SdkAddonsListConstants.getSchemaUri(version);
+
+ } catch (SAXParseException e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
+ url,
+ e.getLineNumber(),
+ e.getColumnNumber(),
+ e.toString());
+
+ } catch (Exception e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nError: %2$s",
+ url,
+ e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Helper method that returns a validator for our XSD, or null if the current Java
+ * implementation can't process XSD schemas.
+ *
+ * @param version The version of the XML Schema.
+ * See {@link SdkAddonsListConstants#getXsdStream(int)}
+ */
+ private Validator getValidator(int version) throws SAXException {
+ InputStream xsdStream = SdkAddonsListConstants.getXsdStream(version);
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+ if (factory == null) {
+ return null;
+ }
+
+ // 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 == null ? null : schema.newValidator();
+
+ return validator;
+ }
+
+ /**
+ * Takes an XML document as a string as parameter and returns a DOM for it.
+ *
+ * On error, returns null and prints a (hopefully) useful message on the monitor.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(true);
+ factory.setNamespaceAware(true);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ assert xml.markSupported();
+ xml.reset();
+ Document doc = builder.parse(new InputSource(xml));
+
+ return doc;
+ } catch (ParserConfigurationException e) {
+ monitor.logError("Failed to create XML document builder");
+
+ } catch (SAXException e) {
+ monitor.logError("Failed to parse XML document");
+
+ } catch (IOException e) {
+ monitor.logError("Failed to read XML document");
+ }
+
+ return null;
+ }
+
+ /**
+ * Parse all sites defined in the Addaons list XML and returns an array of sites.
+ *
+ * @param doc The XML DOM to parse.
+ * @param nsUri The addons-list schema URI of the document.
+ * @param baseUrl The base URL of the caller (e.g. where addons-list-N.xml was fetched from.)
+ * @param monitor A non-null monitor to print to.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Site[] parseAddonsList(
+ Document doc,
+ String nsUri,
+ String baseUrl,
+ ITaskMonitor monitor) {
+
+ String testBaseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
+ if (testBaseUrl != null) {
+ if (testBaseUrl.length() <= 0 || !testBaseUrl.endsWith("/")) { //$NON-NLS-1$
+ testBaseUrl = null;
+ }
+ }
+
+ Node root = getFirstChild(doc, nsUri, SdkAddonsListConstants.NODE_SDK_ADDONS_LIST);
+ if (root != null) {
+ ArrayList<Site> sites = new ArrayList<Site>();
+
+ for (Node child = root.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+
+ String elementName = child.getLocalName();
+ SiteType type = null;
+
+ if (SdkAddonsListConstants.NODE_SYS_IMG_SITE.equals(elementName)) {
+ type = SiteType.SYS_IMG_SITE;
+
+ } else if (SdkAddonsListConstants.NODE_ADDON_SITE.equals(elementName)) {
+ type = SiteType.ADDON_SITE;
+ }
+
+ // Not an addon-site nor a sys-img-site, don't process this.
+ if (type == null) {
+ continue;
+ }
+
+ Node url = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_URL);
+ Node name = getFirstChild(child, nsUri, SdkAddonsListConstants.NODE_NAME);
+
+ if (name != null && url != null) {
+ String strUrl = url.getTextContent().trim();
+ String strName = name.getTextContent().trim();
+
+ if (testBaseUrl != null &&
+ strUrl.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
+ strUrl = testBaseUrl +
+ strUrl.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
+ } else if (!strUrl.startsWith("http://") && //$NON-NLS-1$
+ !strUrl.startsWith("https://")) { //$NON-NLS-1$
+ // This looks like a relative URL, add the fetcher's base URL to it.
+ strUrl = baseUrl + strUrl;
+ }
+
+ if (strUrl.length() > 0 && strName.length() > 0) {
+ sites.add(new Site(strUrl, strName, type));
+ }
+ }
+ }
+ }
+
+ return sites.toArray(new Site[sites.size()]);
+ }
+
+ return null;
+ }
+
+ /**
+ * 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 nsUri, String xmlLocalName) {
+
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+ if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
+ return child;
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/CanceledByUserException.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/CanceledByUserException.java
new file mode 100755
index 0000000..a0a74d8
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/CanceledByUserException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * Exception thrown by {@link DownloadCache} and {@link UrlOpener} when a user
+ * cancels an HTTP Basic authentication or NTML authentication dialog.
+ */
+public class CanceledByUserException extends Exception {
+ private static final long serialVersionUID = -7669346110926032403L;
+
+ public CanceledByUserException(String message) {
+ super(message);
+ }
+}
+
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
index 039e165..e02023d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
@@ -16,14 +16,14 @@
package com.android.sdklib.internal.repository;
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.internal.repository.UrlOpener.CanceledByUserException;
-import com.android.util.Pair;
+import com.android.utils.Pair;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
@@ -50,7 +50,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* A simple cache for the XML resources handled by the SDK Manager.
* <p/>
- * Callers should use {@link #openDirectUrl(String, ITaskMonitor)} to download "large files"
+ * Callers should use {@link #openDirectUrl} to download "large files"
* that should not be cached (like actual installation packages which are several MBs big)
* and call {@link #openCachedUrl(String, ITaskMonitor)} to download small XML files.
* <p/>
@@ -71,7 +71,7 @@ public class DownloadCache {
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
*/
- private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null;
+ private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null; //$NON-NLS-1$
/** Key for the Status-Code in the info properties. */
private static final String KEY_STATUS_CODE = "Status-Code"; //$NON-NLS-1$
@@ -79,9 +79,11 @@ public class DownloadCache {
private static final String KEY_URL = "URL"; //$NON-NLS-1$
/** Prefix of binary files stored in the {@link SdkConstants#FD_CACHE} directory. */
- private final static String BIN_FILE_PREFIX = "sdkbin-"; //$NON-NLS-1$
+ private final static String BIN_FILE_PREFIX = "sdkbin"; //$NON-NLS-1$
/** Prefix of meta info files stored in the {@link SdkConstants#FD_CACHE} directory. */
- private final static String INFO_FILE_PREFIX = "sdkinf-"; //$NON-NLS-1$
+ private final static String INFO_FILE_PREFIX = "sdkinf"; //$NON-NLS-1$
+ /* Revision suffixed to the prefix. */
+ private final static String REV_FILE_PREFIX = "-1_"; //$NON-NLS-1$
/**
* Minimum time before we consider a cached entry is potentially stale.
@@ -134,6 +136,12 @@ public class DownloadCache {
public enum Strategy {
/**
+ * Exclusively serves data from the cache. If files are available in the
+ * cache, serve them as is (without trying to refresh them). If files are
+ * not available, they are <em>not</em> fetched at all.
+ */
+ ONLY_CACHE,
+ /**
* If the files are available in the cache, serve them as-is, otherwise
* download them and return the cached version. No expiration or refresh
* is attempted if a file is in the cache.
@@ -156,6 +164,12 @@ public class DownloadCache {
/** Creates a default instance of the URL cache */
public DownloadCache(Strategy strategy) {
mCacheRoot = initCacheRoot();
+
+ // If this is defined in the environment, never use the cache. Useful for testing.
+ if (System.getenv("SDKMAN_DISABLE_CACHE") != null) { //$NON-NLS-1$
+ strategy = Strategy.DIRECT;
+ }
+
mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
}
@@ -163,6 +177,81 @@ public class DownloadCache {
return mStrategy;
}
+ public File getCacheRoot() {
+ return mCacheRoot;
+ }
+
+ /**
+ * Computes the size of the cached files.
+ *
+ * @return The sum of the byte size of the cached files.
+ */
+ public long getCurrentSize() {
+ long size = 0;
+
+ if (mCacheRoot != null) {
+ File[] files = mCacheRoot.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ if (f.isFile()) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) ||
+ name.startsWith(INFO_FILE_PREFIX)) {
+ size += f.length();
+ }
+ }
+ }
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * Removes all cached files from the cache directory.
+ */
+ public void clearCache() {
+ if (mCacheRoot != null) {
+ File[] files = mCacheRoot.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ if (f.isFile()) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) ||
+ name.startsWith(INFO_FILE_PREFIX)) {
+ f.delete();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes all obsolete cached files from the cache directory
+ * that do not match the latest revision.
+ */
+ public void clearOldCache() {
+ String prefix1 = BIN_FILE_PREFIX + REV_FILE_PREFIX;
+ String prefix2 = INFO_FILE_PREFIX + REV_FILE_PREFIX;
+ if (mCacheRoot != null) {
+ File[] files = mCacheRoot.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ if (f.isFile()) {
+ String name = f.getName();
+ if (name.startsWith(BIN_FILE_PREFIX) ||
+ name.startsWith(INFO_FILE_PREFIX)) {
+ if (!name.startsWith(prefix1) && !name.startsWith(prefix2)) {
+ f.delete();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Returns the directory to be used as a cache.
* Creates it if necessary.
@@ -192,26 +281,82 @@ public class DownloadCache {
* Instead the HttpClient library returns a progressive download stream.
* <p/>
* For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, ITaskMonitor, Header[])}
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
* documentation.
+ * <p/>
+ * The resulting input stream may not support mark/reset.
*
* @param urlString the URL string to be opened.
+ * @param headers An optional set of headers to pass when requesting the resource. Can be null.
* @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
- * @return Returns an {@link InputStream} holding the URL content.
+ * fetching.
+ * @return Returns a pair with a {@link InputStream} and an {@link HttpResponse}.
+ * The pair is never null.
+ * The input stream can be null in case of error, although in general the
+ * method will probably throw an exception instead.
+ * The caller should look at the response code's status and only accept the
+ * input stream if it's the desired code (e.g. 200 or 206).
* @throws IOException Exception thrown when there are problems retrieving
- * the URL or its content.
+ * the URL or its content.
* @throws CanceledByUserException Exception thrown if the user cancels the
* authentication dialog.
*/
- public InputStream openDirectUrl(String urlString, ITaskMonitor monitor)
- throws IOException, CanceledByUserException {
+ public Pair<InputStream, HttpResponse> openDirectUrl(
+ @NonNull String urlString,
+ @Nullable Header[] headers,
+ @NonNull ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
+ if (DEBUG) {
+ System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
+ }
+ return UrlOpener.openUrl(
+ urlString,
+ false /*needsMarkResetSupport*/,
+ monitor,
+ headers);
+ }
+
+ /**
+ * This is a simplified convenience method that calls
+ * {@link #openDirectUrl(String, Header[], ITaskMonitor)}
+ * without passing any specific HTTP headers and returns the resulting input stream
+ * and the HTTP status code.
+ * See the original method's description for details on its behavior.
+ * <p/>
+ * {@link #openDirectUrl(String, Header[], ITaskMonitor)} can accept customized
+ * HTTP headers to send with the requests and also returns the full HTTP
+ * response -- status line with code and protocol and all headers.
+ * <p/>
+ * The resulting input stream may not support mark/reset.
+ *
+ * @param urlString the URL string to be opened.
+ * @param monitor {@link ITaskMonitor} which is related to this URL
+ * fetching.
+ * @return Returns a pair with a {@link InputStream} and an HTTP status code.
+ * The pair is never null.
+ * The input stream can be null in case of error, although in general the
+ * method will probably throw an exception instead.
+ * The caller should look at the response code's status and only accept the
+ * input stream if it's the desired code (e.g. 200 or 206).
+ * @throws IOException Exception thrown when there are problems retrieving
+ * the URL or its content.
+ * @throws CanceledByUserException Exception thrown if the user cancels the
+ * authentication dialog.
+ * @see #openDirectUrl(String, Header[], ITaskMonitor)
+ */
+ public Pair<InputStream, Integer> openDirectUrl(
+ @NonNull String urlString,
+ @NonNull ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
if (DEBUG) {
System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
}
- Pair<InputStream, HttpResponse> result =
- UrlOpener.openUrl(urlString, monitor, null /*headers*/);
- return result.getFirst();
+ Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ urlString,
+ false /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
+ return Pair.of(result.getFirst(), result.getSecond().getStatusLine().getStatusCode());
}
/**
@@ -220,17 +365,18 @@ public class DownloadCache {
* from the cache, potentially updated first or directly downloaded.
* <p/>
* For large downloads (e.g. installable archives) please do not invoke the
- * cache and instead use the {@link #openDirectUrl(String, ITaskMonitor)}
- * method.
+ * cache and instead use the {@link #openDirectUrl} method.
* <p/>
* For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, ITaskMonitor, Header[])}
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
* documentation.
*
* @param urlString the URL string to be opened.
* @param monitor {@link ITaskMonitor} which is related to this URL
* fetching.
* @return Returns an {@link InputStream} holding the URL content.
+ * Returns null if there's no content (e.g. resource not found.)
+ * Returns null if the document is not cached and strategy is {@link Strategy#ONLY_CACHE}.
* @throws IOException Exception thrown when there are problems retrieving
* the URL or its content.
* @throws CanceledByUserException Exception thrown if the user cancels the
@@ -238,9 +384,14 @@ public class DownloadCache {
*/
public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
throws IOException, CanceledByUserException {
- // Don't cache in direct mode. Don't try to cache non-http URLs.
- if (mStrategy == Strategy.DIRECT || !urlString.startsWith("http")) { //$NON-NLS-1$
- return openDirectUrl(urlString, monitor);
+ // Don't cache in direct mode.
+ if (mStrategy == Strategy.DIRECT) {
+ Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ urlString,
+ true /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
+ return result.getFirst();
}
File cached = new File(mCacheRoot, getCacheFilename(urlString));
@@ -408,6 +559,14 @@ public class DownloadCache {
} catch (IOException ignore) {}
}
+ if (!useCached && mStrategy == Strategy.ONLY_CACHE) {
+ // We don't have a document to serve from the cache.
+ if (DEBUG) {
+ System.out.println(String.format("%s : file not in cache", urlString)); //$NON-NLS-1$
+ }
+ return null;
+ }
+
// If we're not using the cache, try to remove the cache and download again.
try {
cached.delete();
@@ -418,6 +577,8 @@ public class DownloadCache {
null /*headers*/, null /*statusCode*/);
}
+
+
// --------------
private InputStream readCachedFile(File cached) throws IOException {
@@ -488,7 +649,8 @@ public class DownloadCache {
byte[] result = new byte[inc];
try {
- Pair<InputStream, HttpResponse> r = UrlOpener.openUrl(urlString, monitor, headers);
+ Pair<InputStream, HttpResponse> r =
+ UrlOpener.openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
is = r.getFirst();
HttpResponse response = r.getSecond();
@@ -653,12 +815,13 @@ public class DownloadCache {
leaf = leaf.replaceAll("__+", "_");
leaf = hash + '-' + leaf;
- int n = 64 - BIN_FILE_PREFIX.length();
+ String prefix = BIN_FILE_PREFIX + REV_FILE_PREFIX;
+ int n = 64 - prefix.length();
if (leaf.length() > n) {
leaf = leaf.substring(0, n);
}
- return BIN_FILE_PREFIX + leaf;
+ return prefix + leaf;
}
private String getInfoFilename(String cacheFilename) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IDescription.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IDescription.java
index 7af92e2..5662a9c 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IDescription.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IDescription.java
@@ -1,40 +1,40 @@
-/*
- * 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;
-
-/**
- * Interface for elements that can provide a description of themselves.
- */
-public interface IDescription {
-
- /**
- * Returns a description of the given element. Cannot be null.
- * <p/>
- * A description is a multi-line of text, typically much more
- * elaborate than what {@link #toString()} would provide.
- */
- public abstract String getShortDescription();
-
- /**
- * Returns a description of the given element. Cannot be null.
- * <p/>
- * A description is a multi-line of text, typically much more
- * elaborate than what {@link #toString()} would provide.
- */
- public abstract String getLongDescription();
-
-}
+/*
+ * 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;
+
+/**
+ * Interface for elements that can provide a description of themselves.
+ */
+public interface IDescription {
+
+ /**
+ * Returns a description of the given element. Cannot be null.
+ * <p/>
+ * A description is a multi-line of text, typically much more
+ * elaborate than what {@link #toString()} would provide.
+ */
+ public abstract String getShortDescription();
+
+ /**
+ * Returns a description of the given element. Cannot be null.
+ * <p/>
+ * A description is a multi-line of text, typically much more
+ * elaborate than what {@link #toString()} would provide.
+ */
+ public abstract String getLongDescription();
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITask.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITask.java
index 9178460..5e561eb 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITask.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITask.java
@@ -1,26 +1,26 @@
-/*
- * 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;
-
-
-/**
- * A task that executes and can update a monitor to display its status.
- * The task will generally be run in a separate thread.
- */
-public interface ITask {
- public abstract void run(ITaskMonitor monitor);
-}
+/*
+ * 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;
+
+
+/**
+ * A task that executes and can update a monitor to display its status.
+ * The task will generally be run in a separate thread.
+ */
+public interface ITask {
+ public abstract void run(ITaskMonitor monitor);
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
index fb59b42..959549b 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
@@ -1,45 +1,59 @@
-/*
- * 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;
-
-/**
- * A factory that can start and run new {@link ITask}s.
- */
-public interface ITaskFactory {
-
- /**
- * Starts a new task with a new {@link ITaskMonitor}.
- *
- * @param title The title of the task, displayed in the monitor if any.
- * @param task The task to run.
- */
- public abstract void start(String title, ITask task);
-
- /**
- * Starts a new task contributing to an already existing {@link ITaskMonitor}.
- * <p/>
- * To use this properly, you should use {@link ITaskMonitor#createSubMonitor(int)}
- * and give the sub-monitor to the new task with the number of work units you want
- * it to fill. The {@link #start} method will make sure to <em>fill</em> the progress
- * when the task is completed, in case the actual task did not.
- *
- * @param title The title of the task, displayed in the monitor if any.
- * @param parentMonitor The parent monitor. Can be null.
- * @param task The task to run and have it display on the monitor.
- */
- public abstract void start(String title, ITaskMonitor parentMonitor, ITask task);
-}
+/*
+ * 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;
+
+
+/**
+ * A factory that can start and run new {@link ITask}s.
+ */
+public interface ITaskFactory {
+
+ /**
+ * Starts a new task with a new {@link ITaskMonitor}.
+ * <p/>
+ * The task will execute in a thread and runs it own UI loop.
+ * This means the task can perform UI operations using
+ * {@code Display#asyncExec(Runnable)}.
+ * <p/>
+ * In either case, the method only returns when the task has finished.
+ *
+ * @param title The title of the task, displayed in the monitor if any.
+ * @param task The task to run.
+ */
+ public abstract void start(String title, ITask task);
+
+ /**
+ * Starts a new task contributing to an already existing {@link ITaskMonitor}.
+ * <p/>
+ * To use this properly, you should use {@link ITaskMonitor#createSubMonitor(int)}
+ * and give the sub-monitor to the new task with the number of work units you want
+ * it to fill. The {@link #start} method will make sure to <em>fill</em> the progress
+ * when the task is completed, in case the actual task did not.
+ * <p/>
+ * When a task is started from within a monitor, it reuses the thread
+ * from the parent. Otherwise it starts a new thread and runs it own
+ * UI loop. This means the task can perform UI operations using
+ * {@code Display#asyncExec(Runnable)}.
+ * <p/>
+ * In either case, the method only returns when the task has finished.
+ *
+ * @param title The title of the task, displayed in the monitor if any.
+ * @param parentMonitor The parent monitor. Can be null.
+ * @param task The task to run and have it display on the monitor.
+ */
+ public abstract void start(String title, ITaskMonitor parentMonitor, ITask task);
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
index dcb1f22..74dd14a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
@@ -1,147 +1,147 @@
-/*
- * 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.ISdkLog;
-
-
-/**
- * A monitor interface for a {@link ITask}.
- * <p/>
- * Depending on the task factory that created the task, there might not be any UI
- * or it might not implement all the methods, in which case calling them would be
- * a no-op but is guaranteed not to crash.
- * <p/>
- * If the task runs in a non-UI worker thread, the task factory implementation
- * will take care of the update the UI in the correct thread. The task itself
- * must not have to deal with it.
- * <p/>
- * A monitor typically has 3 levels of text displayed: <br/>
- * - A <b>title</b> <em>may</em> be present on a task dialog, typically when a task
- * dialog is created. This is not covered by this monitor interface. <br/>
- * - A <b>description</b> displays prominent information on what the task
- * is currently doing. This is expected to vary over time, typically changing
- * with each sub-monitor, and typically only the last description is visible.
- * For example an updater would typically have descriptions such as "downloading",
- * "installing" and finally "done". This is set using {@link #setDescription}. <br/>
- * - A <b>verbose</b> optional log that can provide more information than the summary
- * description and is typically displayed in some kind of scrollable multi-line
- * text field so that the user can keep track of what happened. 3 levels are
- * provided: error, normal and verbose. An UI may hide the log till an error is
- * logged and/or might hide the verbose text unless a flag is checked by the user.
- * This is set using {@link #log}, {@link #logError} and {@link #logVerbose}.
- * <p/>
- * A monitor is also an {@link ISdkLog} implementation.
- */
-public interface ITaskMonitor extends ISdkLog {
-
- /**
- * Sets the description in the current task dialog.
- * This method can be invoked from a non-UI thread.
- */
- public void setDescription(String format, Object...args);
-
- /**
- * Logs a "normal" information line.
- * This method can be invoked from a non-UI thread.
- */
- public void log(String format, Object...args);
-
- /**
- * Logs an "error" information line.
- * This method can be invoked from a non-UI thread.
- */
- public void logError(String format, Object...args);
-
- /**
- * Logs a "verbose" information line, that is extra details which are typically
- * not that useful for the end-user and might be hidden until explicitly shown.
- * This method can be invoked from a non-UI thread.
- */
- public void logVerbose(String format, Object...args);
-
- /**
- * Sets the max value of the progress bar.
- * This method can be invoked from a non-UI thread.
- *
- * This method MUST be invoked once before using {@link #incProgress(int)} or
- * {@link #getProgress()} or {@link #createSubMonitor(int)}. Callers are
- * discouraged from using more than once -- implementations can either discard
- * the next calls or behave incoherently.
- */
- public void setProgressMax(int max);
-
- /**
- * Returns the max valie of the progress bar, as last set by {@link #setProgressMax(int)}.
- * Returns 0 if the max has never been set yet.
- */
- public int getProgressMax();
-
- /**
- * Increments the current value of the progress bar.
- * This method can be invoked from a non-UI thread.
- *
- * Callers MUST use setProgressMax before using this method.
- */
- public void incProgress(int delta);
-
- /**
- * Returns the current value of the progress bar,
- * between 0 and up to {@link #setProgressMax(int)} - 1.
- *
- * Callers MUST use setProgressMax before using this method.
- */
- public int getProgress();
-
- /**
- * Returns true if the user requested to cancel the operation.
- * It is up to the task thread to pool this and exit as soon
- * as possible.
- */
- public boolean isCancelRequested();
-
- /**
- * Creates a sub-monitor that will use up to tickCount on the progress bar.
- * tickCount must be 1 or more.
- */
- public ITaskMonitor createSubMonitor(int tickCount);
-
- /**
- * Display a yes/no question dialog box.
- *
- * Implementations MUST allow this to be called from any thread, e.g. by
- * making sure the dialog is opened synchronously in the ui thread.
- *
- * @param title The title of the dialog box
- * @param message The error message
- * @return true if YES was clicked.
- */
- public boolean displayPrompt(final String title, final String message);
-
- /**
- * Launch an interface which asks for user credentials. Implementations
- * MUST allow this to be called from any thread, e.g. by making sure the
- * dialog is opened synchronously in the UI thread.
- *
- * @param title The title of the dialog box.
- * @param message The message to be displayed as an instruction.
- * @return Returns the user provided credentials. Some fields may be blank if the user
- * did not provide any input.
- If operation is <b>canceled</b> by user the return value must be <b>null</b>.
- */
+/*
+ * 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.utils.ILogger;
+
+
+/**
+ * A monitor interface for a {@link ITask}.
+ * <p/>
+ * Depending on the task factory that created the task, there might not be any UI
+ * or it might not implement all the methods, in which case calling them would be
+ * a no-op but is guaranteed not to crash.
+ * <p/>
+ * If the task runs in a non-UI worker thread, the task factory implementation
+ * will take care of the update the UI in the correct thread. The task itself
+ * must not have to deal with it.
+ * <p/>
+ * A monitor typically has 3 levels of text displayed: <br/>
+ * - A <b>title</b> <em>may</em> be present on a task dialog, typically when a task
+ * dialog is created. This is not covered by this monitor interface. <br/>
+ * - A <b>description</b> displays prominent information on what the task
+ * is currently doing. This is expected to vary over time, typically changing
+ * with each sub-monitor, and typically only the last description is visible.
+ * For example an updater would typically have descriptions such as "downloading",
+ * "installing" and finally "done". This is set using {@link #setDescription}. <br/>
+ * - A <b>verbose</b> optional log that can provide more information than the summary
+ * description and is typically displayed in some kind of scrollable multi-line
+ * text field so that the user can keep track of what happened. 3 levels are
+ * provided: error, normal and verbose. An UI may hide the log till an error is
+ * logged and/or might hide the verbose text unless a flag is checked by the user.
+ * This is set using {@link #log}, {@link #logError} and {@link #logVerbose}.
+ * <p/>
+ * A monitor is also an {@link ILogger} implementation.
+ */
+public interface ITaskMonitor extends ILogger {
+
+ /**
+ * Sets the description in the current task dialog.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setDescription(String format, Object...args);
+
+ /**
+ * Logs a "normal" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void log(String format, Object...args);
+
+ /**
+ * Logs an "error" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logError(String format, Object...args);
+
+ /**
+ * Logs a "verbose" information line, that is extra details which are typically
+ * not that useful for the end-user and might be hidden until explicitly shown.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logVerbose(String format, Object...args);
+
+ /**
+ * Sets the max value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ *
+ * This method MUST be invoked once before using {@link #incProgress(int)} or
+ * {@link #getProgress()} or {@link #createSubMonitor(int)}. Callers are
+ * discouraged from using more than once -- implementations can either discard
+ * the next calls or behave incoherently.
+ */
+ public void setProgressMax(int max);
+
+ /**
+ * Returns the max valie of the progress bar, as last set by {@link #setProgressMax(int)}.
+ * Returns 0 if the max has never been set yet.
+ */
+ public int getProgressMax();
+
+ /**
+ * Increments the current value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ *
+ * Callers MUST use setProgressMax before using this method.
+ */
+ public void incProgress(int delta);
+
+ /**
+ * Returns the current value of the progress bar,
+ * between 0 and up to {@link #setProgressMax(int)} - 1.
+ *
+ * Callers MUST use setProgressMax before using this method.
+ */
+ public int getProgress();
+
+ /**
+ * Returns true if the user requested to cancel the operation.
+ * It is up to the task thread to pool this and exit as soon
+ * as possible.
+ */
+ public boolean isCancelRequested();
+
+ /**
+ * Creates a sub-monitor that will use up to tickCount on the progress bar.
+ * tickCount must be 1 or more.
+ */
+ public ITaskMonitor createSubMonitor(int tickCount);
+
+ /**
+ * Display a yes/no question dialog box.
+ *
+ * Implementations MUST allow this to be called from any thread, e.g. by
+ * making sure the dialog is opened synchronously in the ui thread.
+ *
+ * @param title The title of the dialog box
+ * @param message The error message
+ * @return true if YES was clicked.
+ */
+ public boolean displayPrompt(final String title, final String message);
+
+ /**
+ * Launch an interface which asks for user credentials. Implementations
+ * MUST allow this to be called from any thread, e.g. by making sure the
+ * dialog is opened synchronously in the UI thread.
+ *
+ * @param title The title of the dialog box.
+ * @param message The message to be displayed as an instruction.
+ * @return Returns the user provided credentials. Some fields may be blank if the user
+ * did not provide any input.
+ If operation is <b>canceled</b> by user the return value must be <b>null</b>.
+ */
public UserCredentials displayLoginCredentialsPrompt(String title, String message);
-}
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
index a4d1599..313819b 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
@@ -1,659 +1,659 @@
-/*
- * 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.annotations.NonNull;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.ISdkLog;
-import com.android.sdklib.ISystemImage;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.DocPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SamplePackage;
-import com.android.sdklib.internal.repository.packages.SourcePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.util.Pair;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Scans a local SDK to find which packages are currently installed.
- */
-public class LocalSdkParser {
-
- private Package[] mPackages;
-
- /** Parse all SDK folders. */
- public static final int PARSE_ALL = 0xFFFF;
- /** Parse the SDK/tools folder. */
- public static final int PARSE_TOOLS = 0x0001;
- /** Parse the SDK/platform-tools folder */
- public static final int PARSE_PLATFORM_TOOLS = 0x0002;
- /** Parse the SDK/docs folder. */
- public static final int PARSE_DOCS = 0x0004;
- /**
- * Equivalent to parsing the SDK/platforms folder but does so
- * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
- * Parsing the platforms also parses the SDK/system-images folder.
- */
- public static final int PARSE_PLATFORMS = 0x0010;
- /**
- * Equivalent to parsing the SDK/addons folder but does so
- * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
- */
- public static final int PARSE_ADDONS = 0x0020;
- /** Parse the SDK/samples folder.
- * Note: this will not detect samples located in the SDK/extras packages. */
- public static final int PARSE_SAMPLES = 0x0100;
- /** Parse the SDK/sources folder. */
- public static final int PARSE_SOURCES = 0x0200;
- /** Parse the SDK/extras folder. */
- public static final int PARSE_EXTRAS = 0x0400;
-
- public LocalSdkParser() {
- // pass
- }
-
- /**
- * Returns the packages found by the last call to {@link #parseSdk}.
- * <p/>
- * This returns initially returns null.
- * Once the parseSdk() method has been called, this returns a possibly empty but non-null array.
- */
- public Package[] getPackages() {
- return mPackages;
- }
-
- /**
- * Clear the internal packages list. After this call, {@link #getPackages()} will return
- * null till {@link #parseSdk} is called.
- */
- public void clearPackages() {
- mPackages = null;
- }
-
- /**
- * Scan the give SDK to find all the packages already installed at this location.
- * <p/>
- * Store the packages internally. You can use {@link #getPackages()} to retrieve them
- * at any time later.
- * <p/>
- * Equivalent to calling {@code parseSdk(..., PARSE_ALL, ...); }
- *
- * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @param monitor A monitor to track progress. Cannot be null.
- * @return The packages found. Can be retrieved later using {@link #getPackages()}.
- */
- public @NonNull Package[] parseSdk(
- @NonNull String osSdkRoot,
- @NonNull SdkManager sdkManager,
- @NonNull ITaskMonitor monitor) {
- return parseSdk(osSdkRoot, sdkManager, PARSE_ALL, monitor);
- }
-
- /**
- * Scan the give SDK to find all the packages already installed at this location.
- * <p/>
- * Store the packages internally. You can use {@link #getPackages()} to retrieve them
- * at any time later.
- *
- * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @param parseFilter Either {@link #PARSE_ALL} or an ORed combination of the other
- * {@code PARSE_} constants to indicate what should be parsed.
- * @param monitor A monitor to track progress. Cannot be null.
- * @return The packages found. Can be retrieved later using {@link #getPackages()}.
- */
- public @NonNull Package[] parseSdk(
- @NonNull String osSdkRoot,
- @NonNull SdkManager sdkManager,
- int parseFilter,
- @NonNull ITaskMonitor monitor) {
- ArrayList<Package> packages = new ArrayList<Package>();
- HashSet<File> visited = new HashSet<File>();
-
- monitor.setProgressMax(10);
-
- File dir = null;
- Package pkg = null;
-
- if ((parseFilter & PARSE_DOCS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_DOCS);
- pkg = scanDoc(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_TOOLS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_TOOLS);
- pkg = scanTools(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_PLATFORM_TOOLS) != 0) {
- dir = new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
- pkg = scanPlatformTools(dir, monitor);
- if (pkg != null) {
- packages.add(pkg);
- visited.add(dir);
- }
- }
- monitor.incProgress(1);
-
- // for platforms, add-ons and samples, rely on the SdkManager parser
- if ((parseFilter & (PARSE_ADDONS | PARSE_PLATFORMS)) != 0) {
- File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
-
- for(IAndroidTarget target : sdkManager.getTargets()) {
- Properties props = parseProperties(new File(target.getLocation(),
- SdkConstants.FN_SOURCE_PROP));
-
- try {
- pkg = null;
- if (target.isPlatform() && (parseFilter & PARSE_PLATFORMS) != 0) {
- pkg = PlatformPackage.create(target, props);
-
- if (samplesRoot.isDirectory()) {
- // Get the samples dir for a platform if it is located in the new
- // root /samples dir. We purposely ignore "old" samples that are
- // located under the platform dir.
- File samplesDir = new File(target.getPath(IAndroidTarget.SAMPLES));
- if (samplesDir.exists() &&
- samplesDir.getParentFile().equals(samplesRoot)) {
- Properties samplesProps = parseProperties(
- new File(samplesDir, SdkConstants.FN_SOURCE_PROP));
- if (samplesProps != null) {
- Package pkg2 = SamplePackage.create(target, samplesProps);
- packages.add(pkg2);
- }
- visited.add(samplesDir);
- }
- }
- } else if ((parseFilter & PARSE_ADDONS) != 0) {
- pkg = AddonPackage.create(target, props);
- }
-
- if (pkg != null) {
- for (ISystemImage systemImage : target.getSystemImages()) {
- if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {
- File siDir = systemImage.getLocation();
- if (siDir.isDirectory()) {
- Properties siProps = parseProperties(
- new File(siDir, SdkConstants.FN_SOURCE_PROP));
- Package pkg2 = new SystemImagePackage(
- target.getVersion(),
- 0 /*rev*/, // this will use the one from siProps
- systemImage.getAbiType(),
- siProps,
- siDir.getAbsolutePath());
- packages.add(pkg2);
- visited.add(siDir);
- }
- }
- }
- }
-
- } catch (Exception e) {
- monitor.error(e, null);
- }
-
- if (pkg != null) {
- packages.add(pkg);
- visited.add(new File(target.getLocation()));
- }
- }
- }
- monitor.incProgress(1);
-
- if ((parseFilter & PARSE_PLATFORMS) != 0) {
- scanMissingSystemImages(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_ADDONS) != 0) {
- scanMissingAddons(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_SAMPLES) != 0) {
- scanMissingSamples(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_EXTRAS) != 0) {
- scanExtras(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_EXTRAS) != 0) {
- scanExtrasDirectory(osSdkRoot, visited, packages, monitor);
- }
- monitor.incProgress(1);
- if ((parseFilter & PARSE_SOURCES) != 0) {
- scanSources(sdkManager, visited, packages, monitor);
- }
- monitor.incProgress(1);
-
- Collections.sort(packages);
-
- mPackages = packages.toArray(new Package[packages.size()]);
- return mPackages;
- }
-
- /**
- * Find any directory in the /extras/vendors/path folders for extra packages.
- * This isn't a recursive search.
- */
- private void scanExtras(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File root = new File(sdkManager.getLocation(), SdkConstants.FD_EXTRAS);
-
- if (!root.isDirectory()) {
- // This should not happen. It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File vendor : root.listFiles()) {
- if (vendor.isDirectory()) {
- scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log);
- }
- }
- }
-
- /**
- * Find any other directory in the given "root" directory that hasn't been visited yet
- * and assume they contain extra packages. This is <em>not</em> a recursive search.
- */
- private void scanExtrasDirectory(String extrasRoot,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File root = new File(extrasRoot);
-
- if (!root.isDirectory()) {
- // This should not happen. It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File dir : root.listFiles()) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
- if (props != null) {
- try {
- Package pkg = ExtraPackage.create(
- null, //source
- props, //properties
- null, //vendor
- dir.getName(), //path
- 0, //revision
- null, //license
- null, //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- dir.getPath() //archiveOsPath
- );
-
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Find any other sub-directories under the /samples root that hasn't been visited yet
- * and assume they contain sample packages. This is <em>not</em> a recursive search.
- * <p/>
- * The use case is to find samples dirs under /samples when their target isn't loaded.
- */
- private void scanMissingSamples(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File root = new File(sdkManager.getLocation());
- root = new File(root, SdkConstants.FD_SAMPLES);
-
- if (!root.isDirectory()) {
- // It makes listFiles() return null so let's avoid it.
- return;
- }
-
- for (File dir : root.listFiles()) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
- if (props != null) {
- try {
- Package pkg = SamplePackage.create(dir.getAbsolutePath(), props);
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * The sdk manager only lists valid addons. However here we also want to find "broken"
- * addons, i.e. addons that failed to load for some reason.
- * <p/>
- * Find any other sub-directories under the /add-ons root that hasn't been visited yet
- * and assume they contain broken addons.
- */
- private void scanMissingAddons(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
-
- File[] files = addons.listFiles();
- if (files == null) {
- return;
- }
-
- for (File dir : files) {
- if (dir.isDirectory() && !visited.contains(dir)) {
- Pair<Map<String, String>, String> infos =
- SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);
- Properties sourceProps =
- parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
-
- Map<String, String> addonProps = infos.getFirst();
- String error = infos.getSecond();
- try {
- Package pkg = AddonPackage.createBroken(dir.getAbsolutePath(),
- sourceProps,
- addonProps,
- error);
- packages.add(pkg);
- visited.add(dir);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
-
- /**
- * The sdk manager only lists valid system image via its addons or platform targets.
- * However here we also want to find "broken" system images, that is system images
- * that are located in the sdk/system-images folder but somehow not loaded properly.
- */
- private void scanMissingSystemImages(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES);
-
- File[] files = siRoot.listFiles();
- if (files == null) {
- return;
- }
-
- // The system-images folder contains a list of platform folders.
- for (File platformDir : files) {
- if (platformDir.isDirectory() && !visited.contains(platformDir)) {
- visited.add(platformDir);
-
- // In the platform directory, we expect a list of abi folders
- File[] platformFiles = platformDir.listFiles();
- if (platformFiles != null) {
- for (File abiDir : platformFiles) {
- if (abiDir.isDirectory() && !visited.contains(abiDir)) {
- visited.add(abiDir);
-
- // Ignore empty directories
- File[] abiFiles = abiDir.listFiles();
- if (abiFiles != null && abiFiles.length > 0) {
- Properties props =
- parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP));
-
- try {
- Package pkg = SystemImagePackage.createBroken(abiDir, props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Scan the sources/folders and register valid as well as broken source packages.
- */
- private void scanSources(SdkManager sdkManager,
- HashSet<File> visited,
- ArrayList<Package> packages,
- ISdkLog log) {
- File srcRoot = new File(sdkManager.getLocation(), SdkConstants.FD_PKG_SOURCES);
-
- File[] subDirs = srcRoot.listFiles();
- if (subDirs == null) {
- return;
- }
-
- // The sources folder contains a list of platform folders.
- for (File platformDir : subDirs) {
- if (platformDir.isDirectory() && !visited.contains(platformDir)) {
- visited.add(platformDir);
-
- // Ignore empty directories
- File[] srcFiles = platformDir.listFiles();
- if (srcFiles != null && srcFiles.length > 0) {
- Properties props =
- parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
-
- try {
- Package pkg = SourcePackage.create(platformDir, props);
- packages.add(pkg);
- } catch (Exception e) {
- log.error(e, null);
- }
- }
- }
- }
- }
-
- /**
- * Try to find a tools package at the given location.
- * Returns null if not found.
- */
- private Package scanTools(File toolFolder, ISdkLog log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
-
- // We're not going to check that all tools are present. At the very least
- // we should expect to find android and an emulator adapted to the current OS.
- boolean hasEmulator = false;
- boolean hasAndroid = false;
- String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
- String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
- File[] files = toolFolder.listFiles();
- if (files != null) {
- for (File file : files) {
- String name = file.getName();
- if (SdkConstants.FN_EMULATOR.equals(name)) {
- hasEmulator = true;
- }
- if (android1.equals(name) || (android2 != null && android2.equals(name))) {
- hasAndroid = true;
- }
- }
- }
-
- if (!hasAndroid || !hasEmulator) {
- return null;
- }
-
- // Create our package. use the properties if we found any.
- try {
- Package pkg = ToolPackage.create(
- null, //source
- props, //properties
- 0, //revision
- null, //license
- "Tools", //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- toolFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- return null;
- }
-
- /**
- * Try to find a platform-tools package at the given location.
- * Returns null if not found.
- */
- private Package scanPlatformTools(File platformToolsFolder, ISdkLog log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(platformToolsFolder,
- SdkConstants.FN_SOURCE_PROP));
-
- // We're not going to check that all tools are present. At the very least
- // we should expect to find adb, aidl, aapt and dx (adapted to the current OS).
-
- if (platformToolsFolder.listFiles() == null) {
- // ListFiles is null if the directory doesn't even exist.
- // Not going to find anything in there...
- return null;
- }
-
- // Create our package. use the properties if we found any.
- try {
- Package pkg = PlatformToolPackage.create(
- null, //source
- props, //properties
- 0, //revision
- null, //license
- "Platform Tools", //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- platformToolsFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- return null;
- }
-
- /**
- * Try to find a docs package at the given location.
- * Returns null if not found.
- */
- private Package scanDoc(File docFolder, ISdkLog log) {
- // Can we find some properties?
- Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
-
- // To start with, a doc folder should have an "index.html" to be acceptable.
- // We don't actually check the content of the file.
- if (new File(docFolder, "index.html").isFile()) {
- try {
- Package pkg = DocPackage.create(
- null, //source
- props, //properties
- 0, //apiLevel
- null, //codename
- 0, //revision
- null, //license
- null, //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- docFolder.getPath() //archiveOsPath
- );
-
- return pkg;
- } catch (Exception e) {
- log.error(e, null);
- }
- }
-
- return null;
- }
-
- /**
- * Parses the given file as properties file if it exists.
- * Returns null if the file does not exist, cannot be parsed or has no properties.
- */
- private Properties parseProperties(File propsFile) {
- FileInputStream fis = null;
- try {
- if (propsFile.exists()) {
- fis = new FileInputStream(propsFile);
-
- Properties props = new Properties();
- props.load(fis);
-
- // To be valid, there must be at least one property in it.
- if (props.size() > 0) {
- return props;
- }
- }
-
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- return null;
- }
-}
+/*
+ * 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.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.ISystemImage.LocationType;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.AddonPackage;
+import com.android.sdklib.internal.repository.packages.DocPackage;
+import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformPackage;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
+import com.android.sdklib.internal.repository.packages.SamplePackage;
+import com.android.sdklib.internal.repository.packages.SourcePackage;
+import com.android.sdklib.internal.repository.packages.SystemImagePackage;
+import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.utils.ILogger;
+import com.android.utils.Pair;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Scans a local SDK to find which packages are currently installed.
+ */
+public class LocalSdkParser {
+
+ private Package[] mPackages;
+
+ /** Parse all SDK folders. */
+ public static final int PARSE_ALL = 0xFFFF;
+ /** Parse the SDK/tools folder. */
+ public static final int PARSE_TOOLS = 0x0001;
+ /** Parse the SDK/platform-tools folder */
+ public static final int PARSE_PLATFORM_TOOLS = 0x0002;
+ /** Parse the SDK/docs folder. */
+ public static final int PARSE_DOCS = 0x0004;
+ /**
+ * Equivalent to parsing the SDK/platforms folder but does so
+ * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
+ * Parsing the platforms also parses the SDK/system-images folder.
+ */
+ public static final int PARSE_PLATFORMS = 0x0010;
+ /**
+ * Equivalent to parsing the SDK/addons folder but does so
+ * by using the <em>valid</em> targets loaded by the {@link SdkManager}.
+ */
+ public static final int PARSE_ADDONS = 0x0020;
+ /** Parse the SDK/samples folder.
+ * Note: this will not detect samples located in the SDK/extras packages. */
+ public static final int PARSE_SAMPLES = 0x0100;
+ /** Parse the SDK/sources folder. */
+ public static final int PARSE_SOURCES = 0x0200;
+ /** Parse the SDK/extras folder. */
+ public static final int PARSE_EXTRAS = 0x0400;
+
+ public LocalSdkParser() {
+ // pass
+ }
+
+ /**
+ * Returns the packages found by the last call to {@link #parseSdk}.
+ * <p/>
+ * This returns initially returns null.
+ * Once the parseSdk() method has been called, this returns a possibly empty but non-null array.
+ */
+ public Package[] getPackages() {
+ return mPackages;
+ }
+
+ /**
+ * Clear the internal packages list. After this call, {@link #getPackages()} will return
+ * null till {@link #parseSdk} is called.
+ */
+ public void clearPackages() {
+ mPackages = null;
+ }
+
+ /**
+ * Scan the give SDK to find all the packages already installed at this location.
+ * <p/>
+ * Store the packages internally. You can use {@link #getPackages()} to retrieve them
+ * at any time later.
+ * <p/>
+ * Equivalent to calling {@code parseSdk(..., PARSE_ALL, ...); }
+ *
+ * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @param monitor A monitor to track progress. Cannot be null.
+ * @return The packages found. Can be retrieved later using {@link #getPackages()}.
+ */
+ public @NonNull Package[] parseSdk(
+ @NonNull String osSdkRoot,
+ @NonNull SdkManager sdkManager,
+ @NonNull ITaskMonitor monitor) {
+ return parseSdk(osSdkRoot, sdkManager, PARSE_ALL, monitor);
+ }
+
+ /**
+ * Scan the give SDK to find all the packages already installed at this location.
+ * <p/>
+ * Store the packages internally. You can use {@link #getPackages()} to retrieve them
+ * at any time later.
+ *
+ * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @param parseFilter Either {@link #PARSE_ALL} or an ORed combination of the other
+ * {@code PARSE_} constants to indicate what should be parsed.
+ * @param monitor A monitor to track progress. Cannot be null.
+ * @return The packages found. Can be retrieved later using {@link #getPackages()}.
+ */
+ public @NonNull Package[] parseSdk(
+ @NonNull String osSdkRoot,
+ @NonNull SdkManager sdkManager,
+ int parseFilter,
+ @NonNull ITaskMonitor monitor) {
+ ArrayList<Package> packages = new ArrayList<Package>();
+ HashSet<File> visited = new HashSet<File>();
+
+ monitor.setProgressMax(10);
+
+ File dir = null;
+ Package pkg = null;
+
+ if ((parseFilter & PARSE_DOCS) != 0) {
+ dir = new File(osSdkRoot, SdkConstants.FD_DOCS);
+ pkg = scanDoc(dir, monitor);
+ if (pkg != null) {
+ packages.add(pkg);
+ visited.add(dir);
+ }
+ }
+ monitor.incProgress(1);
+
+ if ((parseFilter & PARSE_TOOLS) != 0) {
+ dir = new File(osSdkRoot, SdkConstants.FD_TOOLS);
+ pkg = scanTools(dir, monitor);
+ if (pkg != null) {
+ packages.add(pkg);
+ visited.add(dir);
+ }
+ }
+ monitor.incProgress(1);
+
+ if ((parseFilter & PARSE_PLATFORM_TOOLS) != 0) {
+ dir = new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
+ pkg = scanPlatformTools(dir, monitor);
+ if (pkg != null) {
+ packages.add(pkg);
+ visited.add(dir);
+ }
+ }
+ monitor.incProgress(1);
+
+ // for platforms, add-ons and samples, rely on the SdkManager parser
+ if ((parseFilter & (PARSE_ADDONS | PARSE_PLATFORMS)) != 0) {
+ File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
+
+ for(IAndroidTarget target : sdkManager.getTargets()) {
+ Properties props = parseProperties(new File(target.getLocation(),
+ SdkConstants.FN_SOURCE_PROP));
+
+ try {
+ pkg = null;
+ if (target.isPlatform() && (parseFilter & PARSE_PLATFORMS) != 0) {
+ pkg = PlatformPackage.create(target, props);
+
+ if (samplesRoot.isDirectory()) {
+ // Get the samples dir for a platform if it is located in the new
+ // root /samples dir. We purposely ignore "old" samples that are
+ // located under the platform dir.
+ File samplesDir = new File(target.getPath(IAndroidTarget.SAMPLES));
+ if (samplesDir.exists() &&
+ samplesDir.getParentFile().equals(samplesRoot)) {
+ Properties samplesProps = parseProperties(
+ new File(samplesDir, SdkConstants.FN_SOURCE_PROP));
+ if (samplesProps != null) {
+ Package pkg2 = SamplePackage.create(target, samplesProps);
+ packages.add(pkg2);
+ }
+ visited.add(samplesDir);
+ }
+ }
+ } else if ((parseFilter & PARSE_ADDONS) != 0) {
+ pkg = AddonPackage.create(target, props);
+ }
+
+ if (pkg != null) {
+ for (ISystemImage systemImage : target.getSystemImages()) {
+ if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {
+ File siDir = systemImage.getLocation();
+ if (siDir.isDirectory()) {
+ Properties siProps = parseProperties(
+ new File(siDir, SdkConstants.FN_SOURCE_PROP));
+ Package pkg2 = new SystemImagePackage(
+ target.getVersion(),
+ 0 /*rev*/, // this will use the one from siProps
+ systemImage.getAbiType(),
+ siProps,
+ siDir.getAbsolutePath());
+ packages.add(pkg2);
+ visited.add(siDir);
+ }
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ monitor.error(e, null);
+ }
+
+ if (pkg != null) {
+ packages.add(pkg);
+ visited.add(new File(target.getLocation()));
+ }
+ }
+ }
+ monitor.incProgress(1);
+
+ if ((parseFilter & PARSE_PLATFORMS) != 0) {
+ scanMissingSystemImages(sdkManager, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+ if ((parseFilter & PARSE_ADDONS) != 0) {
+ scanMissingAddons(sdkManager, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+ if ((parseFilter & PARSE_SAMPLES) != 0) {
+ scanMissingSamples(sdkManager, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+ if ((parseFilter & PARSE_EXTRAS) != 0) {
+ scanExtras(sdkManager, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+ if ((parseFilter & PARSE_EXTRAS) != 0) {
+ scanExtrasDirectory(osSdkRoot, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+ if ((parseFilter & PARSE_SOURCES) != 0) {
+ scanSources(sdkManager, visited, packages, monitor);
+ }
+ monitor.incProgress(1);
+
+ Collections.sort(packages);
+
+ mPackages = packages.toArray(new Package[packages.size()]);
+ return mPackages;
+ }
+
+ /**
+ * Find any directory in the /extras/vendors/path folders for extra packages.
+ * This isn't a recursive search.
+ */
+ private void scanExtras(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File root = new File(sdkManager.getLocation(), SdkConstants.FD_EXTRAS);
+
+ if (!root.isDirectory()) {
+ // This should not happen. It makes listFiles() return null so let's avoid it.
+ return;
+ }
+
+ for (File vendor : root.listFiles()) {
+ if (vendor.isDirectory()) {
+ scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log);
+ }
+ }
+ }
+
+ /**
+ * Find any other directory in the given "root" directory that hasn't been visited yet
+ * and assume they contain extra packages. This is <em>not</em> a recursive search.
+ */
+ private void scanExtrasDirectory(String extrasRoot,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File root = new File(extrasRoot);
+
+ if (!root.isDirectory()) {
+ // This should not happen. It makes listFiles() return null so let's avoid it.
+ return;
+ }
+
+ for (File dir : root.listFiles()) {
+ if (dir.isDirectory() && !visited.contains(dir)) {
+ Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
+ if (props != null) {
+ try {
+ Package pkg = ExtraPackage.create(
+ null, //source
+ props, //properties
+ null, //vendor
+ dir.getName(), //path
+ 0, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ dir.getPath() //archiveOsPath
+ );
+
+ packages.add(pkg);
+ visited.add(dir);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Find any other sub-directories under the /samples root that hasn't been visited yet
+ * and assume they contain sample packages. This is <em>not</em> a recursive search.
+ * <p/>
+ * The use case is to find samples dirs under /samples when their target isn't loaded.
+ */
+ private void scanMissingSamples(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File root = new File(sdkManager.getLocation());
+ root = new File(root, SdkConstants.FD_SAMPLES);
+
+ if (!root.isDirectory()) {
+ // It makes listFiles() return null so let's avoid it.
+ return;
+ }
+
+ for (File dir : root.listFiles()) {
+ if (dir.isDirectory() && !visited.contains(dir)) {
+ Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
+ if (props != null) {
+ try {
+ Package pkg = SamplePackage.create(dir.getAbsolutePath(), props);
+ packages.add(pkg);
+ visited.add(dir);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The sdk manager only lists valid addons. However here we also want to find "broken"
+ * addons, i.e. addons that failed to load for some reason.
+ * <p/>
+ * Find any other sub-directories under the /add-ons root that hasn't been visited yet
+ * and assume they contain broken addons.
+ */
+ private void scanMissingAddons(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
+
+ File[] files = addons.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (File dir : files) {
+ if (dir.isDirectory() && !visited.contains(dir)) {
+ Pair<Map<String, String>, String> infos =
+ SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);
+ Properties sourceProps =
+ parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
+
+ Map<String, String> addonProps = infos.getFirst();
+ String error = infos.getSecond();
+ try {
+ Package pkg = AddonPackage.createBroken(dir.getAbsolutePath(),
+ sourceProps,
+ addonProps,
+ error);
+ packages.add(pkg);
+ visited.add(dir);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+
+ /**
+ * The sdk manager only lists valid system image via its addons or platform targets.
+ * However here we also want to find "broken" system images, that is system images
+ * that are located in the sdk/system-images folder but somehow not loaded properly.
+ */
+ private void scanMissingSystemImages(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES);
+
+ File[] files = siRoot.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ // The system-images folder contains a list of platform folders.
+ for (File platformDir : files) {
+ if (platformDir.isDirectory() && !visited.contains(platformDir)) {
+ visited.add(platformDir);
+
+ // In the platform directory, we expect a list of abi folders
+ File[] platformFiles = platformDir.listFiles();
+ if (platformFiles != null) {
+ for (File abiDir : platformFiles) {
+ if (abiDir.isDirectory() && !visited.contains(abiDir)) {
+ visited.add(abiDir);
+
+ // Ignore empty directories
+ File[] abiFiles = abiDir.listFiles();
+ if (abiFiles != null && abiFiles.length > 0) {
+ Properties props =
+ parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP));
+
+ try {
+ Package pkg = SystemImagePackage.createBroken(abiDir, props);
+ packages.add(pkg);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Scan the sources/folders and register valid as well as broken source packages.
+ */
+ private void scanSources(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ILogger log) {
+ File srcRoot = new File(sdkManager.getLocation(), SdkConstants.FD_PKG_SOURCES);
+
+ File[] subDirs = srcRoot.listFiles();
+ if (subDirs == null) {
+ return;
+ }
+
+ // The sources folder contains a list of platform folders.
+ for (File platformDir : subDirs) {
+ if (platformDir.isDirectory() && !visited.contains(platformDir)) {
+ visited.add(platformDir);
+
+ // Ignore empty directories
+ File[] srcFiles = platformDir.listFiles();
+ if (srcFiles != null && srcFiles.length > 0) {
+ Properties props =
+ parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+
+ try {
+ Package pkg = SourcePackage.create(platformDir, props);
+ packages.add(pkg);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Try to find a tools package at the given location.
+ * Returns null if not found.
+ */
+ private Package scanTools(File toolFolder, ILogger log) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
+
+ // We're not going to check that all tools are present. At the very least
+ // we should expect to find android and an emulator adapted to the current OS.
+ boolean hasEmulator = false;
+ boolean hasAndroid = false;
+ String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
+ String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
+ File[] files = toolFolder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ String name = file.getName();
+ if (SdkConstants.FN_EMULATOR.equals(name)) {
+ hasEmulator = true;
+ }
+ if (android1.equals(name) || (android2 != null && android2.equals(name))) {
+ hasAndroid = true;
+ }
+ }
+ }
+
+ if (!hasAndroid || !hasEmulator) {
+ return null;
+ }
+
+ // Create our package. use the properties if we found any.
+ try {
+ Package pkg = ToolPackage.create(
+ null, //source
+ props, //properties
+ 0, //revision
+ null, //license
+ "Tools", //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ toolFolder.getPath() //archiveOsPath
+ );
+
+ return pkg;
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ return null;
+ }
+
+ /**
+ * Try to find a platform-tools package at the given location.
+ * Returns null if not found.
+ */
+ private Package scanPlatformTools(File platformToolsFolder, ILogger log) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(platformToolsFolder,
+ SdkConstants.FN_SOURCE_PROP));
+
+ // We're not going to check that all tools are present. At the very least
+ // we should expect to find adb, aidl, aapt and dx (adapted to the current OS).
+
+ if (platformToolsFolder.listFiles() == null) {
+ // ListFiles is null if the directory doesn't even exist.
+ // Not going to find anything in there...
+ return null;
+ }
+
+ // Create our package. use the properties if we found any.
+ try {
+ Package pkg = PlatformToolPackage.create(
+ null, //source
+ props, //properties
+ 0, //revision
+ null, //license
+ "Platform Tools", //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ platformToolsFolder.getPath() //archiveOsPath
+ );
+
+ return pkg;
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ return null;
+ }
+
+ /**
+ * Try to find a docs package at the given location.
+ * Returns null if not found.
+ */
+ private Package scanDoc(File docFolder, ILogger log) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
+
+ // To start with, a doc folder should have an "index.html" to be acceptable.
+ // We don't actually check the content of the file.
+ if (new File(docFolder, "index.html").isFile()) {
+ try {
+ Package pkg = DocPackage.create(
+ null, //source
+ props, //properties
+ 0, //apiLevel
+ null, //codename
+ 0, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ docFolder.getPath() //archiveOsPath
+ );
+
+ return pkg;
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the given file as properties file if it exists.
+ * Returns null if the file does not exist, cannot be parsed or has no properties.
+ */
+ private Properties parseProperties(File propsFile) {
+ FileInputStream fis = null;
+ try {
+ if (propsFile.exists()) {
+ fis = new FileInputStream(propsFile);
+
+ Properties props = new Properties();
+ props.load(fis);
+
+ // To be valid, there must be at least one property in it.
+ if (props.size() > 0) {
+ return props;
+ }
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java
index ac40f57..f69e37d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/NullTaskMonitor.java
@@ -1,129 +1,134 @@
-/*
- * Copyright (C) 2011 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.ISdkLog;
-import com.android.sdklib.NullSdkLog;
-
-
-/**
- * A no-op implementation of the {@link ITaskMonitor} interface.
- * <p/>
- * This can be passed to methods that require a monitor when the caller doesn't
- * have any UI to update or means to report tracked progress.
- * A custom {@link ISdkLog} is used. Clients could use {@link NullSdkLog} if
- * they really don't care about the logging either.
- */
-public class NullTaskMonitor implements ITaskMonitor {
-
- private final ISdkLog mLog;
-
- /**
- * Creates a no-op {@link ITaskMonitor} that defers logging to the specified
- * logger.
- * <p/>
- * This can be passed to methods that require a monitor when the caller doesn't
- * have any UI to update or means to report tracked progress.
- *
- * @param log An {@link ISdkLog}. Must not be null. Consider using {@link NullSdkLog}.
- */
- public NullTaskMonitor(ISdkLog log) {
- mLog = log;
- }
-
- @Override
- public void setDescription(String format, Object...args) {
- // pass
- }
-
- @Override
- public void log(String format, Object...args) {
- mLog.printf(format, args);
- }
-
- @Override
- public void logError(String format, Object...args) {
- mLog.error(null /*throwable*/, format, args);
- }
-
- @Override
- public void logVerbose(String format, Object...args) {
- mLog.printf(format, args);
- }
-
- @Override
- public void setProgressMax(int max) {
- // pass
- }
-
- @Override
- public int getProgressMax() {
- return 0;
- }
-
- @Override
- public void incProgress(int delta) {
- // pass
- }
-
- /** Always return 1. */
- @Override
- public int getProgress() {
- return 1;
- }
-
- /** Always return false. */
- @Override
- public boolean isCancelRequested() {
- return false;
- }
-
- @Override
- public ITaskMonitor createSubMonitor(int tickCount) {
- return this;
- }
-
- /** Always return false. */
- @Override
- public boolean displayPrompt(final String title, final String message) {
- return false;
- }
-
- /** Always return null. */
- @Override
- public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
- return null;
- }
-
- // --- ISdkLog ---
-
- @Override
- public void error(Throwable t, String errorFormat, Object... args) {
- mLog.error(t, errorFormat, args);
- }
-
- @Override
- public void warning(String warningFormat, Object... args) {
- mLog.warning(warningFormat, args);
- }
-
- @Override
- public void printf(String msgFormat, Object... args) {
- mLog.printf(msgFormat, args);
- }
-
-}
+/*
+ * Copyright (C) 2011 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.annotations.NonNull;
+import com.android.utils.ILogger;
+import com.android.utils.NullLogger;
+
+
+/**
+ * A no-op implementation of the {@link ITaskMonitor} interface.
+ * <p/>
+ * This can be passed to methods that require a monitor when the caller doesn't
+ * have any UI to update or means to report tracked progress.
+ * A custom {@link ILogger} is used. Clients could use {@link NullLogger} if
+ * they really don't care about the logging either.
+ */
+public class NullTaskMonitor implements ITaskMonitor {
+
+ private final ILogger mLog;
+
+ /**
+ * Creates a no-op {@link ITaskMonitor} that defers logging to the specified
+ * logger.
+ * <p/>
+ * This can be passed to methods that require a monitor when the caller doesn't
+ * have any UI to update or means to report tracked progress.
+ *
+ * @param log An {@link ILogger}. Must not be null. Consider using {@link NullLogger}.
+ */
+ public NullTaskMonitor(ILogger log) {
+ mLog = log;
+ }
+
+ @Override
+ public void setDescription(String format, Object...args) {
+ // pass
+ }
+
+ @Override
+ public void log(String format, Object...args) {
+ mLog.info(format, args);
+ }
+
+ @Override
+ public void logError(String format, Object...args) {
+ mLog.error(null /*throwable*/, format, args);
+ }
+
+ @Override
+ public void logVerbose(String format, Object...args) {
+ mLog.verbose(format, args);
+ }
+
+ @Override
+ public void setProgressMax(int max) {
+ // pass
+ }
+
+ @Override
+ public int getProgressMax() {
+ return 0;
+ }
+
+ @Override
+ public void incProgress(int delta) {
+ // pass
+ }
+
+ /** Always return 1. */
+ @Override
+ public int getProgress() {
+ return 1;
+ }
+
+ /** Always return false. */
+ @Override
+ public boolean isCancelRequested() {
+ return false;
+ }
+
+ @Override
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ return this;
+ }
+
+ /** Always return false. */
+ @Override
+ public boolean displayPrompt(final String title, final String message) {
+ return false;
+ }
+
+ /** Always return null. */
+ @Override
+ public UserCredentials displayLoginCredentialsPrompt(String title, String message) {
+ return null;
+ }
+
+ // --- ILogger ---
+
+ @Override
+ public void error(Throwable t, String errorFormat, Object... args) {
+ mLog.error(t, errorFormat, args);
+ }
+
+ @Override
+ public void warning(@NonNull String warningFormat, Object... args) {
+ mLog.warning(warningFormat, args);
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ mLog.info(msgFormat, args);
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ mLog.verbose(msgFormat, args);
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
index 4958505..0301b5e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
@@ -1,589 +1,622 @@
-/*
- * Copyright (C) 2012 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.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.repository.SdkStatsConstants;
-import com.android.sdklib.util.SparseArray;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.UnknownHostException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-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;
-
-
-/**
- * Retrieves stats on platforms.
- * <p/>
- * This returns information stored on the repository in a different XML file
- * and isn't directly tied to the existence of the listed platforms.
- */
-public class SdkStats {
-
- public static class PlatformStatBase {
- private final int mApiLevel;
- private final String mVersionName;
- private final String mCodeName;
- private final float mShare;
-
- public PlatformStatBase(int apiLevel,
- String versionName,
- String codeName,
- float share) {
- mApiLevel = apiLevel;
- mVersionName = versionName;
- mCodeName = codeName;
- mShare = share;
- }
-
- /** The Android API Level for the platform. An int > 0. */
- public int getApiLevel() {
- return mApiLevel;
- }
-
- /** The official codename for this platform, for example "Cupcake". */
- public String getCodeName() {
- return mCodeName;
- }
-
- /** The official version name of this platform, for example "Android 1.5". */
- public String getVersionName() {
- return mVersionName;
- }
-
- /** An approximate share percentage of this platform and all the
- * platforms of lower API level. */
- public float getShare() {
- return mShare;
- }
-
- /** Returns a string representation of this object, for debugging purposes. */
- @Override
- public String toString() {
- return String.format("api=%d, code=%s, vers=%s, share=%.1f%%", //$NON-NLS-1$
- mApiLevel, mCodeName, mVersionName, mShare);
- }
- }
-
- public static class PlatformStat extends PlatformStatBase {
- private final float mAccumShare;
-
- public PlatformStat(int apiLevel,
- String versionName,
- String codeName,
- float share,
- float accumShare) {
- super(apiLevel, versionName, codeName, share);
- mAccumShare = accumShare;
- }
-
- public PlatformStat(PlatformStatBase base, float accumShare) {
- super(base.getApiLevel(),
- base.getVersionName(),
- base.getCodeName(),
- base.getShare());
- mAccumShare = accumShare;
- }
-
- /** The accumulated approximate share percentage of that platform. */
- public float getAccumShare() {
- return mAccumShare;
- }
-
- /** Returns a string representation of this object, for debugging purposes. */
- @Override
- public String toString() {
- return String.format("<Stat %s, accum=%.1f%%>", super.toString(), mAccumShare);
- }
- }
-
- private final SparseArray<PlatformStat> mStats = new SparseArray<SdkStats.PlatformStat>();
-
- public SdkStats() {
- }
-
- public SparseArray<PlatformStat> getStats() {
- return mStats;
- }
-
- public void load(DownloadCache cache, boolean forceHttp, ITaskMonitor monitor) {
-
- String url = SdkStatsConstants.URL_STATS;
-
- if (forceHttp) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- monitor.setProgressMax(5);
- monitor.setDescription("Fetching %1$s", url);
- monitor.incProgress(1);
-
- Exception[] exception = new Exception[] { null };
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Document validatedDoc = null;
- String validatedUri = null;
-
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
-
- if (xml != null) {
- monitor.setDescription("Validate XML");
-
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= SdkStatsConstants.NS_LATEST_VERSION) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- }
- } else if (version > SdkStatsConstants.NS_LATEST_VERSION) {
- // The schema used is more recent than what is supported by this tool.
- // We don't have an upgrade-path support yet, so simply ignore the document.
- return;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- } else if (exception[0] instanceof UnknownHostException &&
- exception[0].getMessage() != null) {
- // This has no useful getMessage yet could really use one
- reason = String.format("Unknown Host %1$s", exception[0].getMessage());
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- } else if (exception[0].getMessage() != null) {
- reason = exception[0].getMessage();
- } else {
- // We don't know what's wrong. Let's give the exception class at least.
- reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- return;
- }
-
- monitor.incProgress(1);
-
- if (xml != null) {
- monitor.setDescription("Parse XML");
- monitor.incProgress(1);
- parseStatsDocument(validatedDoc, validatedUri, monitor);
- }
-
- // done
- monitor.incProgress(1);
- }
-
- /**
- * Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens. References: <br/>
- * URL Connection:
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- * @see UrlOpener UrlOpener, which handles all URL logic.
- */
- private InputStream fetchUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- return cache.openCachedUrl(urlString, monitor);
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
- * declaration.
- *
- * @return 1..{@link SdkStatsConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-addons-list
- // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(SdkStatsConstants.NS_PATTERN);
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkStatsConstants.NODE_SDK_STATS.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return SdkStatsConstants.getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkStatsConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = SdkStatsConstants.getXsdStream(version);
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // 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 == null ? null : schema.newValidator();
-
- return validator;
- }
-
- /**
- * Takes an XML document as a string as parameter and returns a DOM for it.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-
- /**
- * Parses all valid platforms found in the XML.
- * Changes the stats array returned by {@link #getStats()}
- * (also returns the value directly, useful for unti tests.)
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SparseArray<PlatformStat> parseStatsDocument(
- Document doc,
- String nsUri,
- ITaskMonitor monitor) {
-
- String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
- if (baseUrl != null) {
- if (baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
- baseUrl = null;
- }
- }
-
- SparseArray<PlatformStatBase> platforms = new SparseArray<SdkStats.PlatformStatBase>();
- int maxApi = 0;
-
- Node root = getFirstChild(doc, nsUri, SdkStatsConstants.NODE_SDK_STATS);
- if (root != null) {
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- child.getLocalName().equals(SdkStatsConstants.NODE_PLATFORM)) {
-
- try {
- Node node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_API_LEVEL);
- int apiLevel = Integer.parseInt(node.getTextContent().trim());
-
- if (apiLevel < 1) {
- // bad API level, ignore it.
- continue;
- }
-
- if (platforms.indexOfKey(apiLevel) >= 0) {
- // if we already loaded that API, ignore duplicates
- continue;
- }
-
- String codeName =
- getFirstChild(child, nsUri, SdkStatsConstants.NODE_CODENAME).
- getTextContent().trim();
- String versName =
- getFirstChild(child, nsUri, SdkStatsConstants.NODE_VERSION).
- getTextContent().trim();
-
- if (codeName == null || versName == null ||
- codeName.length() == 0 || versName.length() == 0) {
- // bad names. ignore.
- continue;
- }
-
- node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_SHARE);
- float percent = Float.parseFloat(node.getTextContent().trim());
-
- if (percent < 0 || percent > 100) {
- // invalid percentage. ignore.
- continue;
- }
-
- PlatformStatBase p = new PlatformStatBase(
- apiLevel, versName, codeName, percent);
- platforms.put(apiLevel, p);
-
- maxApi = apiLevel > maxApi ? apiLevel : maxApi;
-
- } catch (Exception ignore) {
- // Error parsing this platform. Ignore it.
- continue;
- }
- }
- }
- }
-
- mStats.clear();
-
- // Compute cumulative share percents & fill in final map
- for (int api = 1; api <= maxApi; api++) {
- PlatformStatBase p = platforms.get(api);
- if (p == null) {
- continue;
- }
-
- float sum = p.getShare();
- for (int j = api + 1; j <= maxApi; j++) {
- PlatformStatBase pj = platforms.get(j);
- if (pj != null) {
- sum += pj.getShare();
- }
- }
-
- mStats.put(api, new PlatformStat(p, sum));
- }
-
- return mStats;
- }
-
- /**
- * 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 nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
- return child;
- }
- }
- }
-
- return null;
- }
-
-}
+/*
+ * Copyright (C) 2012 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.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
+import com.android.sdklib.repository.SdkStatsConstants;
+import com.android.sdklib.util.SparseArray;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.UnknownHostException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.SSLKeyException;
+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;
+
+
+/**
+ * Retrieves stats on platforms.
+ * <p/>
+ * This returns information stored on the repository in a different XML file
+ * and isn't directly tied to the existence of the listed platforms.
+ */
+public class SdkStats {
+
+ public static class PlatformStatBase {
+ private final int mApiLevel;
+ private final String mVersionName;
+ private final String mCodeName;
+ private final float mShare;
+
+ public PlatformStatBase(int apiLevel,
+ String versionName,
+ String codeName,
+ float share) {
+ mApiLevel = apiLevel;
+ mVersionName = versionName;
+ mCodeName = codeName;
+ mShare = share;
+ }
+
+ /** The Android API Level for the platform. An int > 0. */
+ public int getApiLevel() {
+ return mApiLevel;
+ }
+
+ /** The official codename for this platform, for example "Cupcake". */
+ public String getCodeName() {
+ return mCodeName;
+ }
+
+ /** The official version name of this platform, for example "Android 1.5". */
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ /** An approximate share percentage of this platform and all the
+ * platforms of lower API level. */
+ public float getShare() {
+ return mShare;
+ }
+
+ /** Returns a string representation of this object, for debugging purposes. */
+ @Override
+ public String toString() {
+ return String.format("api=%d, code=%s, vers=%s, share=%.1f%%", //$NON-NLS-1$
+ mApiLevel, mCodeName, mVersionName, mShare);
+ }
+ }
+
+ public static class PlatformStat extends PlatformStatBase {
+ private final float mAccumShare;
+
+ public PlatformStat(int apiLevel,
+ String versionName,
+ String codeName,
+ float share,
+ float accumShare) {
+ super(apiLevel, versionName, codeName, share);
+ mAccumShare = accumShare;
+ }
+
+ public PlatformStat(PlatformStatBase base, float accumShare) {
+ super(base.getApiLevel(),
+ base.getVersionName(),
+ base.getCodeName(),
+ base.getShare());
+ mAccumShare = accumShare;
+ }
+
+ /** The accumulated approximate share percentage of that platform. */
+ public float getAccumShare() {
+ return mAccumShare;
+ }
+
+ /** Returns a string representation of this object, for debugging purposes. */
+ @Override
+ public String toString() {
+ return String.format("<Stat %s, accum=%.1f%%>", super.toString(), mAccumShare);
+ }
+ }
+
+ private final SparseArray<PlatformStat> mStats = new SparseArray<SdkStats.PlatformStat>();
+
+ public SdkStats() {
+ }
+
+ public SparseArray<PlatformStat> getStats() {
+ return mStats;
+ }
+
+ public void load(DownloadCache cache, boolean forceHttp, ITaskMonitor monitor) {
+
+ String url = SdkStatsConstants.URL_STATS;
+
+ if (forceHttp) {
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ monitor.setProgressMax(5);
+ monitor.setDescription("Fetching %1$s", url);
+ monitor.incProgress(1);
+
+ Exception[] exception = new Exception[] { null };
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ Document validatedDoc = null;
+ String validatedUri = null;
+
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
+
+ if (xml != null) {
+ monitor.setDescription("Validate XML");
+
+ // Explore the XML to find the potential XML schema version
+ int version = getXmlSchemaVersion(xml);
+
+ if (version >= 1 && version <= SdkStatsConstants.NS_LATEST_VERSION) {
+ // This should be a version we can handle. Try to validate it
+ // and report any error as invalid XML syntax,
+
+ String uri = validateXml(xml, url, version, validationError, validatorFound);
+ if (uri != null) {
+ // Validation was successful
+ validatedDoc = getDocument(xml, monitor);
+ validatedUri = uri;
+
+ }
+ } else if (version > SdkStatsConstants.NS_LATEST_VERSION) {
+ // The schema used is more recent than what is supported by this tool.
+ // We don't have an upgrade-path support yet, so simply ignore the document.
+ closeStream(xml);
+ return;
+ }
+ }
+
+ // If any exception was handled during the URL fetch, display it now.
+ if (exception[0] != null) {
+ String reason = null;
+ if (exception[0] instanceof FileNotFoundException) {
+ // FNF has no useful getMessage, so we need to special handle it.
+ reason = "File not found";
+ } else if (exception[0] instanceof UnknownHostException &&
+ exception[0].getMessage() != null) {
+ // This has no useful getMessage yet could really use one
+ reason = String.format("Unknown Host %1$s", exception[0].getMessage());
+ } else if (exception[0] instanceof SSLKeyException) {
+ // That's a common error and we have a pref for it.
+ reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
+ } else if (exception[0].getMessage() != null) {
+ reason = exception[0].getMessage();
+ } else {
+ // We don't know what's wrong. Let's give the exception class at least.
+ reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
+ }
+
+ monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
+ }
+
+ if (validationError[0] != null) {
+ monitor.logError("%s", validationError[0]); //$NON-NLS-1$
+ }
+
+ // Stop here if we failed to validate the XML. We don't want to load it.
+ if (validatedDoc == null) {
+ closeStream(xml);
+ return;
+ }
+
+ monitor.incProgress(1);
+
+ if (xml != null) {
+ monitor.setDescription("Parse XML");
+ monitor.incProgress(1);
+ parseStatsDocument(validatedDoc, validatedUri, monitor);
+ }
+
+ // done
+ monitor.incProgress(1);
+ closeStream(xml);
+ }
+
+ /**
+ * Fetches the document at the given URL and returns it as a stream. Returns
+ * null if anything wrong happens.
+ *
+ * @param urlString The URL to load, as a string.
+ * @param monitor {@link ITaskMonitor} related to this URL.
+ * @param outException If non null, where to store any exception that
+ * happens during the fetch.
+ * @see UrlOpener UrlOpener, which handles all URL logic.
+ */
+ private InputStream fetchXmlUrl(String urlString,
+ DownloadCache cache,
+ ITaskMonitor monitor,
+ Exception[] outException) {
+ try {
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml);
+ ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
+ } catch (Exception e) {
+ if (outException != null) {
+ outException[0] = e;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
+ * Manually parses the root element of the XML to extract the schema version
+ * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
+ * declaration.
+ *
+ * @return 1..{@link SdkStatsConstants#NS_LATEST_VERSION} for a valid schema version
+ * or 0 if no schema could be found.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected int getXmlSchemaVersion(InputStream xml) {
+ if (xml == null) {
+ return 0;
+ }
+
+ // Get an XML document
+ Document doc = null;
+ try {
+ xml.reset();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(false);
+ factory.setValidating(false);
+
+ // Parse the old document using a non namespace aware builder
+ factory.setNamespaceAware(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // We don't want the default handler which prints errors to stderr.
+ builder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+ // pass
+ }
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+
+ doc = builder.parse(xml);
+
+ // Prepare a new document using a namespace aware builder
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+
+ } catch (Exception e) {
+ // Failed to reset XML stream
+ // Failed to get builder factor
+ // Failed to create XML document builder
+ // Failed to parse XML document
+ // Failed to read XML document
+ }
+
+ if (doc == null) {
+ return 0;
+ }
+
+ // Check the root element is an XML with at least the following properties:
+ // <sdk:sdk-addons-list
+ // xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N">
+ //
+ // Note that we don't have namespace support enabled, we just do it manually.
+
+ Pattern nsPattern = Pattern.compile(SdkStatsConstants.NS_PATTERN);
+
+ String prefix = null;
+ for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ prefix = null;
+ String name = child.getNodeName();
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ prefix = name.substring(0, pos);
+ name = name.substring(pos + 1);
+ }
+ if (SdkStatsConstants.NODE_SDK_STATS.equals(name)) {
+ NamedNodeMap attrs = child.getAttributes();
+ String xmlns = "xmlns"; //$NON-NLS-1$
+ if (prefix != null) {
+ xmlns += ":" + prefix; //$NON-NLS-1$
+ }
+ Node attr = attrs.getNamedItem(xmlns);
+ if (attr != null) {
+ String uri = attr.getNodeValue();
+ if (uri != null) {
+ Matcher m = nsPattern.matcher(uri);
+ if (m.matches()) {
+ String version = m.group(1);
+ try {
+ return Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Validates this XML against one of the requested SDK Repository schemas.
+ * If the XML was correctly validated, returns the schema that worked.
+ * If it doesn't validate, returns null and stores the error in outError[0].
+ * If we can't find a validator, returns null and set validatorFound[0] to false.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected String validateXml(InputStream xml, String url, int version,
+ String[] outError, Boolean[] validatorFound) {
+
+ if (xml == null) {
+ return null;
+ }
+
+ try {
+ Validator validator = getValidator(version);
+
+ if (validator == null) {
+ validatorFound[0] = Boolean.FALSE;
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
+ url);
+ return null;
+ }
+
+ validatorFound[0] = Boolean.TRUE;
+
+ // Reset the stream if it supports that operation.
+ xml.reset();
+
+ // Validation throws a bunch of possible Exceptions on failure.
+ validator.validate(new StreamSource(xml));
+ return SdkStatsConstants.getSchemaUri(version);
+
+ } catch (SAXParseException e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
+ url,
+ e.getLineNumber(),
+ e.getColumnNumber(),
+ e.toString());
+
+ } catch (Exception e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nError: %2$s",
+ url,
+ e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Helper method that returns a validator for our XSD, or null if the current Java
+ * implementation can't process XSD schemas.
+ *
+ * @param version The version of the XML Schema.
+ * See {@link SdkStatsConstants#getXsdStream(int)}
+ */
+ private Validator getValidator(int version) throws SAXException {
+ InputStream xsdStream = SdkStatsConstants.getXsdStream(version);
+ try {
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+ if (factory == null) {
+ return null;
+ }
+
+ // 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 == null ? null : schema.newValidator();
+
+ return validator;
+ } finally {
+ if (xsdStream != null) {
+ try {
+ xsdStream.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+ /**
+ * Takes an XML document as a string as parameter and returns a DOM for it.
+ *
+ * On error, returns null and prints a (hopefully) useful message on the monitor.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(true);
+ factory.setNamespaceAware(true);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ xml.reset();
+ Document doc = builder.parse(new InputSource(xml));
+
+ return doc;
+ } catch (ParserConfigurationException e) {
+ monitor.logError("Failed to create XML document builder");
+
+ } catch (SAXException e) {
+ monitor.logError("Failed to parse XML document");
+
+ } catch (IOException e) {
+ monitor.logError("Failed to read XML document");
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses all valid platforms found in the XML.
+ * Changes the stats array returned by {@link #getStats()}
+ * (also returns the value directly, useful for unti tests.)
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected SparseArray<PlatformStat> parseStatsDocument(
+ Document doc,
+ String nsUri,
+ ITaskMonitor monitor) {
+
+ String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
+ if (baseUrl != null) {
+ if (baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
+ baseUrl = null;
+ }
+ }
+
+ SparseArray<PlatformStatBase> platforms = new SparseArray<SdkStats.PlatformStatBase>();
+ int maxApi = 0;
+
+ Node root = getFirstChild(doc, nsUri, SdkStatsConstants.NODE_SDK_STATS);
+ if (root != null) {
+ for (Node child = root.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI()) &&
+ child.getLocalName().equals(SdkStatsConstants.NODE_PLATFORM)) {
+
+ try {
+ Node node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_API_LEVEL);
+ int apiLevel = Integer.parseInt(node.getTextContent().trim());
+
+ if (apiLevel < 1) {
+ // bad API level, ignore it.
+ continue;
+ }
+
+ if (platforms.indexOfKey(apiLevel) >= 0) {
+ // if we already loaded that API, ignore duplicates
+ continue;
+ }
+
+ String codeName =
+ getFirstChild(child, nsUri, SdkStatsConstants.NODE_CODENAME).
+ getTextContent().trim();
+ String versName =
+ getFirstChild(child, nsUri, SdkStatsConstants.NODE_VERSION).
+ getTextContent().trim();
+
+ if (codeName == null || versName == null ||
+ codeName.length() == 0 || versName.length() == 0) {
+ // bad names. ignore.
+ continue;
+ }
+
+ node = getFirstChild(child, nsUri, SdkStatsConstants.NODE_SHARE);
+ float percent = Float.parseFloat(node.getTextContent().trim());
+
+ if (percent < 0 || percent > 100) {
+ // invalid percentage. ignore.
+ continue;
+ }
+
+ PlatformStatBase p = new PlatformStatBase(
+ apiLevel, versName, codeName, percent);
+ platforms.put(apiLevel, p);
+
+ maxApi = apiLevel > maxApi ? apiLevel : maxApi;
+
+ } catch (Exception ignore) {
+ // Error parsing this platform. Ignore it.
+ continue;
+ }
+ }
+ }
+ }
+
+ mStats.clear();
+
+ // Compute cumulative share percents & fill in final map
+ for (int api = 1; api <= maxApi; api++) {
+ PlatformStatBase p = platforms.get(api);
+ if (p == null) {
+ continue;
+ }
+
+ float sum = p.getShare();
+ for (int j = api + 1; j <= maxApi; j++) {
+ PlatformStatBase pj = platforms.get(j);
+ if (pj != null) {
+ sum += pj.getShare();
+ }
+ }
+
+ mStats.put(api, new PlatformStat(p, sum));
+ }
+
+ return mStats;
+ }
+
+ /**
+ * 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 nsUri, String xmlLocalName) {
+
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+ if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
+ return child;
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
index 74b023e..52724c7 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
@@ -18,7 +18,7 @@ package com.android.sdklib.internal.repository;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.util.Pair;
+import com.android.utils.Pair;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
@@ -37,36 +37,92 @@ import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
import java.net.ProxySelector;
+import java.net.URI;
import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
/**
- * This class holds methods for adding URLs management.
- * @see #openUrl(String, ITaskMonitor, Header[])
+ * This class holds static methods for downloading URL resources.
+ * @see #openUrl(String, boolean, ITaskMonitor, Header[])
+ * <p/>
+ * Implementation detail: callers should use {@link DownloadCache} instead of this class.
+ * {@link DownloadCache#openDirectUrl} is a direct pass-through to {@link UrlOpener} since
+ * there's no caching. However from an implementation perspective it's still recommended
+ * to pass down a {@link DownloadCache} instance, which will let us override the implementation
+ * later on (for testing, for example.)
*/
-public class UrlOpener {
+class UrlOpener {
- public static class CanceledByUserException extends Exception {
- private static final long serialVersionUID = -7669346110926032403L;
+ private static final boolean DEBUG =
+ System.getenv("ANDROID_DEBUG_URL_OPENER") != null; //$NON-NLS-1$
- public CanceledByUserException(String message) {
- super(message);
+ private static Map<String, UserCredentials> sRealmCache =
+ new HashMap<String, UserCredentials>();
+
+ /** Timeout to establish a connection, in milliseconds. */
+ private static int sConnectionTimeoutMs;
+ /** Timeout waiting for data on a socket, in milliseconds. */
+ private static int sSocketTimeoutMs;
+
+ static {
+ if (DEBUG) {
+ Properties props = System.getProperties();
+ for (String key : new String[] {
+ "http.proxyHost", //$NON-NLS-1$
+ "http.proxyPort", //$NON-NLS-1$
+ "https.proxyHost", //$NON-NLS-1$
+ "https.proxyPort" }) { //$NON-NLS-1$
+ String prop = props.getProperty(key);
+ if (prop != null) {
+ System.out.printf(
+ "SdkLib.UrlOpener Java.Prop %s='%s'\n", //$NON-NLS-1$
+ key, prop);
+ }
+ }
+ }
+
+ try {
+ sConnectionTimeoutMs = Integer.parseInt(System.getenv("ANDROID_SDKMAN_CONN_TIMEOUT"));
+ } catch (Exception ignore) {
+ sConnectionTimeoutMs = 2 * 60 * 1000;
+ }
+
+ try {
+ sSocketTimeoutMs = Integer.parseInt(System.getenv("ANDROID_SDKMAN_READ_TIMEOUT"));
+ } catch (Exception ignore) {
+ sSocketTimeoutMs = 1 * 60 * 1000;
}
}
- private static Map<String, UserCredentials> sRealmCache =
- new HashMap<String, UserCredentials>();
+ /**
+ * This class cannot be instantiated.
+ * @see #openUrl(String, boolean, ITaskMonitor, Header[])
+ */
+ private UrlOpener() {
+ }
/**
* Opens a URL. It can be a simple URL or one which requires basic
@@ -92,13 +148,21 @@ public class UrlOpener {
* available in the memory cache.
*
* @param url the URL string to be opened.
- * @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
+ * @param needsMarkResetSupport Indicates the caller <em>must</em> have an input stream that
+ * supports the mark/reset operations (as indicated by {@link InputStream#markSupported()}.
+ * Implementation detail: If the original stream does not, it will be fetched and wrapped
+ * into a {@link ByteArrayInputStream}. This can only work sanely if the resource is a
+ * small file that can fit in memory. It also means the caller has no chance of showing
+ * a meaningful download progress. If unsure, callers should set this to false.
+ * @param monitor {@link ITaskMonitor} to output status.
* @param headers An optional array of HTTP headers to use in the GET request.
- * @return Returns an {@link InputStream} holding the URL content and
- * the HttpResponse (locale, headers and an status line).
- * This never returns null; an exception is thrown instead in case of
- * error or if the user canceled an authentication dialog.
+ * @return Returns a {@link Pair} with {@code first} holding an {@link InputStream}
+ * and {@code second} holding an {@link HttpResponse}.
+ * The returned pair is never null and contains
+ * at least a code; for http requests that provide them the response
+ * also contains locale, headers and an status line.
+ * The input stream can be null, especially in case of error.
+ * The caller must only accept the stream if the response code is 200 or similar.
* @throws IOException Exception thrown when there are problems retrieving
* the URL or its content.
* @throws CanceledByUserException Exception thrown if the user cancels the
@@ -106,42 +170,189 @@ public class UrlOpener {
*/
static @NonNull Pair<InputStream, HttpResponse> openUrl(
@NonNull String url,
+ boolean needsMarkResetSupport,
@NonNull ITaskMonitor monitor,
@Nullable Header[] headers)
throws IOException, CanceledByUserException {
+ Exception fallbackOnJavaUrlConnect = null;
+ Pair<InputStream, HttpResponse> result = null;
+
try {
- return openWithHttpClient(url, monitor, headers);
+ result = openWithHttpClient(url, monitor, headers);
+
+ } catch (UnknownHostException e) {
+ // Host in unknown. No need to even retry with the Url object,
+ // if it's broken, it's broken. It's already an IOException but
+ // it could use a better message.
+ throw new IOException("Unknown Host " + e.getMessage(), e);
} catch (ClientProtocolException e) {
+ // We get this when HttpClient fails to accept the current protocol,
+ // e.g. when processing file:// URLs.
+ fallbackOnJavaUrlConnect = e;
+
+ } catch (IOException e) {
+ throw e;
+
+ } catch (CanceledByUserException e) {
+ // HTTP Basic Auth or NTLM login was canceled by user.
+ throw e;
+
+ } catch (Exception e) {
+ if (DEBUG) {
+ System.out.printf("[HttpClient Error] %s : %s\n", url, e.toString());
+ }
+
+ fallbackOnJavaUrlConnect = e;
+ }
+
+ if (fallbackOnJavaUrlConnect != null) {
// If the protocol is not supported by HttpClient (e.g. file:///),
- // revert to the standard java.net.Url.open
-
- URL u = new URL(url);
- InputStream is = u.openStream();
- HttpResponse response = new BasicHttpResponse(
- new ProtocolVersion(u.getProtocol(), 1, 0),
- 200, "");
- return Pair.of(is, response);
+ // revert to the standard java.net.Url.open.
+
+ try {
+ result = openWithUrl(url, headers);
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ if (DEBUG && !fallbackOnJavaUrlConnect.equals(e)) {
+ System.out.printf("[Url Error] %s : %s\n", url, e.toString());
+ }
+ }
+ }
+
+ // If the caller requires an InputStream that supports mark/reset, let's
+ // make sure we have such a stream.
+ if (result != null && needsMarkResetSupport) {
+ InputStream is = result.getFirst();
+ if (is != null) {
+ if (!is.markSupported()) {
+ try {
+ // Consume the whole input stream and offer a byte array stream instead.
+ // This can only work sanely if the resource is a small file that can
+ // fit in memory. It also means the caller has no chance of showing
+ // a meaningful download progress.
+ InputStream is2 = toByteArrayInputStream(is);
+ if (is2 != null) {
+ result = Pair.of(is2, result.getSecond());
+ try {
+ is.close();
+ } catch (Exception ignore) {}
+ }
+ } catch (Exception e3) {
+ // Ignore. If this can't work, caller will fail later.
+ }
+ }
+ }
+ }
+
+ if (result == null) {
+ // Make up an error code if we don't have one already.
+ HttpResponse outResponse = new BasicHttpResponse(
+ new ProtocolVersion("HTTP", 1, 0), //$NON-NLS-1$
+ HttpStatus.SC_METHOD_FAILURE, ""); //$NON-NLS-1$; // 420=Method Failure
+ result = Pair.of(null, outResponse);
}
+
+ return result;
+ }
+
+ // ByteArrayInputStream is the duct tape of input streams.
+ private static InputStream toByteArrayInputStream(InputStream is) throws IOException {
+ int inc = 4096;
+ int curr = 0;
+ byte[] result = new byte[inc];
+
+ int n;
+ while ((n = is.read(result, curr, result.length - curr)) != -1) {
+ curr += n;
+ if (curr == result.length) {
+ byte[] temp = new byte[curr + inc];
+ System.arraycopy(result, 0, temp, 0, curr);
+ result = temp;
+ }
+ }
+
+ return new ByteArrayInputStream(result, 0, curr);
+ }
+
+ private static Pair<InputStream, HttpResponse> openWithUrl(
+ String url,
+ Header[] inHeaders) throws IOException {
+ URL u = new URL(url);
+
+ URLConnection c = u.openConnection();
+
+ c.setConnectTimeout(sConnectionTimeoutMs);
+ c.setReadTimeout(sSocketTimeoutMs);
+
+ if (inHeaders != null) {
+ for (Header header : inHeaders) {
+ c.setRequestProperty(header.getName(), header.getValue());
+ }
+ }
+
+ // Trigger the access to the resource
+ // (at which point setRequestProperty can't be used anymore.)
+ int code = 200;
+
+ if (c instanceof HttpURLConnection) {
+ code = ((HttpURLConnection) c).getResponseCode();
+ }
+
+ // Get the input stream. That can fail for a file:// that doesn't exist
+ // in which case we set the response code to 404.
+ // Also we need a buffered input stream since the caller need to use is.reset().
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(c.getInputStream());
+ } catch (Exception ignore) {
+ if (is == null && code == 200) {
+ code = 404;
+ }
+ }
+
+ HttpResponse outResponse = new BasicHttpResponse(
+ new ProtocolVersion(u.getProtocol(), 1, 0), // make up the protocol version
+ code, ""); //$NON-NLS-1$;
+
+ Map<String, List<String>> outHeaderMap = c.getHeaderFields();
+
+ for (Entry<String, List<String>> entry : outHeaderMap.entrySet()) {
+ String name = entry.getKey();
+ if (name != null) {
+ List<String> values = entry.getValue();
+ if (!values.isEmpty()) {
+ outResponse.setHeader(name, values.get(0));
+ }
+ }
+ }
+
+ return Pair.of(is, outResponse);
}
private static @NonNull Pair<InputStream, HttpResponse> openWithHttpClient(
@NonNull String url,
@NonNull ITaskMonitor monitor,
- Header[] headers)
+ Header[] inHeaders)
throws IOException, ClientProtocolException, CanceledByUserException {
UserCredentials result = null;
String realm = null;
+ HttpParams params = new BasicHttpParams();
+ HttpConnectionParams.setConnectionTimeout(params, sConnectionTimeoutMs);
+ HttpConnectionParams.setSoTimeout(params, sSocketTimeoutMs);
+
// use the simple one
- final DefaultHttpClient httpClient = new DefaultHttpClient();
+ final DefaultHttpClient httpClient = new DefaultHttpClient(params);
+
// create local execution context
HttpContext localContext = new BasicHttpContext();
final HttpGet httpGet = new HttpGet(url);
- if (headers != null) {
- for (Header header : headers) {
+ if (inHeaders != null) {
+ for (Header header : inHeaders) {
httpGet.addHeader(header);
}
}
@@ -166,6 +377,24 @@ public class UrlOpener {
httpClient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authpref);
+ if (DEBUG) {
+ try {
+ URI uri = new URI(url);
+ ProxySelector sel = routePlanner.getProxySelector();
+ if (sel != null && uri.getScheme().startsWith("httP")) { //$NON-NLS-1$
+ List<Proxy> list = sel.select(uri);
+ System.out.printf(
+ "SdkLib.UrlOpener:\n Connect to: %s\n Proxy List: %s\n", //$NON-NLS-1$
+ url,
+ list == null ? "(null)" : Arrays.toString(list.toArray()));//$NON-NLS-1$
+ }
+ } catch (Exception e) {
+ System.out.printf(
+ "SdkLib.UrlOpener: Failed to get proxy info for %s: %s\n", //$NON-NLS-1$
+ url, e.toString());
+ }
+ }
+
boolean trying = true;
// loop while the response is being fetched
while (trying) {
@@ -173,6 +402,10 @@ public class UrlOpener {
HttpResponse response = httpClient.execute(httpGet, localContext);
int statusCode = response.getStatusLine().getStatusCode();
+ if (DEBUG) {
+ System.out.printf(" Status: %d\n", statusCode); //$NON-NLS-1$
+ }
+
// check whether any authentication is required
AuthState authenticationState = null;
if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/Archive.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/Archive.java
index cd79275..508911f 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/Archive.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/Archive.java
@@ -1,504 +1,504 @@
-/*
- * 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.archives;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.FileOp;
-
-import java.io.File;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
-import java.util.Properties;
-
-
-/**
- * A {@link Archive} is the base class for "something" that can be downloaded from
- * the SDK repository.
- * <p/>
- * A package has some attributes (revision, description) and a list of archives
- * which represent the downloadable bits.
- * <p/>
- * Packages are offered by a {@link SdkSource} (a download site).
- * The {@link ArchiveInstaller} takes care of downloading, unpacking and installing an archive.
- */
-public class Archive implements IDescription, Comparable<Archive> {
-
- private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$
- private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
-
- /** The checksum type. */
- public enum ChecksumType {
- /** A SHA1 checksum, represented as a 40-hex string. */
- SHA1("SHA-1"); //$NON-NLS-1$
-
- private final String mAlgorithmName;
-
- /**
- * Constructs a {@link ChecksumType} with the algorigth name
- * suitable for {@link MessageDigest#getInstance(String)}.
- * <p/>
- * 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("Any"),
- LINUX("Linux"),
- MACOSX("MacOS X"),
- WINDOWS("Windows");
-
- private final String mUiName;
-
- private Os(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the OS. */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the XML name of the OS. */
- public String getXmlName() {
- return toString().toLowerCase(Locale.US);
- }
-
- /**
- * 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")) { //$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;
- }
-
- /** Returns true if this OS is compatible with the current one. */
- public boolean isCompatible() {
- if (this == ANY) {
- return true;
- }
-
- Os os = getCurrentOs();
- return this == os;
- }
- }
-
- /** The Architecture that this archive can be downloaded on. */
- public enum Arch {
- ANY("Any"),
- PPC("PowerPC"),
- X86("x86"),
- X86_64("x86_64");
-
- private final String mUiName;
-
- private Arch(String uiName) {
- mUiName = uiName;
- }
-
- /** Returns the UI name of the architecture. */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the XML name of the architecture. */
- public String getXmlName() {
- return toString().toLowerCase(Locale.US);
- }
-
- /**
- * 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;
- }
-
- /** Returns true if this architecture is compatible with the current one. */
- public boolean isCompatible() {
- if (this == ANY) {
- return true;
- }
-
- Arch arch = getCurrentArch();
- return this == arch;
- }
- }
-
- private final Os mOs;
- private final Arch mArch;
- private final String mUrl;
- private final long mSize;
- private final String mChecksum;
- private final ChecksumType mChecksumType = ChecksumType.SHA1;
- private final Package mPackage;
- private final String mLocalOsPath;
- private final boolean mIsLocal;
-
- /**
- * Creates a new remote archive.
- */
- public Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
- mPackage = pkg;
- mOs = os;
- mArch = arch;
- mUrl = url == null ? null : url.trim();
- mLocalOsPath = null;
- mSize = size;
- mChecksum = checksum;
- mIsLocal = false;
- }
-
- /**
- * Creates a new local archive.
- * Uses the properties from props first, if possible. Props can be null.
- */
- @VisibleForTesting(visibility=Visibility.PACKAGE)
- public Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) {
- mPackage = pkg;
-
- mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString()));
- mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString()));
-
- mUrl = null;
- mLocalOsPath = localOsPath;
- mSize = 0;
- mChecksum = "";
- mIsLocal = localOsPath != null;
- }
-
- /**
- * Save the properties of the current archive in the give {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- void saveProperties(Properties props) {
- props.setProperty(PROP_OS, mOs.toString());
- props.setProperty(PROP_ARCH, mArch.toString());
- }
-
- /**
- * Returns true if this is a locally installed archive.
- * Returns false if this is a remote archive that needs to be downloaded.
- */
- public boolean isLocal() {
- return mIsLocal;
- }
-
- /**
- * 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.
- * 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.
- */
- public ChecksumType getChecksumType() {
- return mChecksumType;
- }
-
- /**
- * Returns the download archive URL, either absolute or relative to the repository xml.
- * Always return null for a local installed folder.
- * @see #getLocalOsPath()
- */
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * Returns the local OS folder where a local archive is installed.
- * Always return null for remote archives.
- * @see #getUrl()
- */
- public String getLocalOsPath() {
- return mLocalOsPath;
- }
-
- /**
- * 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.
- * Can be null for a local installed folder on an unknown architecture.
- */
- public Arch getArch() {
- return mArch;
- }
-
- /**
- * Generates a description for this archive of the OS/Arch supported by this archive.
- */
- public String getOsDescription() {
- String os;
- if (mOs == null) {
- os = "unknown OS";
- } else if (mOs == Os.ANY) {
- os = "any OS";
- } else {
- os = mOs.getUiName();
- }
-
- String arch = ""; //$NON-NLS-1$
- if (mArch != null && mArch != Arch.ANY) {
- arch = mArch.getUiName();
- }
-
- return String.format("%1$s%2$s%3$s",
- os,
- arch.length() > 0 ? " " : "", //$NON-NLS-2$
- arch);
- }
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- /**
- * Generates a short description for this archive.
- */
- @Override
- public String getShortDescription() {
- return String.format("Archive for %1$s", getOsDescription());
- }
-
- /**
- * Generates a longer description for this archive.
- */
- @Override
- public String getLongDescription() {
- return String.format("%1$s\n%2$s\n%3$s",
- getShortDescription(),
- getSizeDescription(),
- getSha1Description());
- }
-
- public String getSizeDescription() {
- long size = getSize();
- String sizeStr;
- if (size < 1024) {
- sizeStr = String.format("%d Bytes", size);
- } else if (size < 1024 * 1024) {
- sizeStr = String.format("%d KiB", Math.round(size / 1024.0));
- } else if (size < 1024 * 1024 * 1024) {
- sizeStr = String.format("%.1f MiB",
- Math.round(10.0 * size / (1024 * 1024.0))/ 10.0);
- } else {
- sizeStr = String.format("%.1f GiB",
- Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0);
- }
-
- return String.format("Size: %1$s", sizeStr);
- }
-
- public String getSha1Description() {
- return String.format("SHA1: %1$s", getChecksum());
- }
-
- /**
- * Returns true if this archive can be installed on the current platform.
- */
- public boolean isCompatible() {
- return getOs().isCompatible() && getArch().isCompatible();
- }
-
- /**
- * Delete the archive folder if this is a local archive.
- */
- public void deleteLocal() {
- if (isLocal()) {
- new FileOp().deleteFileOrFolder(new File(getLocalOsPath()));
- }
- }
-
- /**
- * Archives are compared using their {@link Package} ordering.
- *
- * @see Package#compareTo(Package)
- */
- @Override
- public int compareTo(Archive rhs) {
- if (mPackage != null && rhs != null) {
- return mPackage.compareTo(rhs.getParentPackage());
- }
- return 0;
- }
-
- /**
- * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mArch == null) ? 0 : mArch.hashCode());
- result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode());
- result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode());
- result = prime * result + (mIsLocal ? 1231 : 1237);
- result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode());
- result = prime * result + ((mOs == null) ? 0 : mOs.hashCode());
- result = prime * result + (int) (mSize ^ (mSize >>> 32));
- result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode());
- return result;
- }
-
- /**
- * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Archive)) {
- return false;
- }
- Archive other = (Archive) obj;
- if (mArch == null) {
- if (other.mArch != null) {
- return false;
- }
- } else if (!mArch.equals(other.mArch)) {
- return false;
- }
- if (mChecksum == null) {
- if (other.mChecksum != null) {
- return false;
- }
- } else if (!mChecksum.equals(other.mChecksum)) {
- return false;
- }
- if (mChecksumType == null) {
- if (other.mChecksumType != null) {
- return false;
- }
- } else if (!mChecksumType.equals(other.mChecksumType)) {
- return false;
- }
- if (mIsLocal != other.mIsLocal) {
- return false;
- }
- if (mLocalOsPath == null) {
- if (other.mLocalOsPath != null) {
- return false;
- }
- } else if (!mLocalOsPath.equals(other.mLocalOsPath)) {
- return false;
- }
- if (mOs == null) {
- if (other.mOs != null) {
- return false;
- }
- } else if (!mOs.equals(other.mOs)) {
- return false;
- }
- if (mSize != other.mSize) {
- return false;
- }
- if (mUrl == null) {
- if (other.mUrl != null) {
- return false;
- }
- } else if (!mUrl.equals(other.mUrl)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.archives;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.io.FileOp;
+
+import java.io.File;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+import java.util.Properties;
+
+
+/**
+ * A {@link Archive} is the base class for "something" that can be downloaded from
+ * the SDK repository.
+ * <p/>
+ * A package has some attributes (revision, description) and a list of archives
+ * which represent the downloadable bits.
+ * <p/>
+ * Packages are offered by a {@link SdkSource} (a download site).
+ * The {@link ArchiveInstaller} takes care of downloading, unpacking and installing an archive.
+ */
+public class Archive implements IDescription, Comparable<Archive> {
+
+ private static final String PROP_OS = "Archive.Os"; //$NON-NLS-1$
+ private static final String PROP_ARCH = "Archive.Arch"; //$NON-NLS-1$
+
+ /** The checksum type. */
+ public enum ChecksumType {
+ /** A SHA1 checksum, represented as a 40-hex string. */
+ SHA1("SHA-1"); //$NON-NLS-1$
+
+ private final String mAlgorithmName;
+
+ /**
+ * Constructs a {@link ChecksumType} with the algorigth name
+ * suitable for {@link MessageDigest#getInstance(String)}.
+ * <p/>
+ * 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("Any"),
+ LINUX("Linux"),
+ MACOSX("MacOS X"),
+ WINDOWS("Windows");
+
+ private final String mUiName;
+
+ private Os(String uiName) {
+ mUiName = uiName;
+ }
+
+ /** Returns the UI name of the OS. */
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /** Returns the XML name of the OS. */
+ public String getXmlName() {
+ return toString().toLowerCase(Locale.US);
+ }
+
+ /**
+ * 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")) { //$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;
+ }
+
+ /** Returns true if this OS is compatible with the current one. */
+ public boolean isCompatible() {
+ if (this == ANY) {
+ return true;
+ }
+
+ Os os = getCurrentOs();
+ return this == os;
+ }
+ }
+
+ /** The Architecture that this archive can be downloaded on. */
+ public enum Arch {
+ ANY("Any"),
+ PPC("PowerPC"),
+ X86("x86"),
+ X86_64("x86_64");
+
+ private final String mUiName;
+
+ private Arch(String uiName) {
+ mUiName = uiName;
+ }
+
+ /** Returns the UI name of the architecture. */
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /** Returns the XML name of the architecture. */
+ public String getXmlName() {
+ return toString().toLowerCase(Locale.US);
+ }
+
+ /**
+ * 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;
+ }
+
+ /** Returns true if this architecture is compatible with the current one. */
+ public boolean isCompatible() {
+ if (this == ANY) {
+ return true;
+ }
+
+ Arch arch = getCurrentArch();
+ return this == arch;
+ }
+ }
+
+ private final Os mOs;
+ private final Arch mArch;
+ private final String mUrl;
+ private final long mSize;
+ private final String mChecksum;
+ private final ChecksumType mChecksumType = ChecksumType.SHA1;
+ private final Package mPackage;
+ private final String mLocalOsPath;
+ private final boolean mIsLocal;
+
+ /**
+ * Creates a new remote archive.
+ */
+ public Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
+ mPackage = pkg;
+ mOs = os;
+ mArch = arch;
+ mUrl = url == null ? null : url.trim();
+ mLocalOsPath = null;
+ mSize = size;
+ mChecksum = checksum;
+ mIsLocal = false;
+ }
+
+ /**
+ * Creates a new local archive.
+ * Uses the properties from props first, if possible. Props can be null.
+ */
+ @VisibleForTesting(visibility=Visibility.PACKAGE)
+ public Archive(Package pkg, Properties props, Os os, Arch arch, String localOsPath) {
+ mPackage = pkg;
+
+ mOs = props == null ? os : Os.valueOf( props.getProperty(PROP_OS, os.toString()));
+ mArch = props == null ? arch : Arch.valueOf(props.getProperty(PROP_ARCH, arch.toString()));
+
+ mUrl = null;
+ mLocalOsPath = localOsPath;
+ mSize = 0;
+ mChecksum = "";
+ mIsLocal = localOsPath != null;
+ }
+
+ /**
+ * Save the properties of the current archive in the give {@link Properties} object.
+ * These properties will later be give the constructor that takes a {@link Properties} object.
+ */
+ void saveProperties(Properties props) {
+ props.setProperty(PROP_OS, mOs.toString());
+ props.setProperty(PROP_ARCH, mArch.toString());
+ }
+
+ /**
+ * Returns true if this is a locally installed archive.
+ * Returns false if this is a remote archive that needs to be downloaded.
+ */
+ public boolean isLocal() {
+ return mIsLocal;
+ }
+
+ /**
+ * 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.
+ * 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.
+ */
+ public ChecksumType getChecksumType() {
+ return mChecksumType;
+ }
+
+ /**
+ * Returns the download archive URL, either absolute or relative to the repository xml.
+ * Always return null for a local installed folder.
+ * @see #getLocalOsPath()
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Returns the local OS folder where a local archive is installed.
+ * Always return null for remote archives.
+ * @see #getUrl()
+ */
+ public String getLocalOsPath() {
+ return mLocalOsPath;
+ }
+
+ /**
+ * 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.
+ * Can be null for a local installed folder on an unknown architecture.
+ */
+ public Arch getArch() {
+ return mArch;
+ }
+
+ /**
+ * Generates a description for this archive of the OS/Arch supported by this archive.
+ */
+ public String getOsDescription() {
+ String os;
+ if (mOs == null) {
+ os = "unknown OS";
+ } else if (mOs == Os.ANY) {
+ os = "any OS";
+ } else {
+ os = mOs.getUiName();
+ }
+
+ String arch = ""; //$NON-NLS-1$
+ if (mArch != null && mArch != Arch.ANY) {
+ arch = mArch.getUiName();
+ }
+
+ return String.format("%1$s%2$s%3$s",
+ os,
+ arch.length() > 0 ? " " : "", //$NON-NLS-2$
+ arch);
+ }
+
+ /**
+ * Returns the short description of the source, if not null.
+ * Otherwise returns the default Object toString result.
+ * <p/>
+ * This is mostly helpful for debugging.
+ * For UI display, use the {@link IDescription} interface.
+ */
+ @Override
+ public String toString() {
+ String s = getShortDescription();
+ if (s != null) {
+ return s;
+ }
+ return super.toString();
+ }
+
+ /**
+ * Generates a short description for this archive.
+ */
+ @Override
+ public String getShortDescription() {
+ return String.format("Archive for %1$s", getOsDescription());
+ }
+
+ /**
+ * Generates a longer description for this archive.
+ */
+ @Override
+ public String getLongDescription() {
+ return String.format("%1$s\n%2$s\n%3$s",
+ getShortDescription(),
+ getSizeDescription(),
+ getSha1Description());
+ }
+
+ public String getSizeDescription() {
+ long size = getSize();
+ String sizeStr;
+ if (size < 1024) {
+ sizeStr = String.format("%d Bytes", size);
+ } else if (size < 1024 * 1024) {
+ sizeStr = String.format("%d KiB", Math.round(size / 1024.0));
+ } else if (size < 1024 * 1024 * 1024) {
+ sizeStr = String.format("%.1f MiB",
+ Math.round(10.0 * size / (1024 * 1024.0))/ 10.0);
+ } else {
+ sizeStr = String.format("%.1f GiB",
+ Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0);
+ }
+
+ return String.format("Size: %1$s", sizeStr);
+ }
+
+ public String getSha1Description() {
+ return String.format("SHA1: %1$s", getChecksum());
+ }
+
+ /**
+ * Returns true if this archive can be installed on the current platform.
+ */
+ public boolean isCompatible() {
+ return getOs().isCompatible() && getArch().isCompatible();
+ }
+
+ /**
+ * Delete the archive folder if this is a local archive.
+ */
+ public void deleteLocal() {
+ if (isLocal()) {
+ new FileOp().deleteFileOrFolder(new File(getLocalOsPath()));
+ }
+ }
+
+ /**
+ * Archives are compared using their {@link Package} ordering.
+ *
+ * @see Package#compareTo(Package)
+ */
+ @Override
+ public int compareTo(Archive rhs) {
+ if (mPackage != null && rhs != null) {
+ return mPackage.compareTo(rhs.getParentPackage());
+ }
+ return 0;
+ }
+
+ /**
+ * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mArch == null) ? 0 : mArch.hashCode());
+ result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode());
+ result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode());
+ result = prime * result + (mIsLocal ? 1231 : 1237);
+ result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode());
+ result = prime * result + ((mOs == null) ? 0 : mOs.hashCode());
+ result = prime * result + (int) (mSize ^ (mSize >>> 32));
+ result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode());
+ return result;
+ }
+
+ /**
+ * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Archive)) {
+ return false;
+ }
+ Archive other = (Archive) obj;
+ if (mArch == null) {
+ if (other.mArch != null) {
+ return false;
+ }
+ } else if (!mArch.equals(other.mArch)) {
+ return false;
+ }
+ if (mChecksum == null) {
+ if (other.mChecksum != null) {
+ return false;
+ }
+ } else if (!mChecksum.equals(other.mChecksum)) {
+ return false;
+ }
+ if (mChecksumType == null) {
+ if (other.mChecksumType != null) {
+ return false;
+ }
+ } else if (!mChecksumType.equals(other.mChecksumType)) {
+ return false;
+ }
+ if (mIsLocal != other.mIsLocal) {
+ return false;
+ }
+ if (mLocalOsPath == null) {
+ if (other.mLocalOsPath != null) {
+ return false;
+ }
+ } else if (!mLocalOsPath.equals(other.mLocalOsPath)) {
+ return false;
+ }
+ if (mOs == null) {
+ if (other.mOs != null) {
+ return false;
+ }
+ } else if (!mOs.equals(other.mOs)) {
+ return false;
+ }
+ if (mSize != other.mSize) {
+ return false;
+ }
+ if (mUrl == null) {
+ if (other.mUrl != null) {
+ return false;
+ }
+ } else if (!mUrl.equals(other.mUrl)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
index 3addb31..75e8912 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveInstaller.java
@@ -1,1025 +1,1167 @@
-/*
- * Copyright (C) 2010 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.archives;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.FileOp;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.apache.commons.compress.archivers.zip.ZipFile;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/**
- * Performs the work of installing a given {@link Archive}.
- */
-public class ArchiveInstaller {
-
- public static final String ENV_VAR_IGNORE_COMPAT = "ANDROID_SDK_IGNORE_COMPAT";
-
- public static final int NUM_MONITOR_INC = 100;
-
- /** The current {@link FileOp} to use. Never null. */
- private final IFileOp mFileOp;
-
- /**
- * Generates an {@link ArchiveInstaller} that relies on the default {@link FileOp}.
- */
- public ArchiveInstaller() {
- mFileOp = new FileOp();
- }
-
- /**
- * Generates an {@link ArchiveInstaller} that relies on the given {@link FileOp}.
- *
- * @param fileUtils An alternate version of {@link FileOp} to use for file operations.
- */
- protected ArchiveInstaller(IFileOp fileUtils) {
- mFileOp = fileUtils;
- }
-
- /** Returns current {@link FileOp} to use. Never null. */
- protected IFileOp getFileOp() {
- return mFileOp;
- }
-
- /**
- * Install this {@link ArchiveReplacement}s.
- * A "replacement" is composed of the actual new archive to install
- * (c.f. {@link ArchiveReplacement#getNewArchive()} and an <em>optional</em>
- * archive being replaced (c.f. {@link ArchiveReplacement#getReplaced()}.
- * In the case of a new install, the later should be null.
- * <p/>
- * The new archive to install will be skipped if it is incompatible.
- *
- * @return True if the archive was installed, false otherwise.
- */
- public boolean install(ArchiveReplacement archiveInfo,
- String osSdkRoot,
- boolean forceHttp,
- SdkManager sdkManager,
- DownloadCache cache,
- ITaskMonitor monitor) {
-
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
-
- File archiveFile = null;
- String name = pkg.getShortDescription();
-
- if (newArchive.isLocal()) {
- // This should never happen.
- monitor.log("Skipping already installed archive: %1$s for %2$s",
- name,
- newArchive.getOsDescription());
- return false;
- }
-
- // In detail mode, give us a way to force install of incompatible archives.
- boolean checkIsCompatible = System.getenv(ENV_VAR_IGNORE_COMPAT) == null;
-
- if (checkIsCompatible && !newArchive.isCompatible()) {
- monitor.log("Skipping incompatible archive: %1$s for %2$s",
- name,
- newArchive.getOsDescription());
- return false;
- }
-
- archiveFile = downloadFile(newArchive, osSdkRoot, cache, monitor, forceHttp);
- if (archiveFile != null) {
- // Unarchive calls the pre/postInstallHook methods.
- if (unarchive(archiveInfo, osSdkRoot, archiveFile, sdkManager, monitor)) {
- monitor.log("Installed %1$s", name);
- // Delete the temp archive if it exists, only on success
- mFileOp.deleteFileOrFolder(archiveFile);
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Downloads an archive and returns the temp file with it.
- * Caller is responsible with deleting the temp file when done.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected File downloadFile(Archive archive,
- String osSdkRoot,
- DownloadCache cache,
- ITaskMonitor monitor,
- boolean forceHttp) {
-
- String pkgName = archive.getParentPackage().getShortDescription();
- monitor.setDescription("Downloading %1$s", pkgName);
- monitor.log("Downloading %1$s", pkgName);
-
- 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();
- SdkSource src = pkg.getParentSource();
- if (src == null) {
- monitor.logError("Internal error: no source for archive %1$s", pkgName);
- return null;
- }
-
- // take the URL to the repository.xml and remove the last component
- // to get the base
- String repoXml = src.getUrl();
- int pos = repoXml.lastIndexOf('/');
- String base = repoXml.substring(0, pos + 1);
-
- link = base + link;
- }
-
- if (forceHttp) {
- link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Get the basename of the file we're downloading, i.e. the last component
- // of the URL
- int pos = link.lastIndexOf('/');
- String base = link.substring(pos + 1);
-
- // Rather than create a real temp file in the system, we simply use our
- // temp folder (in the SDK base folder) and use the archive name for the
- // download. This allows us to reuse or continue downloads.
-
- File tmpFolder = getTempFolder(osSdkRoot);
- if (!mFileOp.isDirectory(tmpFolder)) {
- if (mFileOp.isFile(tmpFolder)) {
- mFileOp.deleteFileOrFolder(tmpFolder);
- }
- if (!mFileOp.mkdirs(tmpFolder)) {
- monitor.logError("Failed to create directory %1$s", tmpFolder.getPath());
- return null;
- }
- }
- File tmpFile = new File(tmpFolder, base);
-
- // if the file exists, check its checksum & size. Use it if complete
- if (mFileOp.exists(tmpFile)) {
- if (mFileOp.length(tmpFile) == archive.getSize()) {
- String chksum = ""; //$NON-NLS-1$
- try {
- chksum = fileChecksum(archive.getChecksumType().getMessageDigest(),
- tmpFile,
- monitor);
- } catch (NoSuchAlgorithmException e) {
- // Ignore.
- }
- if (chksum.equalsIgnoreCase(archive.getChecksum())) {
- // File is good, let's use it.
- return tmpFile;
- }
- }
-
- // Existing file is either of different size or content.
- // TODO: continue download when we support continue mode.
- // Right now, let's simply remove the file and start over.
- mFileOp.deleteFileOrFolder(tmpFile);
- }
-
- if (fetchUrl(archive, tmpFile, link, pkgName, cache, monitor)) {
- // Fetching was successful, let's use this file.
- return tmpFile;
- } else {
- // Delete the temp file if we aborted the download
- // TODO: disable this when we want to support partial downloads.
- mFileOp.deleteFileOrFolder(tmpFile);
- return null;
- }
- }
-
- /**
- * Computes the SHA-1 checksum of the content of the given file.
- * Returns an empty string on error (rather than null).
- */
- private String fileChecksum(MessageDigest digester, File tmpFile, ITaskMonitor monitor) {
- InputStream is = null;
- try {
- is = new FileInputStream(tmpFile);
-
- byte[] buf = new byte[65536];
- int n;
-
- while ((n = is.read(buf)) >= 0) {
- if (n > 0) {
- digester.update(buf, 0, n);
- }
- }
-
- return getDigestChecksum(digester);
-
- } catch (FileNotFoundException e) {
- // The FNF message is just the URL. Make it a bit more useful.
- monitor.logError("File not found: %1$s", e.getMessage());
-
- } catch (Exception e) {
- monitor.logError("%1$s", e.getMessage()); //$NON-NLS-1$
-
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns the SHA-1 from a {@link MessageDigest} as an hex string
- * that can be compared with {@link Archive#getChecksum()}.
- */
- private String getDigestChecksum(MessageDigest digester) {
- int n;
- // 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++) {
- int b = digest[i] & 0x0FF;
- hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
- hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
- }
-
- return new String(hexDigest);
- }
-
- /**
- * Actually performs the download.
- * Also computes the SHA1 of the file on the fly.
- * <p/>
- * 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.
- * <p/>
- * Increments the monitor by {@link #NUM_MONITOR_INC}.
- */
- private boolean fetchUrl(Archive archive,
- File tmpFile,
- String urlString,
- String pkgName,
- DownloadCache cache,
- ITaskMonitor monitor) {
-
- FileOutputStream os = null;
- InputStream is = null;
- try {
- is = cache.openDirectUrl(urlString, monitor);
- 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_MONITOR_INC;
- long next_inc = inc;
-
- long startMs = System.currentTimeMillis();
- long nextMs = startMs + 2000; // start update after 2 seconds
-
- while ((n = is.read(buf)) >= 0) {
- if (n > 0) {
- os.write(buf, 0, n);
- digester.update(buf, 0, n);
- }
-
- long timeMs = System.currentTimeMillis();
-
- total += n;
- if (total >= next_inc) {
- monitor.incProgress(1);
- next_inc += inc;
- }
-
- if (timeMs > nextMs) {
- long delta = timeMs - startMs;
- if (total > 0 && delta > 0) {
- // percent left to download
- int percent = (int) (100 * total / size);
- // speed in KiB/s
- float speed = (float)total / (float)delta * (1000.f / 1024.f);
- // time left to download the rest at the current KiB/s rate
- int timeLeft = (speed > 1e-3) ?
- (int)(((size - total) / 1024.0f) / speed) :
- 0;
- String timeUnit = "seconds";
- if (timeLeft > 120) {
- timeUnit = "minutes";
- timeLeft /= 60;
- }
-
- monitor.setDescription(
- "Downloading %1$s (%2$d%%, %3$.0f KiB/s, %4$d %5$s left)",
- pkgName,
- percent,
- speed,
- timeLeft,
- timeUnit);
- }
- nextMs = timeMs + 1000; // update every second
- }
-
- if (monitor.isCancelRequested()) {
- monitor.log("Download aborted by user at %1$d bytes.", total);
- return false;
- }
-
- }
-
- if (total != size) {
- monitor.logError(
- "Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
- size, total);
- return false;
- }
-
- // Create an hex string from the digest
- String actual = getDigestChecksum(digester);
- String expected = archive.getChecksum();
- if (!actual.equalsIgnoreCase(expected)) {
- monitor.logError("Download finished with wrong checksum. Expected %1$s, got %2$s.",
- expected, actual);
- return false;
- }
-
- return true;
-
- } catch (FileNotFoundException e) {
- // The FNF message is just the URL. Make it a bit more useful.
- monitor.logError("File not found: %1$s", e.getMessage());
-
- } catch (Exception e) {
- monitor.logError("%1$s", e.getMessage()); //$NON-NLS-1$
-
- } finally {
- if (os != null) {
- try {
- os.close();
- } catch (IOException e) {
- // pass
- }
- }
-
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- return false;
- }
-
- /**
- * Install the given archive in the given folder.
- */
- private boolean unarchive(ArchiveReplacement archiveInfo,
- String osSdkRoot,
- File archiveFile,
- SdkManager sdkManager,
- ITaskMonitor monitor) {
- boolean success = false;
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
- String pkgName = pkg.getShortDescription();
- monitor.setDescription("Installing %1$s", pkgName);
- monitor.log("Installing %1$s", pkgName);
-
- // Ideally we want to always unzip in a temp folder which name depends on the package
- // type (e.g. addon, tools, etc.) and then move the folder to the destination folder.
- // If the destination folder exists, it will be renamed and deleted at the very
- // end if everything succeeded. This provides a nice atomic swap and should leave the
- // original folder untouched in case something wrong (e.g. program crash) in the
- // middle of the unzip operation.
- //
- // However that doesn't work on Windows, we always end up not being able to move the
- // new folder. There are actually 2 cases:
- // A- A process such as a the explorer is locking the *old* folder or a file inside
- // (e.g. adb.exe)
- // In this case we really shouldn't be tried to work around it and we need to let
- // the user know and let it close apps that access that folder.
- // B- A process is locking the *new* folder. Very often this turns to be a file indexer
- // or an anti-virus that is busy scanning the new folder that we just unzipped.
- //
- // So we're going to change the strategy:
- // 1- Try to move the old folder to a temp/old folder. This might fail in case of issue A.
- // Note: for platform-tools, we can try killing adb first.
- // If it still fails, we do nothing and ask the user to terminate apps that can be
- // locking that folder.
- // 2- Once the old folder is out of the way, we unzip the archive directly into the
- // optimal new location. We no longer unzip it in a temp folder and move it since we
- // know that's what fails in most of the cases.
- // 3- If the unzip fails, remove everything and try to restore the old folder by doing
- // a *copy* in place and not a folder move (which will likely fail too).
-
- String pkgKind = pkg.getClass().getSimpleName();
-
- File destFolder = null;
- File oldDestFolder = null;
-
- try {
- // -0- Compute destination directory and check install pre-conditions
-
- destFolder = pkg.getInstallFolder(osSdkRoot, sdkManager);
-
- if (destFolder == null) {
- // this should not seriously happen.
- monitor.log("Failed to compute installation directory for %1$s.", pkgName);
- return false;
- }
-
- if (!pkg.preInstallHook(newArchive, monitor, osSdkRoot, destFolder)) {
- monitor.log("Skipping archive: %1$s", pkgName);
- return false;
- }
-
- // -1- move old folder.
-
- if (mFileOp.exists(destFolder)) {
- // Create a new temp/old dir
- if (oldDestFolder == null) {
- oldDestFolder = getNewTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
- }
- if (oldDestFolder == null) {
- // this should not seriously happen.
- monitor.logError("Failed to find a temp directory in %1$s.", osSdkRoot);
- return false;
- }
-
- // Try to move the current dest dir to the temp/old one. Tell the user if it failed.
- while(true) {
- if (!moveFolder(destFolder, oldDestFolder)) {
- monitor.logError("Failed to rename directory %1$s to %2$s.",
- destFolder.getPath(), oldDestFolder.getPath());
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- boolean tryAgain = true;
-
- tryAgain = windowsDestDirLocked(osSdkRoot, destFolder, monitor);
-
- if (tryAgain) {
- // loop, trying to rename the temp dir into the destination
- continue;
- } else {
- return false;
- }
- }
- }
- break;
- }
- }
-
- assert !mFileOp.exists(destFolder);
-
- // -2- Unzip new content directly in place.
-
- if (!mFileOp.mkdirs(destFolder)) {
- monitor.logError("Failed to create directory %1$s", destFolder.getPath());
- return false;
- }
-
- if (!unzipFolder(archiveInfo,
- archiveFile,
- destFolder,
- monitor)) {
- return false;
- }
-
- if (!generateSourceProperties(newArchive, destFolder)) {
- monitor.logError("Failed to generate source.properties in directory %1$s",
- destFolder.getPath());
- return false;
- }
-
- // In case of success, if we were replacing an archive
- // and the older one had a different path, remove it now.
- Archive oldArchive = archiveInfo.getReplaced();
- if (oldArchive != null && oldArchive.isLocal()) {
- String oldPath = oldArchive.getLocalOsPath();
- File oldFolder = oldPath == null ? null : new File(oldPath);
- if (oldFolder == null && oldArchive.getParentPackage() != null) {
- oldFolder = oldArchive.getParentPackage().getInstallFolder(
- osSdkRoot, sdkManager);
- }
- if (oldFolder != null && mFileOp.exists(oldFolder) &&
- !oldFolder.equals(destFolder)) {
- monitor.logVerbose("Removing old archive at %1$s", oldFolder.getAbsolutePath());
- mFileOp.deleteFileOrFolder(oldFolder);
- }
- }
-
- success = true;
- pkg.postInstallHook(newArchive, monitor, destFolder);
- return true;
-
- } finally {
- if (!success) {
- // In case of failure, we try to restore the old folder content.
- if (oldDestFolder != null) {
- restoreFolder(oldDestFolder, destFolder);
- }
-
- // We also call the postInstallHool with a null directory to give a chance
- // to the archive to cleanup after preInstallHook.
- pkg.postInstallHook(newArchive, monitor, null /*installDir*/);
- }
-
- // Cleanup if the unzip folder is still set.
- mFileOp.deleteFileOrFolder(oldDestFolder);
- }
- }
-
- private boolean windowsDestDirLocked(
- String osSdkRoot,
- File destFolder,
- final ITaskMonitor monitor) {
- String msg = null;
-
- assert SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS;
-
- File findLockExe = FileOp.append(
- osSdkRoot, SdkConstants.FD_TOOLS, SdkConstants.FD_LIB, SdkConstants.FN_FIND_LOCK);
-
- if (mFileOp.exists(findLockExe)) {
- try {
- final StringBuilder result = new StringBuilder();
- String command[] = new String[] {
- findLockExe.getAbsolutePath(),
- destFolder.getAbsolutePath()
- };
- Process process = Runtime.getRuntime().exec(command);
- int retCode = GrabProcessOutput.grabProcessOutput(
- process,
- Wait.WAIT_FOR_READERS,
- new IProcessOutput() {
- @Override
- public void out(String line) {
- if (line != null) {
- result.append(line).append("\n");
- }
- }
-
- @Override
- public void err(String line) {
- if (line != null) {
- monitor.logError("[find_lock] Error: %1$s", line);
- }
- }
- });
-
- if (retCode == 0 && result.length() > 0) {
- // TODO create a better dialog
-
- String found = result.toString().trim();
- monitor.logError("[find_lock] Directory locked by %1$s", found);
-
- TreeSet<String> apps = new TreeSet<String>(Arrays.asList(
- found.split(Pattern.quote(";")))); //$NON-NLS-1$
- StringBuilder appStr = new StringBuilder();
- for (String app : apps) {
- appStr.append("\n - ").append(app.trim()); //$NON-NLS-1$
- }
-
- msg = String.format(
- "-= Warning ! =-\n" +
- "The following processes: %1$s\n" +
- "are locking the following directory: \n" +
- " %2$s\n" +
- "Please close these applications so that the installation can continue.\n" +
- "When ready, press YES to try again.",
- appStr.toString(),
- destFolder.getPath());
- }
-
- } catch (Exception e) {
- monitor.error(e, "[find_lock failed]");
- }
-
-
- }
-
- if (msg == null) {
- // Old way: simply display a generic text and let user figure it out.
- msg = String.format(
- "-= Warning ! =-\n" +
- "A folder failed to be moved. On Windows this " +
- "typically means that a program is using that folder (for " +
- "example Windows Explorer or your anti-virus software.)\n" +
- "Please momentarily deactivate your anti-virus software or " +
- "close any running programs that may be accessing the " +
- "directory '%1$s'.\n" +
- "When ready, press YES to try again.",
- destFolder.getPath());
- }
-
- boolean tryAgain = monitor.displayPrompt("SDK Manager: failed to install", msg);
- return tryAgain;
- }
-
- /**
- * Tries to rename/move a folder.
- * <p/>
- * Contract:
- * <ul>
- * <li> When we start, oldDir must exist and be a directory. newDir must not exist. </li>
- * <li> On successful completion, oldDir must not exists.
- * newDir must exist and have the same content. </li>
- * <li> On failure completion, oldDir must have the same content as before.
- * newDir must not exist. </li>
- * </ul>
- * <p/>
- * The simple "rename" operation on a folder can typically fail on Windows for a variety
- * of reason, in fact as soon as a single process holds a reference on a directory. The
- * most common case are the Explorer, the system's file indexer, Tortoise SVN cache or
- * an anti-virus that are busy indexing a new directory having been created.
- *
- * @param oldDir The old location to move. It must exist and be a directory.
- * @param newDir The new location where to move. It must not exist.
- * @return True if the move succeeded. On failure, we try hard to not have touched the old
- * directory in order not to loose its content.
- */
- private boolean moveFolder(File oldDir, File newDir) {
- // This is a simple folder rename that works on Linux/Mac all the time.
- //
- // On Windows this might fail if an indexer is busy looking at a new directory
- // (e.g. right after we unzip our archive), so it fails let's be nice and give
- // it a bit of time to succeed.
- for (int i = 0; i < 5; i++) {
- if (mFileOp.renameTo(oldDir, newDir)) {
- return true;
- }
- try {
- Thread.sleep(500 /*ms*/);
- } catch (InterruptedException e) {
- // ignore
- }
- }
-
- return false;
- }
-
- /**
- * Unzips a zip file into the given destination directory.
- *
- * The archive file MUST have a unique "root" folder.
- * This root folder is skipped when unarchiving.
- */
- @SuppressWarnings("unchecked")
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean unzipFolder(
- ArchiveReplacement archiveInfo,
- File archiveFile,
- File unzipDestFolder,
- ITaskMonitor monitor) {
-
- Archive newArchive = archiveInfo.getNewArchive();
- Package pkg = newArchive.getParentPackage();
- String pkgName = pkg.getShortDescription();
- long compressedSize = newArchive.getSize();
-
- ZipFile zipFile = null;
- try {
- zipFile = new ZipFile(archiveFile);
-
- // To advance the percent and the progress bar, we don't know the number of
- // items left to unzip. However we know the size of the archive and the size of
- // each uncompressed item. The zip file format overhead is negligible so that's
- // a good approximation.
- long incStep = compressedSize / NUM_MONITOR_INC;
- long incTotal = 0;
- long incCurr = 0;
- int lastPercent = 0;
-
- byte[] buf = new byte[65536];
-
- Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
- while (entries.hasMoreElements()) {
- ZipArchiveEntry entry = entries.nextElement();
-
- String name = entry.getName();
-
- // ZipFile entries should have forward slashes, but not all Zip
- // implementations can be expected to do that.
- name = name.replace('\\', '/');
-
- // Zip entries are always packages in a top-level directory
- // (e.g. docs/index.html). However we want to use our top-level
- // directory so we drop the first segment of the path name.
- int pos = name.indexOf('/');
- if (pos < 0 || pos == name.length() - 1) {
- continue;
- } else {
- name = name.substring(pos + 1);
- }
-
- File destFile = new File(unzipDestFolder, name);
-
- if (name.endsWith("/")) { //$NON-NLS-1$
- // Create directory if it doesn't exist yet. This allows us to create
- // empty directories.
- if (!mFileOp.isDirectory(destFile) && !mFileOp.mkdirs(destFile)) {
- monitor.logError("Failed to create directory %1$s",
- destFile.getPath());
- return false;
- }
- continue;
- } else if (name.indexOf('/') != -1) {
- // Otherwise it's a file in a sub-directory.
- // Make sure the parent directory has been created.
- File parentDir = destFile.getParentFile();
- if (!mFileOp.isDirectory(parentDir)) {
- if (!mFileOp.mkdirs(parentDir)) {
- monitor.logError("Failed to create directory %1$s",
- parentDir.getPath());
- return false;
- }
- }
- }
-
- FileOutputStream fos = null;
- long remains = entry.getSize();
- try {
- fos = new FileOutputStream(destFile);
-
- // Java bug 4040920: do not rely on the input stream EOF and don't
- // try to read more than the entry's size.
- InputStream entryContent = zipFile.getInputStream(entry);
- int n;
- while (remains > 0 &&
- (n = entryContent.read(
- buf, 0, (int) Math.min(remains, buf.length))) != -1) {
- remains -= n;
- if (n > 0) {
- fos.write(buf, 0, n);
- }
- }
- } catch (EOFException e) {
- monitor.logError("Error uncompressing file %s. Size: %d bytes, Unwritten: %d bytes.",
- entry.getName(), entry.getSize(), remains);
- throw e;
- } finally {
- if (fos != null) {
- fos.close();
- }
- }
-
- pkg.postUnzipFileHook(newArchive, monitor, mFileOp, destFile, entry);
-
- // Increment progress bar to match. We update only between files.
- for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
- monitor.incProgress(1);
- }
-
- int percent = (int) (100 * incTotal / compressedSize);
- if (percent != lastPercent) {
- monitor.setDescription("Unzipping %1$s (%2$d%%)", pkgName, percent);
- lastPercent = percent;
- }
-
- if (monitor.isCancelRequested()) {
- return false;
- }
- }
-
- return true;
-
- } catch (IOException e) {
- monitor.logError("Unzip failed: %1$s", e.getMessage());
-
- } finally {
- if (zipFile != null) {
- try {
- zipFile.close();
- } catch (IOException e) {
- // pass
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns an unused temp folder path in the form of osBasePath/temp/prefix.suffixNNN.
- * <p/>
- * This does not actually <em>create</em> the folder. It just scan the base path for
- * a free folder name to use and returns the file to use to reference it.
- * <p/>
- * This operation is not atomic so there's no guarantee the folder can't get
- * created in between. This is however unlikely and the caller can assume the
- * returned folder does not exist yet.
- * <p/>
- * Returns null if no such folder can be found (e.g. if all candidates exist,
- * which is rather unlikely) or if the base temp folder cannot be created.
- */
- private File getNewTempFolder(String osBasePath, String prefix, String suffix) {
- File baseTempFolder = getTempFolder(osBasePath);
-
- if (!mFileOp.isDirectory(baseTempFolder)) {
- if (mFileOp.isFile(baseTempFolder)) {
- mFileOp.deleteFileOrFolder(baseTempFolder);
- }
- if (!mFileOp.mkdirs(baseTempFolder)) {
- return null;
- }
- }
-
- for (int i = 1; i < 100; i++) {
- File folder = new File(baseTempFolder,
- String.format("%1$s.%2$s%3$02d", prefix, suffix, i)); //$NON-NLS-1$
- if (!mFileOp.exists(folder)) {
- return folder;
- }
- }
- return null;
- }
-
- /**
- * Returns the single fixed "temp" folder used by the SDK Manager.
- * This folder is always at osBasePath/temp.
- * <p/>
- * This does not actually <em>create</em> the folder.
- */
- private File getTempFolder(String osBasePath) {
- File baseTempFolder = new File(osBasePath, RepoConstants.FD_TEMP);
- return baseTempFolder;
- }
-
- /**
- * Generates a source.properties in the destination folder that contains all the infos
- * relevant to this archive, this package and the source so that we can reload them
- * locally later.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean generateSourceProperties(Archive archive, File unzipDestFolder) {
- Properties props = new Properties();
-
- archive.saveProperties(props);
-
- Package pkg = archive.getParentPackage();
- if (pkg != null) {
- pkg.saveProperties(props);
- }
-
- OutputStream fos = null;
- try {
- File f = new File(unzipDestFolder, SdkConstants.FN_SOURCE_PROP);
-
- fos = mFileOp.newFileOutputStream(f);
-
- props.store(fos, "## Android Tool: Source of this archive."); //$NON-NLS-1$
-
- return true;
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
-
- return false;
- }
-
- /**
- * Recursively restore srcFolder into destFolder by performing a copy of the file
- * content rather than rename/moves.
- *
- * @param srcFolder The source folder to restore.
- * @param destFolder The destination folder where to restore.
- * @return True if the folder was successfully restored, false if it was not at all or
- * only partially restored.
- */
- private boolean restoreFolder(File srcFolder, File destFolder) {
- boolean result = true;
-
- // Process sub-folders first
- File[] srcFiles = mFileOp.listFiles(srcFolder);
- if (srcFiles == null) {
- // Source does not exist. That is quite odd.
- return false;
- }
-
- if (mFileOp.isFile(destFolder)) {
- if (!mFileOp.delete(destFolder)) {
- // There's already a file in there where we want a directory and
- // we can't delete it. This is rather unexpected. Just give up on
- // that folder.
- return false;
- }
- } else if (!mFileOp.isDirectory(destFolder)) {
- mFileOp.mkdirs(destFolder);
- }
-
- // Get all the files and dirs of the current destination.
- // We are not going to clean up the destination first.
- // Instead we'll copy over and just remove any remaining files or directories.
- Set<File> destDirs = new HashSet<File>();
- Set<File> destFiles = new HashSet<File>();
- File[] files = mFileOp.listFiles(destFolder);
- if (files != null) {
- for (File f : files) {
- if (mFileOp.isDirectory(f)) {
- destDirs.add(f);
- } else {
- destFiles.add(f);
- }
- }
- }
-
- // First restore all source directories.
- for (File dir : srcFiles) {
- if (mFileOp.isDirectory(dir)) {
- File d = new File(destFolder, dir.getName());
- destDirs.remove(d);
- if (!restoreFolder(dir, d)) {
- result = false;
- }
- }
- }
-
- // Remove any remaining directories not processed above.
- for (File dir : destDirs) {
- mFileOp.deleteFileOrFolder(dir);
- }
-
- // Copy any source files over to the destination.
- for (File file : srcFiles) {
- if (mFileOp.isFile(file)) {
- File f = new File(destFolder, file.getName());
- destFiles.remove(f);
- try {
- mFileOp.copyFile(file, f);
- } catch (IOException e) {
- result = false;
- }
- }
- }
-
- // Remove any remaining files not processed above.
- for (File file : destFiles) {
- mFileOp.deleteFileOrFolder(file);
- }
-
- return result;
- }
-}
+/*
+ * Copyright (C) 2010 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.archives;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.CanceledByUserException;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.RepoConstants;
+import com.android.sdklib.util.GrabProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.utils.Pair;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.message.BasicHeader;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * Performs the work of installing a given {@link Archive}.
+ */
+public class ArchiveInstaller {
+
+ private static final String PROP_STATUS_CODE = "StatusCode"; //$NON-NLS-1$
+ public static final String ENV_VAR_IGNORE_COMPAT = "ANDROID_SDK_IGNORE_COMPAT"; //$NON-NLS-1$
+
+ public static final int NUM_MONITOR_INC = 100;
+
+ /** The current {@link FileOp} to use. Never null. */
+ private final IFileOp mFileOp;
+
+ /**
+ * Generates an {@link ArchiveInstaller} that relies on the default {@link FileOp}.
+ */
+ public ArchiveInstaller() {
+ mFileOp = new FileOp();
+ }
+
+ /**
+ * Generates an {@link ArchiveInstaller} that relies on the given {@link FileOp}.
+ *
+ * @param fileUtils An alternate version of {@link FileOp} to use for file operations.
+ */
+ protected ArchiveInstaller(IFileOp fileUtils) {
+ mFileOp = fileUtils;
+ }
+
+ /** Returns current {@link FileOp} to use. Never null. */
+ protected IFileOp getFileOp() {
+ return mFileOp;
+ }
+
+ /**
+ * Install this {@link ArchiveReplacement}s.
+ * A "replacement" is composed of the actual new archive to install
+ * (c.f. {@link ArchiveReplacement#getNewArchive()} and an <em>optional</em>
+ * archive being replaced (c.f. {@link ArchiveReplacement#getReplaced()}.
+ * In the case of a new install, the later should be null.
+ * <p/>
+ * The new archive to install will be skipped if it is incompatible.
+ *
+ * @return True if the archive was installed, false otherwise.
+ */
+ public boolean install(ArchiveReplacement archiveInfo,
+ String osSdkRoot,
+ boolean forceHttp,
+ SdkManager sdkManager,
+ DownloadCache cache,
+ ITaskMonitor monitor) {
+
+ Archive newArchive = archiveInfo.getNewArchive();
+ Package pkg = newArchive.getParentPackage();
+
+ String name = pkg.getShortDescription();
+
+ if (newArchive.isLocal()) {
+ // This should never happen.
+ monitor.log("Skipping already installed archive: %1$s for %2$s",
+ name,
+ newArchive.getOsDescription());
+ return false;
+ }
+
+ // In detail mode, give us a way to force install of incompatible archives.
+ boolean checkIsCompatible = System.getenv(ENV_VAR_IGNORE_COMPAT) == null;
+
+ if (checkIsCompatible && !newArchive.isCompatible()) {
+ monitor.log("Skipping incompatible archive: %1$s for %2$s",
+ name,
+ newArchive.getOsDescription());
+ return false;
+ }
+
+ Pair<File, File> files = downloadFile(newArchive, osSdkRoot, cache, monitor, forceHttp);
+ File tmpFile = files == null ? null : files.getFirst();
+ File propsFile = files == null ? null : files.getSecond();
+ if (tmpFile != null) {
+ // Unarchive calls the pre/postInstallHook methods.
+ if (unarchive(archiveInfo, osSdkRoot, tmpFile, sdkManager, monitor)) {
+ monitor.log("Installed %1$s", name);
+ // Delete the temp archive if it exists, only on success
+ mFileOp.deleteFileOrFolder(tmpFile);
+ mFileOp.deleteFileOrFolder(propsFile);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Downloads an archive and returns the temp file with it.
+ * Caller is responsible with deleting the temp file when done.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Pair<File, File> downloadFile(Archive archive,
+ String osSdkRoot,
+ DownloadCache cache,
+ ITaskMonitor monitor,
+ boolean forceHttp) {
+
+ String pkgName = archive.getParentPackage().getShortDescription();
+ monitor.setDescription("Downloading %1$s", pkgName);
+ monitor.log("Downloading %1$s", pkgName);
+
+ 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();
+ SdkSource src = pkg.getParentSource();
+ if (src == null) {
+ monitor.logError("Internal error: no source for archive %1$s", pkgName);
+ return null;
+ }
+
+ // take the URL to the repository.xml and remove the last component
+ // to get the base
+ String repoXml = src.getUrl();
+ int pos = repoXml.lastIndexOf('/');
+ String base = repoXml.substring(0, pos + 1);
+
+ link = base + link;
+ }
+
+ if (forceHttp) {
+ link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Get the basename of the file we're downloading, i.e. the last component
+ // of the URL
+ int pos = link.lastIndexOf('/');
+ String base = link.substring(pos + 1);
+
+ // Rather than create a real temp file in the system, we simply use our
+ // temp folder (in the SDK base folder) and use the archive name for the
+ // download. This allows us to reuse or continue downloads.
+
+ File tmpFolder = getTempFolder(osSdkRoot);
+ if (!mFileOp.isDirectory(tmpFolder)) {
+ if (mFileOp.isFile(tmpFolder)) {
+ mFileOp.deleteFileOrFolder(tmpFolder);
+ }
+ if (!mFileOp.mkdirs(tmpFolder)) {
+ monitor.logError("Failed to create directory %1$s", tmpFolder.getPath());
+ return null;
+ }
+ }
+ File tmpFile = new File(tmpFolder, base);
+
+ // property file were we'll keep partial/resume information for reuse.
+ File propsFile = new File(tmpFolder, base + ".inf"); //$NON-NLS-1$
+
+ // if the file exists, check its checksum & size. Use it if complete
+ if (mFileOp.exists(tmpFile)) {
+ if (mFileOp.length(tmpFile) == archive.getSize()) {
+ String chksum = ""; //$NON-NLS-1$
+ try {
+ chksum = fileChecksum(archive.getChecksumType().getMessageDigest(),
+ tmpFile,
+ monitor);
+ } catch (NoSuchAlgorithmException e) {
+ // Ignore.
+ }
+ if (chksum.equalsIgnoreCase(archive.getChecksum())) {
+ // File is good, let's use it.
+ return Pair.of(tmpFile, propsFile);
+ } else {
+ // The file has the right size but the wrong content.
+ // Just remove it and this will trigger a full download below.
+ mFileOp.deleteFileOrFolder(tmpFile);
+ }
+ }
+ }
+
+ Header[] resumeHeaders = preparePartialDownload(archive, tmpFile, propsFile);
+
+ if (fetchUrl(archive, resumeHeaders, tmpFile, propsFile, link, pkgName, cache, monitor)) {
+ // Fetching was successful, let's use this file.
+ return Pair.of(tmpFile, propsFile);
+ }
+ return null;
+ }
+
+ /**
+ * Prepares to do a partial/resume download.
+ *
+ * @param archive The archive we're trying to download.
+ * @param tmpFile The destination file to download (e.g. something.zip)
+ * @param propsFile A properties file generated by the last partial download (e.g. .zip.inf)
+ * @return Null in case we should perform a full download, or a set of headers
+ * to resume a partial download.
+ */
+ private Header[] preparePartialDownload(Archive archive, File tmpFile, File propsFile) {
+ // We need both the destination file and its properties to do a resume.
+ if (mFileOp.isFile(tmpFile) && mFileOp.isFile(propsFile)) {
+ // The caller already checked the case were the destination file has the
+ // right size _and_ checksum, so we know at this point one of them is wrong
+ // here.
+ // We can obviously only resume a file if its size is smaller than expected.
+ if (mFileOp.length(tmpFile) < archive.getSize()) {
+ Properties props = mFileOp.loadProperties(propsFile);
+
+ List<Header> headers = new ArrayList<Header>(2);
+ headers.add(new BasicHeader(HttpHeaders.RANGE,
+ String.format("bytes=%d-", mFileOp.length(tmpFile))));
+
+ // Don't use the properties if there's not at least a 200 or 206 code from
+ // the last download.
+ int status = 0;
+ try {
+ status = Integer.parseInt(props.getProperty(PROP_STATUS_CODE));
+ } catch (Exception ignore) {}
+
+ if (status == HttpStatus.SC_OK || status == HttpStatus.SC_PARTIAL_CONTENT) {
+ // Do we have an ETag and/or a Last-Modified?
+ String etag = props.getProperty(HttpHeaders.ETAG);
+ String lastMod = props.getProperty(HttpHeaders.LAST_MODIFIED);
+
+ if (etag != null && etag.length() > 0) {
+ headers.add(new BasicHeader(HttpHeaders.IF_MATCH, etag));
+ } else if (lastMod != null && lastMod.length() > 0) {
+ headers.add(new BasicHeader(HttpHeaders.IF_MATCH, lastMod));
+ }
+
+ return headers.toArray(new Header[headers.size()]);
+ }
+ }
+ }
+
+ // Existing file is either of different size or content.
+ // Remove the existing file and request a full download.
+ mFileOp.deleteFileOrFolder(tmpFile);
+ mFileOp.deleteFileOrFolder(propsFile);
+
+ return null;
+ }
+
+ /**
+ * Computes the SHA-1 checksum of the content of the given file.
+ * Returns an empty string on error (rather than null).
+ */
+ private String fileChecksum(MessageDigest digester, File tmpFile, ITaskMonitor monitor) {
+ InputStream is = null;
+ try {
+ is = new FileInputStream(tmpFile);
+
+ byte[] buf = new byte[65536];
+ int n;
+
+ while ((n = is.read(buf)) >= 0) {
+ if (n > 0) {
+ digester.update(buf, 0, n);
+ }
+ }
+
+ return getDigestChecksum(digester);
+
+ } catch (FileNotFoundException e) {
+ // The FNF message is just the URL. Make it a bit more useful.
+ monitor.logError("File not found: %1$s", e.getMessage());
+
+ } catch (Exception e) {
+ monitor.logError("%1$s", e.getMessage()); //$NON-NLS-1$
+
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the SHA-1 from a {@link MessageDigest} as an hex string
+ * that can be compared with {@link Archive#getChecksum()}.
+ */
+ private String getDigestChecksum(MessageDigest digester) {
+ int n;
+ // 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++) {
+ int b = digest[i] & 0x0FF;
+ hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
+ hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
+ }
+
+ return new String(hexDigest);
+ }
+
+ /**
+ * Actually performs the download.
+ * Also computes the SHA1 of the file on the fly.
+ * <p/>
+ * 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.
+ * <p/>
+ * Increments the monitor by {@link #NUM_MONITOR_INC}.
+ *
+ * @param archive The archive we're trying to download.
+ * @param resumeHeaders The headers to use for a partial resume, or null when fetching
+ * a whole new file.
+ * @param tmpFile The destination file to download (e.g. something.zip)
+ * @param propsFile A properties file generated by the last partial download (e.g. .zip.inf)
+ * @param urlString The URL as a string
+ * @param pkgName The archive's package name, used for progress output.
+ * @param cache The {@link DownloadCache} instance to use.
+ * @param monitor The monitor to output the progress and errors.
+ * @return True if we fetched the file successfully.
+ * False if the download failed or was aborted.
+ */
+ private boolean fetchUrl(Archive archive,
+ Header[] resumeHeaders,
+ File tmpFile,
+ File propsFile,
+ String urlString,
+ String pkgName,
+ DownloadCache cache,
+ ITaskMonitor monitor) {
+
+ FileOutputStream os = null;
+ InputStream is = null;
+ int inc_remain = NUM_MONITOR_INC;
+ try {
+ Pair<InputStream, HttpResponse> result =
+ cache.openDirectUrl(urlString, resumeHeaders, monitor);
+
+ is = result.getFirst();
+ HttpResponse resp = result.getSecond();
+ int status = resp.getStatusLine().getStatusCode();
+ if (status == HttpStatus.SC_NOT_FOUND) {
+ throw new Exception("URL not found.");
+ }
+ if (is == null) {
+ throw new Exception("No content.");
+ }
+
+
+ Properties props = new Properties();
+ props.setProperty(PROP_STATUS_CODE, Integer.toString(status));
+ if (resp.containsHeader(HttpHeaders.ETAG)) {
+ props.setProperty(HttpHeaders.ETAG,
+ resp.getFirstHeader(HttpHeaders.ETAG).getValue());
+ }
+ if (resp.containsHeader(HttpHeaders.LAST_MODIFIED)) {
+ props.setProperty(HttpHeaders.LAST_MODIFIED,
+ resp.getFirstHeader(HttpHeaders.LAST_MODIFIED).getValue());
+ }
+
+ mFileOp.saveProperties(propsFile, props, "## Android SDK Download."); //$NON-NLS-1$
+
+ // On success, status can be:
+ // - 206 (Partial content), if resumeHeaders is not null (we asked for a partial
+ // download, and we get partial content for that download => we'll need to append
+ // to the existing file.)
+ // - 200 (OK) meaning we're getting whole new content from scratch. This can happen
+ // even if resumeHeaders is not null (typically means the server has a new version
+ // of the file to serve.) In this case we reset the file and write from scratch.
+
+ boolean append = status == HttpStatus.SC_PARTIAL_CONTENT;
+ if (status != HttpStatus.SC_OK && !(append && resumeHeaders != null)) {
+ throw new Exception(String.format("Unexpected HTTP Status %1$d", status));
+ }
+ MessageDigest digester = archive.getChecksumType().getMessageDigest();
+
+ if (append) {
+ // Seed the digest with the existing content.
+ InputStream temp = null;
+ try {
+ temp = new FileInputStream(tmpFile);
+
+ byte[] buf = new byte[65536];
+ int n;
+
+ while ((n = temp.read(buf)) >= 0) {
+ if (n > 0) {
+ digester.update(buf, 0, n);
+ }
+ }
+ } catch (Exception ignore) {
+ } finally {
+ if (temp != null) {
+ try {
+ temp.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+ // Open the output stream in append for a resume, or reset for a full download.
+ os = new FileOutputStream(tmpFile, append);
+
+ byte[] buf = new byte[65536];
+ int n;
+
+ long total = 0;
+ long size = archive.getSize();
+ if (append) {
+ long len = mFileOp.length(tmpFile);
+ int percent = (int) (len * 100 / size);
+ size -= len;
+ monitor.logVerbose(
+ "Resuming %1$s download at %2$d (%3$d%%)", pkgName, len, percent);
+ }
+ long inc = size / NUM_MONITOR_INC;
+ long next_inc = inc;
+
+ long startMs = System.currentTimeMillis();
+ long nextMs = startMs + 2000; // start update after 2 seconds
+
+ while ((n = is.read(buf)) >= 0) {
+ if (n > 0) {
+ os.write(buf, 0, n);
+ digester.update(buf, 0, n);
+ }
+
+ long timeMs = System.currentTimeMillis();
+
+ total += n;
+ if (total >= next_inc) {
+ monitor.incProgress(1);
+ inc_remain--;
+ next_inc += inc;
+ }
+
+ if (timeMs > nextMs) {
+ long delta = timeMs - startMs;
+ if (total > 0 && delta > 0) {
+ // percent left to download
+ int percent = (int) (100 * total / size);
+ // speed in KiB/s
+ float speed = (float)total / (float)delta * (1000.f / 1024.f);
+ // time left to download the rest at the current KiB/s rate
+ int timeLeft = (speed > 1e-3) ?
+ (int)(((size - total) / 1024.0f) / speed) :
+ 0;
+ String timeUnit = "seconds";
+ if (timeLeft > 120) {
+ timeUnit = "minutes";
+ timeLeft /= 60;
+ }
+
+ monitor.setDescription(
+ "Downloading %1$s (%2$d%%, %3$.0f KiB/s, %4$d %5$s left)",
+ pkgName,
+ percent,
+ speed,
+ timeLeft,
+ timeUnit);
+ }
+ nextMs = timeMs + 1000; // update every second
+ }
+
+ if (monitor.isCancelRequested()) {
+ monitor.log("Download aborted by user at %1$d bytes.", total);
+ return false;
+ }
+
+ }
+
+ if (total != size) {
+ monitor.logError(
+ "Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
+ size, total);
+ return false;
+ }
+
+ // Create an hex string from the digest
+ String actual = getDigestChecksum(digester);
+ String expected = archive.getChecksum();
+ if (!actual.equalsIgnoreCase(expected)) {
+ monitor.logError("Download finished with wrong checksum. Expected %1$s, got %2$s.",
+ expected, actual);
+ return false;
+ }
+
+ return true;
+
+ } catch (CanceledByUserException e) {
+ // HTTP Basic Auth or NTLM login was canceled by user.
+ // Don't output an error in the log.
+
+ } catch (FileNotFoundException e) {
+ // The FNF message is just the URL. Make it a bit more useful.
+ monitor.logError("URL not found: %1$s", e.getMessage());
+
+ } catch (Exception e) {
+ monitor.logError("Download interrupted: %1$s", e.getMessage()); //$NON-NLS-1$
+
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ if (inc_remain > 0) {
+ monitor.incProgress(inc_remain);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Install the given archive in the given folder.
+ */
+ private boolean unarchive(ArchiveReplacement archiveInfo,
+ String osSdkRoot,
+ File archiveFile,
+ SdkManager sdkManager,
+ ITaskMonitor monitor) {
+ boolean success = false;
+ Archive newArchive = archiveInfo.getNewArchive();
+ Package pkg = newArchive.getParentPackage();
+ String pkgName = pkg.getShortDescription();
+ monitor.setDescription("Installing %1$s", pkgName);
+ monitor.log("Installing %1$s", pkgName);
+
+ // Ideally we want to always unzip in a temp folder which name depends on the package
+ // type (e.g. addon, tools, etc.) and then move the folder to the destination folder.
+ // If the destination folder exists, it will be renamed and deleted at the very
+ // end if everything succeeded. This provides a nice atomic swap and should leave the
+ // original folder untouched in case something wrong (e.g. program crash) in the
+ // middle of the unzip operation.
+ //
+ // However that doesn't work on Windows, we always end up not being able to move the
+ // new folder. There are actually 2 cases:
+ // A- A process such as a the explorer is locking the *old* folder or a file inside
+ // (e.g. adb.exe)
+ // In this case we really shouldn't be tried to work around it and we need to let
+ // the user know and let it close apps that access that folder.
+ // B- A process is locking the *new* folder. Very often this turns to be a file indexer
+ // or an anti-virus that is busy scanning the new folder that we just unzipped.
+ //
+ // So we're going to change the strategy:
+ // 1- Try to move the old folder to a temp/old folder. This might fail in case of issue A.
+ // Note: for platform-tools, we can try killing adb first.
+ // If it still fails, we do nothing and ask the user to terminate apps that can be
+ // locking that folder.
+ // 2- Once the old folder is out of the way, we unzip the archive directly into the
+ // optimal new location. We no longer unzip it in a temp folder and move it since we
+ // know that's what fails in most of the cases.
+ // 3- If the unzip fails, remove everything and try to restore the old folder by doing
+ // a *copy* in place and not a folder move (which will likely fail too).
+
+ String pkgKind = pkg.getClass().getSimpleName();
+
+ File destFolder = null;
+ File oldDestFolder = null;
+
+ try {
+ // -0- Compute destination directory and check install pre-conditions
+
+ destFolder = pkg.getInstallFolder(osSdkRoot, sdkManager);
+
+ if (destFolder == null) {
+ // this should not seriously happen.
+ monitor.log("Failed to compute installation directory for %1$s.", pkgName);
+ return false;
+ }
+
+ if (!pkg.preInstallHook(newArchive, monitor, osSdkRoot, destFolder)) {
+ monitor.log("Skipping archive: %1$s", pkgName);
+ return false;
+ }
+
+ // -1- move old folder.
+
+ if (mFileOp.exists(destFolder)) {
+ // Create a new temp/old dir
+ if (oldDestFolder == null) {
+ oldDestFolder = getNewTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
+ }
+ if (oldDestFolder == null) {
+ // this should not seriously happen.
+ monitor.logError("Failed to find a temp directory in %1$s.", osSdkRoot);
+ return false;
+ }
+
+ // Try to move the current dest dir to the temp/old one. Tell the user if it failed.
+ while(true) {
+ if (!moveFolder(destFolder, oldDestFolder)) {
+ monitor.logError("Failed to rename directory %1$s to %2$s.",
+ destFolder.getPath(), oldDestFolder.getPath());
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ boolean tryAgain = true;
+
+ tryAgain = windowsDestDirLocked(osSdkRoot, destFolder, monitor);
+
+ if (tryAgain) {
+ // loop, trying to rename the temp dir into the destination
+ continue;
+ } else {
+ return false;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ assert !mFileOp.exists(destFolder);
+
+ // -2- Unzip new content directly in place.
+
+ if (!mFileOp.mkdirs(destFolder)) {
+ monitor.logError("Failed to create directory %1$s", destFolder.getPath());
+ return false;
+ }
+
+ if (!unzipFolder(archiveInfo,
+ archiveFile,
+ destFolder,
+ monitor)) {
+ return false;
+ }
+
+ if (!generateSourceProperties(newArchive, destFolder)) {
+ monitor.logError("Failed to generate source.properties in directory %1$s",
+ destFolder.getPath());
+ return false;
+ }
+
+ // In case of success, if we were replacing an archive
+ // and the older one had a different path, remove it now.
+ Archive oldArchive = archiveInfo.getReplaced();
+ if (oldArchive != null && oldArchive.isLocal()) {
+ String oldPath = oldArchive.getLocalOsPath();
+ File oldFolder = oldPath == null ? null : new File(oldPath);
+ if (oldFolder == null && oldArchive.getParentPackage() != null) {
+ oldFolder = oldArchive.getParentPackage().getInstallFolder(
+ osSdkRoot, sdkManager);
+ }
+ if (oldFolder != null && mFileOp.exists(oldFolder) &&
+ !oldFolder.equals(destFolder)) {
+ monitor.logVerbose("Removing old archive at %1$s", oldFolder.getAbsolutePath());
+ mFileOp.deleteFileOrFolder(oldFolder);
+ }
+ }
+
+ success = true;
+ pkg.postInstallHook(newArchive, monitor, destFolder);
+ return true;
+
+ } finally {
+ if (!success) {
+ // In case of failure, we try to restore the old folder content.
+ if (oldDestFolder != null) {
+ restoreFolder(oldDestFolder, destFolder);
+ }
+
+ // We also call the postInstallHool with a null directory to give a chance
+ // to the archive to cleanup after preInstallHook.
+ pkg.postInstallHook(newArchive, monitor, null /*installDir*/);
+ }
+
+ // Cleanup if the unzip folder is still set.
+ mFileOp.deleteFileOrFolder(oldDestFolder);
+ }
+ }
+
+ private boolean windowsDestDirLocked(
+ String osSdkRoot,
+ File destFolder,
+ final ITaskMonitor monitor) {
+ String msg = null;
+
+ assert SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS;
+
+ File findLockExe = FileOp.append(
+ osSdkRoot, SdkConstants.FD_TOOLS, SdkConstants.FD_LIB, SdkConstants.FN_FIND_LOCK);
+
+ if (mFileOp.exists(findLockExe)) {
+ try {
+ final StringBuilder result = new StringBuilder();
+ String command[] = new String[] {
+ findLockExe.getAbsolutePath(),
+ destFolder.getAbsolutePath()
+ };
+ Process process = Runtime.getRuntime().exec(command);
+ int retCode = GrabProcessOutput.grabProcessOutput(
+ process,
+ Wait.WAIT_FOR_READERS,
+ new IProcessOutput() {
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ result.append(line).append("\n");
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ monitor.logError("[find_lock] Error: %1$s", line);
+ }
+ }
+ });
+
+ if (retCode == 0 && result.length() > 0) {
+ // TODO create a better dialog
+
+ String found = result.toString().trim();
+ monitor.logError("[find_lock] Directory locked by %1$s", found);
+
+ TreeSet<String> apps = new TreeSet<String>(Arrays.asList(
+ found.split(Pattern.quote(";")))); //$NON-NLS-1$
+ StringBuilder appStr = new StringBuilder();
+ for (String app : apps) {
+ appStr.append("\n - ").append(app.trim()); //$NON-NLS-1$
+ }
+
+ msg = String.format(
+ "-= Warning ! =-\n" +
+ "The following processes: %1$s\n" +
+ "are locking the following directory: \n" +
+ " %2$s\n" +
+ "Please close these applications so that the installation can continue.\n" +
+ "When ready, press YES to try again.",
+ appStr.toString(),
+ destFolder.getPath());
+ }
+
+ } catch (Exception e) {
+ monitor.error(e, "[find_lock failed]");
+ }
+
+
+ }
+
+ if (msg == null) {
+ // Old way: simply display a generic text and let user figure it out.
+ msg = String.format(
+ "-= Warning ! =-\n" +
+ "A folder failed to be moved. On Windows this " +
+ "typically means that a program is using that folder (for " +
+ "example Windows Explorer or your anti-virus software.)\n" +
+ "Please momentarily deactivate your anti-virus software or " +
+ "close any running programs that may be accessing the " +
+ "directory '%1$s'.\n" +
+ "When ready, press YES to try again.",
+ destFolder.getPath());
+ }
+
+ boolean tryAgain = monitor.displayPrompt("SDK Manager: failed to install", msg);
+ return tryAgain;
+ }
+
+ /**
+ * Tries to rename/move a folder.
+ * <p/>
+ * Contract:
+ * <ul>
+ * <li> When we start, oldDir must exist and be a directory. newDir must not exist. </li>
+ * <li> On successful completion, oldDir must not exists.
+ * newDir must exist and have the same content. </li>
+ * <li> On failure completion, oldDir must have the same content as before.
+ * newDir must not exist. </li>
+ * </ul>
+ * <p/>
+ * The simple "rename" operation on a folder can typically fail on Windows for a variety
+ * of reason, in fact as soon as a single process holds a reference on a directory. The
+ * most common case are the Explorer, the system's file indexer, Tortoise SVN cache or
+ * an anti-virus that are busy indexing a new directory having been created.
+ *
+ * @param oldDir The old location to move. It must exist and be a directory.
+ * @param newDir The new location where to move. It must not exist.
+ * @return True if the move succeeded. On failure, we try hard to not have touched the old
+ * directory in order not to loose its content.
+ */
+ private boolean moveFolder(File oldDir, File newDir) {
+ // This is a simple folder rename that works on Linux/Mac all the time.
+ //
+ // On Windows this might fail if an indexer is busy looking at a new directory
+ // (e.g. right after we unzip our archive), so it fails let's be nice and give
+ // it a bit of time to succeed.
+ for (int i = 0; i < 5; i++) {
+ if (mFileOp.renameTo(oldDir, newDir)) {
+ return true;
+ }
+ try {
+ Thread.sleep(500 /*ms*/);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Unzips a zip file into the given destination directory.
+ *
+ * The archive file MUST have a unique "root" folder.
+ * This root folder is skipped when unarchiving.
+ */
+ @SuppressWarnings("unchecked")
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean unzipFolder(
+ ArchiveReplacement archiveInfo,
+ File archiveFile,
+ File unzipDestFolder,
+ ITaskMonitor monitor) {
+
+ Archive newArchive = archiveInfo.getNewArchive();
+ Package pkg = newArchive.getParentPackage();
+ String pkgName = pkg.getShortDescription();
+ long compressedSize = newArchive.getSize();
+
+ ZipFile zipFile = null;
+ try {
+ zipFile = new ZipFile(archiveFile);
+
+ // To advance the percent and the progress bar, we don't know the number of
+ // items left to unzip. However we know the size of the archive and the size of
+ // each uncompressed item. The zip file format overhead is negligible so that's
+ // a good approximation.
+ long incStep = compressedSize / NUM_MONITOR_INC;
+ long incTotal = 0;
+ long incCurr = 0;
+ int lastPercent = 0;
+
+ byte[] buf = new byte[65536];
+
+ Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
+ while (entries.hasMoreElements()) {
+ ZipArchiveEntry entry = entries.nextElement();
+
+ String name = entry.getName();
+
+ // ZipFile entries should have forward slashes, but not all Zip
+ // implementations can be expected to do that.
+ name = name.replace('\\', '/');
+
+ // Zip entries are always packages in a top-level directory
+ // (e.g. docs/index.html). However we want to use our top-level
+ // directory so we drop the first segment of the path name.
+ int pos = name.indexOf('/');
+ if (pos < 0 || pos == name.length() - 1) {
+ continue;
+ } else {
+ name = name.substring(pos + 1);
+ }
+
+ File destFile = new File(unzipDestFolder, name);
+
+ if (name.endsWith("/")) { //$NON-NLS-1$
+ // Create directory if it doesn't exist yet. This allows us to create
+ // empty directories.
+ if (!mFileOp.isDirectory(destFile) && !mFileOp.mkdirs(destFile)) {
+ monitor.logError("Failed to create directory %1$s",
+ destFile.getPath());
+ return false;
+ }
+ continue;
+ } else if (name.indexOf('/') != -1) {
+ // Otherwise it's a file in a sub-directory.
+ // Make sure the parent directory has been created.
+ File parentDir = destFile.getParentFile();
+ if (!mFileOp.isDirectory(parentDir)) {
+ if (!mFileOp.mkdirs(parentDir)) {
+ monitor.logError("Failed to create directory %1$s",
+ parentDir.getPath());
+ return false;
+ }
+ }
+ }
+
+ FileOutputStream fos = null;
+ long remains = entry.getSize();
+ try {
+ fos = new FileOutputStream(destFile);
+
+ // Java bug 4040920: do not rely on the input stream EOF and don't
+ // try to read more than the entry's size.
+ InputStream entryContent = zipFile.getInputStream(entry);
+ int n;
+ while (remains > 0 &&
+ (n = entryContent.read(
+ buf, 0, (int) Math.min(remains, buf.length))) != -1) {
+ remains -= n;
+ if (n > 0) {
+ fos.write(buf, 0, n);
+ }
+ }
+ } catch (EOFException e) {
+ monitor.logError("Error uncompressing file %s. Size: %d bytes, Unwritten: %d bytes.",
+ entry.getName(), entry.getSize(), remains);
+ throw e;
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+
+ pkg.postUnzipFileHook(newArchive, monitor, mFileOp, destFile, entry);
+
+ // Increment progress bar to match. We update only between files.
+ for(incTotal += entry.getCompressedSize(); incCurr < incTotal; incCurr += incStep) {
+ monitor.incProgress(1);
+ }
+
+ int percent = (int) (100 * incTotal / compressedSize);
+ if (percent != lastPercent) {
+ monitor.setDescription("Unzipping %1$s (%2$d%%)", pkgName, percent);
+ lastPercent = percent;
+ }
+
+ if (monitor.isCancelRequested()) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } catch (IOException e) {
+ monitor.logError("Unzip failed: %1$s", e.getMessage());
+
+ } finally {
+ if (zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns an unused temp folder path in the form of osBasePath/temp/prefix.suffixNNN.
+ * <p/>
+ * This does not actually <em>create</em> the folder. It just scan the base path for
+ * a free folder name to use and returns the file to use to reference it.
+ * <p/>
+ * This operation is not atomic so there's no guarantee the folder can't get
+ * created in between. This is however unlikely and the caller can assume the
+ * returned folder does not exist yet.
+ * <p/>
+ * Returns null if no such folder can be found (e.g. if all candidates exist,
+ * which is rather unlikely) or if the base temp folder cannot be created.
+ */
+ private File getNewTempFolder(String osBasePath, String prefix, String suffix) {
+ File baseTempFolder = getTempFolder(osBasePath);
+
+ if (!mFileOp.isDirectory(baseTempFolder)) {
+ if (mFileOp.isFile(baseTempFolder)) {
+ mFileOp.deleteFileOrFolder(baseTempFolder);
+ }
+ if (!mFileOp.mkdirs(baseTempFolder)) {
+ return null;
+ }
+ }
+
+ for (int i = 1; i < 100; i++) {
+ File folder = new File(baseTempFolder,
+ String.format("%1$s.%2$s%3$02d", prefix, suffix, i)); //$NON-NLS-1$
+ if (!mFileOp.exists(folder)) {
+ return folder;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the single fixed "temp" folder used by the SDK Manager.
+ * This folder is always at osBasePath/temp.
+ * <p/>
+ * This does not actually <em>create</em> the folder.
+ */
+ private File getTempFolder(String osBasePath) {
+ File baseTempFolder = new File(osBasePath, RepoConstants.FD_TEMP);
+ return baseTempFolder;
+ }
+
+ /**
+ * Generates a source.properties in the destination folder that contains all the infos
+ * relevant to this archive, this package and the source so that we can reload them
+ * locally later.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean generateSourceProperties(Archive archive, File unzipDestFolder) {
+ Properties props = new Properties();
+
+ archive.saveProperties(props);
+
+ Package pkg = archive.getParentPackage();
+ if (pkg != null) {
+ pkg.saveProperties(props);
+ }
+
+ return mFileOp.saveProperties(
+ new File(unzipDestFolder, SdkConstants.FN_SOURCE_PROP),
+ props,
+ "## Android Tool: Source of this archive."); //$NON-NLS-1$
+ }
+
+ /**
+ * Recursively restore srcFolder into destFolder by performing a copy of the file
+ * content rather than rename/moves.
+ *
+ * @param srcFolder The source folder to restore.
+ * @param destFolder The destination folder where to restore.
+ * @return True if the folder was successfully restored, false if it was not at all or
+ * only partially restored.
+ */
+ private boolean restoreFolder(File srcFolder, File destFolder) {
+ boolean result = true;
+
+ // Process sub-folders first
+ File[] srcFiles = mFileOp.listFiles(srcFolder);
+ if (srcFiles == null) {
+ // Source does not exist. That is quite odd.
+ return false;
+ }
+
+ if (mFileOp.isFile(destFolder)) {
+ if (!mFileOp.delete(destFolder)) {
+ // There's already a file in there where we want a directory and
+ // we can't delete it. This is rather unexpected. Just give up on
+ // that folder.
+ return false;
+ }
+ } else if (!mFileOp.isDirectory(destFolder)) {
+ mFileOp.mkdirs(destFolder);
+ }
+
+ // Get all the files and dirs of the current destination.
+ // We are not going to clean up the destination first.
+ // Instead we'll copy over and just remove any remaining files or directories.
+ Set<File> destDirs = new HashSet<File>();
+ Set<File> destFiles = new HashSet<File>();
+ File[] files = mFileOp.listFiles(destFolder);
+ if (files != null) {
+ for (File f : files) {
+ if (mFileOp.isDirectory(f)) {
+ destDirs.add(f);
+ } else {
+ destFiles.add(f);
+ }
+ }
+ }
+
+ // First restore all source directories.
+ for (File dir : srcFiles) {
+ if (mFileOp.isDirectory(dir)) {
+ File d = new File(destFolder, dir.getName());
+ destDirs.remove(d);
+ if (!restoreFolder(dir, d)) {
+ result = false;
+ }
+ }
+ }
+
+ // Remove any remaining directories not processed above.
+ for (File dir : destDirs) {
+ mFileOp.deleteFileOrFolder(dir);
+ }
+
+ // Copy any source files over to the destination.
+ for (File file : srcFiles) {
+ if (mFileOp.isFile(file)) {
+ File f = new File(destFolder, file.getName());
+ destFiles.remove(f);
+ try {
+ mFileOp.copyFile(file, f);
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+ }
+
+ // Remove any remaining files not processed above.
+ for (File file : destFiles) {
+ mFileOp.deleteFileOrFolder(file);
+ }
+
+ return result;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java
index 10987fa..b6570db 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/archives/ArchiveReplacement.java
@@ -1,112 +1,112 @@
-/*
- * Copyright (C) 2011 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.archives;
-
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.packages.Package;
-
-
-/**
- * Represents an archive that we want to install and the archive that it is
- * going to replace, if any.
- */
-public class ArchiveReplacement implements IDescription {
-
- private final Archive mNewArchive;
- private final Archive mReplaced;
-
- /**
- * Creates a new replacement where the {@code newArchive} will replace the
- * currently installed {@code replaced} archive.
- * When {@code newArchive} is not intended to replace anything (e.g. because
- * the user is installing a new package not present on her system yet), then
- * {@code replace} shall be null.
- *
- * @param newArchive A "new archive" to be installed. This is always an archive
- * that comes from a remote site. This <em>may</em> be null.
- * @param replaced An optional local archive that the new one will replace.
- * Can be null if this archive does not replace anything.
- */
- public ArchiveReplacement(Archive newArchive, Archive replaced) {
- mNewArchive = newArchive;
- mReplaced = replaced;
- }
-
- /**
- * Returns the "new archive" to be installed.
- * This <em>may</em> be null for missing archives.
- */
- public Archive getNewArchive() {
- return mNewArchive;
- }
-
- /**
- * Returns an optional local archive that the new one will replace.
- * Can be null if this archive does not replace anything.
- */
- public Archive getReplaced() {
- return mReplaced;
- }
-
- /**
- * Returns the long description of the parent package of the new archive, if not null.
- * Otherwise returns an empty string.
- */
- @Override
- public String getLongDescription() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getLongDescription();
- }
- }
- return "";
- }
-
- /**
- * Returns the short description of the parent package of the new archive, if not null.
- * Otherwise returns an empty string.
- */
- @Override
- public String getShortDescription() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getShortDescription();
- }
- }
- return "";
- }
-
- /**
- * Returns the short description of the parent package of the new archive, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging. For UI display, use the {@link IDescription}
- * interface.
- */
- @Override
- public String toString() {
- if (mNewArchive != null) {
- Package p = mNewArchive.getParentPackage();
- if (p != null) {
- return p.getShortDescription();
- }
- }
- return super.toString();
- }
-}
+/*
+ * Copyright (C) 2011 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.archives;
+
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.packages.Package;
+
+
+/**
+ * Represents an archive that we want to install and the archive that it is
+ * going to replace, if any.
+ */
+public class ArchiveReplacement implements IDescription {
+
+ private final Archive mNewArchive;
+ private final Archive mReplaced;
+
+ /**
+ * Creates a new replacement where the {@code newArchive} will replace the
+ * currently installed {@code replaced} archive.
+ * When {@code newArchive} is not intended to replace anything (e.g. because
+ * the user is installing a new package not present on her system yet), then
+ * {@code replace} shall be null.
+ *
+ * @param newArchive A "new archive" to be installed. This is always an archive
+ * that comes from a remote site. This <em>may</em> be null.
+ * @param replaced An optional local archive that the new one will replace.
+ * Can be null if this archive does not replace anything.
+ */
+ public ArchiveReplacement(Archive newArchive, Archive replaced) {
+ mNewArchive = newArchive;
+ mReplaced = replaced;
+ }
+
+ /**
+ * Returns the "new archive" to be installed.
+ * This <em>may</em> be null for missing archives.
+ */
+ public Archive getNewArchive() {
+ return mNewArchive;
+ }
+
+ /**
+ * Returns an optional local archive that the new one will replace.
+ * Can be null if this archive does not replace anything.
+ */
+ public Archive getReplaced() {
+ return mReplaced;
+ }
+
+ /**
+ * Returns the long description of the parent package of the new archive, if not null.
+ * Otherwise returns an empty string.
+ */
+ @Override
+ public String getLongDescription() {
+ if (mNewArchive != null) {
+ Package p = mNewArchive.getParentPackage();
+ if (p != null) {
+ return p.getLongDescription();
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Returns the short description of the parent package of the new archive, if not null.
+ * Otherwise returns an empty string.
+ */
+ @Override
+ public String getShortDescription() {
+ if (mNewArchive != null) {
+ Package p = mNewArchive.getParentPackage();
+ if (p != null) {
+ return p.getShortDescription();
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Returns the short description of the parent package of the new archive, if not null.
+ * Otherwise returns the default Object toString result.
+ * <p/>
+ * This is mostly helpful for debugging. For UI display, use the {@link IDescription}
+ * interface.
+ */
+ @Override
+ public String toString() {
+ if (mNewArchive != null) {
+ Package p = mNewArchive.getParentPackage();
+ if (p != null) {
+ return p.getShortDescription();
+ }
+ }
+ return super.toString();
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
index cc35e54..a388f54 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
@@ -1,697 +1,699 @@
-/*
- * 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.packages;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.util.Pair;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents an add-on XML node in an SDK repository.
- */
-public class AddonPackage extends Package
- implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency, ILayoutlibVersion {
-
- private final String mVendorId;
- private final String mVendorDisplay;
- private final String mNameId;
- private final String mDisplayName;
- private final AndroidVersion mVersion;
-
- /**
- * The helper handling the layoutlib version.
- */
- private final LayoutlibVersionMixin mLayoutlibVersion;
-
- /** An add-on library. */
- public static class Lib {
- private final String mName;
- private final String mDescription;
-
- public Lib(String name, String description) {
- mName = name;
- mDescription = description;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
- result = prime * result + ((mName == null) ? 0 : mName.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Lib)) {
- return false;
- }
- Lib other = (Lib) obj;
- if (mDescription == null) {
- if (other.mDescription != null) {
- return false;
- }
- } else if (!mDescription.equals(other.mDescription)) {
- return false;
- }
- if (mName == null) {
- if (other.mName != null) {
- return false;
- }
- } else if (!mName.equals(other.mName)) {
- return false;
- }
- return true;
- }
- }
-
- private final Lib[] mLibs;
-
- /**
- * Creates a new add-on package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public AddonPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- // --- name id/display ---
- // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
- // These are not optional but we still need to support a fallback for older addons
- // that only provide name and vendor. If the addon provides neither set of fields,
- // it will simply not work as expected.
-
- String nameId = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_ID);
- String nameDisp = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME_DISPLAY);
- String name = XmlParserUtils.getXmlString(packageNode,
- SdkRepoConstants.NODE_NAME);
-
- // The old <name> is equivalent to the new <name-display>
- if (nameDisp.length() == 0) {
- nameDisp = name;
- }
-
- // For a missing id, we simply use a sanitized version of the display name
- if (nameId.length() == 0) {
- nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
- }
-
- assert nameId.length() > 0;
- assert nameDisp.length() > 0;
-
- mNameId = nameId.trim();
- mDisplayName = nameDisp.trim();
-
- // --- vendor id/display ---
- // Same processing for vendor id vs display
-
- String vendorId = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_ID);
- String vendorDisp = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR_DISPLAY);
- String vendor = XmlParserUtils.getXmlString(packageNode,
- SdkAddonConstants.NODE_VENDOR);
-
- // The old <vendor> is equivalent to the new <vendor-display>
- if (vendorDisp.length() == 0) {
- vendorDisp = vendor;
- }
-
- // For a missing id, we simply use a sanitized version of the display vendor
- if (vendorId.length() == 0) {
- boolean hasVendor = vendor.length() > 0;
- vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
- }
-
- assert vendorId.length() > 0;
- assert vendorDisp.length() > 0;
-
- mVendorId = vendorId.trim();
- mVendorDisplay = vendorDisp.trim();
-
- // --- other attributes
-
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);
- mVersion = new AndroidVersion(apiLevel, null /*codeName*/);
-
- mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkAddonConstants.NODE_LIBS));
-
- mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
- }
-
- /**
- * Creates a new platform package based on an actual {@link IAndroidTarget} (which
- * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(IAndroidTarget target, Properties props) {
- return new AddonPackage(target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected AddonPackage(IAndroidTarget target, Properties props) {
- this(null /*source*/, target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected AddonPackage(SdkSource source, IAndroidTarget target, Properties props) {
- super( source, //source
- props, //properties
- target.getRevision(), //revision
- null, //license
- target.getDescription(), //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- target.getLocation() //archiveOsPath
- );
-
- // --- name id/display ---
- // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
- // These are not optional but we still need to support a fallback for older addons
- // that only provide name and vendor. If the addon provides neither set of fields,
- // it will simply not work as expected.
-
- String nameId = getProperty(props, PkgProps.ADDON_NAME_ID, ""); //$NON-NLS-1$
- String nameDisp = getProperty(props, PkgProps.ADDON_NAME_DISPLAY, ""); //$NON-NLS-1$
- String name = getProperty(props, PkgProps.ADDON_NAME, target.getName());
-
- // The old <name> is equivalent to the new <name-display>
- if (nameDisp.length() == 0) {
- nameDisp = name;
- }
-
- // For a missing id, we simply use a sanitized version of the display name
- if (nameId.length() == 0) {
- nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
- }
-
- assert nameId.length() > 0;
- assert nameDisp.length() > 0;
-
- mNameId = nameId.trim();
- mDisplayName = nameDisp.trim();
-
- // --- vendor id/display ---
- // Same processing for vendor id vs display
-
- String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, ""); //$NON-NLS-1$
- String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, ""); //$NON-NLS-1$
- String vendor = getProperty(props, PkgProps.ADDON_VENDOR, target.getVendor());
-
- // The old <vendor> is equivalent to the new <vendor-display>
- if (vendorDisp.length() == 0) {
- vendorDisp = vendor;
- }
-
- // For a missing id, we simply use a sanitized version of the display vendor
- if (vendorId.length() == 0) {
- boolean hasVendor = vendor.length() > 0;
- vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
- }
-
- assert vendorId.length() > 0;
- assert vendorDisp.length() > 0;
-
- mVendorId = vendorId.trim();
- mVendorDisplay = vendorDisp.trim();
-
- // --- other attributes
-
- mVersion = target.getVersion();
- mLayoutlibVersion = new LayoutlibVersionMixin(props);
-
- 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());
- }
- }
- }
-
- /**
- * Creates a broken addon which we know failed to load properly.
- *
- * @param archiveOsPath The absolute OS path of the addon folder.
- * @param sourceProps The properties parsed from the addon's source.properties. Can be null.
- * @param addonProps The properties parsed from the addon manifest (NOT the source.properties).
- * @param error The error indicating why this addon failed to be loaded.
- */
- public static Package createBroken(
- String archiveOsPath,
- Properties sourceProps,
- Map<String, String> addonProps,
- String error) {
- String name = getProperty(sourceProps,
- PkgProps.ADDON_NAME_DISPLAY,
- getProperty(sourceProps,
- PkgProps.ADDON_NAME,
- addonProps.get(SdkManager.ADDON_NAME)));
- String vendor = getProperty(sourceProps,
- PkgProps.ADDON_VENDOR_DISPLAY,
- getProperty(sourceProps,
- PkgProps.ADDON_VENDOR,
- addonProps.get(SdkManager.ADDON_VENDOR)));
- String api = addonProps.get(SdkManager.ADDON_API);
- String revision = addonProps.get(SdkManager.ADDON_REVISION);
-
- String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
- name,
- vendor,
- api,
- revision);
-
- String longDesc = String.format(
- "%1$s\n" +
- "[*] Addon failed to load: %2$s",
- shortDesc,
- error);
-
- int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;
-
- try {
- apiLevel = Integer.parseInt(api);
- } catch(NumberFormatException e) {
- // ignore
- }
-
- return new BrokenPackage(null/*props*/, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- apiLevel,
- archiveOsPath);
- }
-
- @Override
- public int getExactApiLevel() {
- return mVersion.getApiLevel();
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- mLayoutlibVersion.saveProperties(props);
-
- props.setProperty(PkgProps.ADDON_NAME_ID, mNameId);
- props.setProperty(PkgProps.ADDON_NAME_DISPLAY, mDisplayName);
- props.setProperty(PkgProps.ADDON_VENDOR_ID, mVendorId);
- props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, mVendorDisplay);
- }
-
- /**
- * Parses a <libs> element.
- */
- private Lib[] parseLibs(Node libsNode) {
- ArrayList<Lib> libs = new ArrayList<Lib>();
-
- if (libsNode != null) {
- String nsUri = libsNode.getNamespaceURI();
- for(Node child = libsNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- SdkRepoConstants.NODE_LIB.equals(child.getLocalName())) {
- libs.add(parseLib(child));
- }
- }
- }
-
- return libs.toArray(new Lib[libs.size()]);
- }
-
- /**
- * Parses a <lib> element from a <libs> container.
- */
- private Lib parseLib(Node libNode) {
- return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),
- XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));
- }
-
- /** Returns the vendor id, a string, for add-on packages. */
- public @NonNull String getVendorId() {
- return mVendorId;
- }
-
- /** Returns the vendor, a string for display purposes. */
- public @NonNull String getDisplayVendor() {
- return mVendorDisplay;
- }
-
- /** Returns the name id, a string, for add-on packages or for libraries. */
- public @NonNull String getNameId() {
- return mNameId;
- }
-
- /** Returns the name, a string for display purposes. */
- public @NonNull String getDisplayName() {
- return mDisplayName;
- }
-
- /**
- * Returns the version of the platform dependency of this package.
- * <p/>
- * An add-on has the same {@link AndroidVersion} as the platform it depends on.
- */
- @Override
- public @NonNull AndroidVersion getVersion() {
- return mVersion;
- }
-
- /** Returns the libs defined in this add-on. Can be an empty array but not null. */
- public @NonNull Lib[] getLibs() {
- return mLibs;
- }
-
- /**
- * Returns the layoutlib version.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
- * if the layoutlib version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-addon-2.xsd
- */
- @Override
- public @NonNull Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion.getLayoutlibVersion();
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For add-ons, we use "addon-vendor-name-N" where N is the base platform API.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public @NonNull String installId() {
- return encodeAddonName();
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- return String.format("%1$s%2$s",
- getDisplayName(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- return String.format("%1$s, Android API %2$s, revision %3$s%4$s",
- getDisplayName(),
- mVersion.getApiString(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = String.format("%1$s, Android API %2$s, revision %3$s%4$s\nBy %5$s",
- getDisplayName(),
- mVersion.getApiString(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
- getDisplayVendor());
-
- String d = getDescription();
- if (d != null && d.length() > 0) {
- s += '\n' + d;
- }
-
- s += String.format("\nRequires SDK Platform Android API %1$s",
- mVersion.getApiString());
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * 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.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
-
- // First find if this add-on is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (!target.isPlatform() && target.getVersion().equals(mVersion)) {
- // Starting with addon-4.xsd, the addon source.properties differentiate
- // between ids and display strings. However the addon target which relies
- // on the manifest.ini does not so we need to cover both cases.
- // TODO fix when we get rid of manifest.ini for addons
- if ((target.getName().equals(getNameId()) &&
- target.getVendor().equals(getVendorId())) ||
- (target.getName().equals(getDisplayName()) &&
- target.getVendor().equals(getDisplayVendor()))) {
- return new File(target.getLocation());
- }
- }
- }
-
- // Compute a folder directory using the addon declared name and vendor strings.
- String name = encodeAddonName();
-
- for (int i = 0; i < 100; i++) {
- String name2 = i == 0 ? name : String.format("%s-%d", name, i); //$NON-NLS-1$
- File folder = new File(addons, name2);
- if (!folder.exists()) {
- return folder;
- }
- }
-
- // We shouldn't really get here. I mean, seriously, we tried hard enough.
- return null;
- }
-
- private String encodeAddonName() {
- String name = String.format("addon-%s-%s-%s", //$NON-NLS-1$
- getNameId(), getVendorId(), mVersion.getApiString());
- name = name.toLowerCase(Locale.US);
- name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- return name;
- }
-
- /**
- * Computes a sanitized name-id based on an addon name-display.
- * This is used to provide compatibility with older addons that lacks the new fields.
- *
- * @param displayName A name-display field or a old-style name field.
- * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
- */
- private String sanitizeDisplayToNameId(String displayName) {
- String name = displayName.toLowerCase(Locale.US);
- name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
-
- // Trim leading and trailing underscores
- if (name.length() > 1) {
- name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name.length() > 1) {
- name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return name;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof AddonPackage) {
- AddonPackage newPkg = (AddonPackage)pkg;
-
- // check they are the same add-on.
- if (getNameId().equals(newPkg.getNameId()) &&
- getVersion().equals(newPkg.getVersion())) {
- // Check the vendor-id field.
- if (getVendorId().equals(newPkg.getVendorId())) {
- return true;
- }
-
- // When loading addons from the v3 schema that only had a <vendor>
- // field, the vendor field has been converted to vendor-display so
- // as a transition mechanism we should test this also.
- // TODO: in a couple iterations of the SDK Manager, remove this check
- // and only compare using the vendor-id field.
- return getDisplayVendor().equals(newPkg.getDisplayVendor());
- }
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- result = prime * result + Arrays.hashCode(mLibs);
- result = prime * result + ((mDisplayName == null) ? 0 : mDisplayName.hashCode());
- result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof AddonPackage)) {
- return false;
- }
- AddonPackage other = (AddonPackage) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- if (!Arrays.equals(mLibs, other.mLibs)) {
- return false;
- }
- if (mNameId == null) {
- if (other.mNameId != null) {
- return false;
- }
- } else if (!mNameId.equals(other.mNameId)) {
- return false;
- }
- if (mVendorId == null) {
- if (other.mVendorId != null) {
- return false;
- }
- } else if (!mVendorId.equals(other.mVendorId)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-
- /**
- * For addon packages, we want to add vendor|name to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|vid:" + getVendorId() + //$NON-NLS-1$
- "|nid:" + getNameId() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkAddonConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.utils.Pair;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents an add-on XML node in an SDK repository.
+ */
+public class AddonPackage extends MajorRevisionPackage
+ implements IAndroidVersionProvider, IPlatformDependency,
+ IExactApiLevelDependency, ILayoutlibVersion {
+
+ private final String mVendorId;
+ private final String mVendorDisplay;
+ private final String mNameId;
+ private final String mDisplayName;
+ private final AndroidVersion mVersion;
+
+ /**
+ * The helper handling the layoutlib version.
+ */
+ private final LayoutlibVersionMixin mLayoutlibVersion;
+
+ /** An add-on library. */
+ public static class Lib {
+ private final String mName;
+ private final String mDescription;
+
+ public Lib(String name, String description) {
+ mName = name;
+ mDescription = description;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
+ result = prime * result + ((mName == null) ? 0 : mName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Lib)) {
+ return false;
+ }
+ Lib other = (Lib) obj;
+ if (mDescription == null) {
+ if (other.mDescription != null) {
+ return false;
+ }
+ } else if (!mDescription.equals(other.mDescription)) {
+ return false;
+ }
+ if (mName == null) {
+ if (other.mName != null) {
+ return false;
+ }
+ } else if (!mName.equals(other.mName)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private final Lib[] mLibs;
+
+ /**
+ * Creates a new add-on package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public AddonPackage(
+ SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ // --- name id/display ---
+ // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
+ // These are not optional but we still need to support a fallback for older addons
+ // that only provide name and vendor. If the addon provides neither set of fields,
+ // it will simply not work as expected.
+
+ String nameId = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME_ID);
+ String nameDisp = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME_DISPLAY);
+ String name = PackageParserUtils.getXmlString(packageNode,
+ SdkRepoConstants.NODE_NAME);
+
+ // The old <name> is equivalent to the new <name-display>
+ if (nameDisp.length() == 0) {
+ nameDisp = name;
+ }
+
+ // For a missing id, we simply use a sanitized version of the display name
+ if (nameId.length() == 0) {
+ nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
+ }
+
+ assert nameId.length() > 0;
+ assert nameDisp.length() > 0;
+
+ mNameId = nameId.trim();
+ mDisplayName = nameDisp.trim();
+
+ // --- vendor id/display ---
+ // Same processing for vendor id vs display
+
+ String vendorId = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR_ID);
+ String vendorDisp = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR_DISPLAY);
+ String vendor = PackageParserUtils.getXmlString(packageNode,
+ SdkAddonConstants.NODE_VENDOR);
+
+ // The old <vendor> is equivalent to the new <vendor-display>
+ if (vendorDisp.length() == 0) {
+ vendorDisp = vendor;
+ }
+
+ // For a missing id, we simply use a sanitized version of the display vendor
+ if (vendorId.length() == 0) {
+ boolean hasVendor = vendor.length() > 0;
+ vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
+ }
+
+ assert vendorId.length() > 0;
+ assert vendorDisp.length() > 0;
+
+ mVendorId = vendorId.trim();
+ mVendorDisplay = vendorDisp.trim();
+
+ // --- other attributes
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);
+ mVersion = new AndroidVersion(apiLevel, null /*codeName*/);
+
+ mLibs = parseLibs(
+ PackageParserUtils.findChildElement(packageNode, SdkAddonConstants.NODE_LIBS));
+
+ mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
+ }
+
+ /**
+ * Creates a new platform package based on an actual {@link IAndroidTarget} (which
+ * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual target location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(IAndroidTarget target, Properties props) {
+ return new AddonPackage(target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected AddonPackage(IAndroidTarget target, Properties props) {
+ this(null /*source*/, target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected AddonPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ super( source, //source
+ props, //properties
+ target.getRevision(), //revision
+ null, //license
+ target.getDescription(), //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ target.getLocation() //archiveOsPath
+ );
+
+ // --- name id/display ---
+ // addon-4.xsd introduces the name-id, name-display, vendor-id and vendor-display.
+ // These are not optional but we still need to support a fallback for older addons
+ // that only provide name and vendor. If the addon provides neither set of fields,
+ // it will simply not work as expected.
+
+ String nameId = getProperty(props, PkgProps.ADDON_NAME_ID, ""); //$NON-NLS-1$
+ String nameDisp = getProperty(props, PkgProps.ADDON_NAME_DISPLAY, ""); //$NON-NLS-1$
+ String name = getProperty(props, PkgProps.ADDON_NAME, target.getName());
+
+ // The old <name> is equivalent to the new <name-display>
+ if (nameDisp.length() == 0) {
+ nameDisp = name;
+ }
+
+ // For a missing id, we simply use a sanitized version of the display name
+ if (nameId.length() == 0) {
+ nameId = sanitizeDisplayToNameId(name.length() > 0 ? name : nameDisp);
+ }
+
+ assert nameId.length() > 0;
+ assert nameDisp.length() > 0;
+
+ mNameId = nameId.trim();
+ mDisplayName = nameDisp.trim();
+
+ // --- vendor id/display ---
+ // Same processing for vendor id vs display
+
+ String vendorId = getProperty(props, PkgProps.ADDON_VENDOR_ID, ""); //$NON-NLS-1$
+ String vendorDisp = getProperty(props, PkgProps.ADDON_VENDOR_DISPLAY, ""); //$NON-NLS-1$
+ String vendor = getProperty(props, PkgProps.ADDON_VENDOR, target.getVendor());
+
+ // The old <vendor> is equivalent to the new <vendor-display>
+ if (vendorDisp.length() == 0) {
+ vendorDisp = vendor;
+ }
+
+ // For a missing id, we simply use a sanitized version of the display vendor
+ if (vendorId.length() == 0) {
+ boolean hasVendor = vendor.length() > 0;
+ vendorId = sanitizeDisplayToNameId(hasVendor ? vendor : vendorDisp);
+ }
+
+ assert vendorId.length() > 0;
+ assert vendorDisp.length() > 0;
+
+ mVendorId = vendorId.trim();
+ mVendorDisplay = vendorDisp.trim();
+
+ // --- other attributes
+
+ mVersion = target.getVersion();
+ mLayoutlibVersion = new LayoutlibVersionMixin(props);
+
+ 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());
+ }
+ }
+ }
+
+ /**
+ * Creates a broken addon which we know failed to load properly.
+ *
+ * @param archiveOsPath The absolute OS path of the addon folder.
+ * @param sourceProps The properties parsed from the addon's source.properties. Can be null.
+ * @param addonProps The properties parsed from the addon manifest (NOT the source.properties).
+ * @param error The error indicating why this addon failed to be loaded.
+ */
+ public static Package createBroken(
+ String archiveOsPath,
+ Properties sourceProps,
+ Map<String, String> addonProps,
+ String error) {
+ String name = getProperty(sourceProps,
+ PkgProps.ADDON_NAME_DISPLAY,
+ getProperty(sourceProps,
+ PkgProps.ADDON_NAME,
+ addonProps.get(SdkManager.ADDON_NAME)));
+ String vendor = getProperty(sourceProps,
+ PkgProps.ADDON_VENDOR_DISPLAY,
+ getProperty(sourceProps,
+ PkgProps.ADDON_VENDOR,
+ addonProps.get(SdkManager.ADDON_VENDOR)));
+ String api = addonProps.get(SdkManager.ADDON_API);
+ String revision = addonProps.get(SdkManager.ADDON_REVISION);
+
+ String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
+ name,
+ vendor,
+ api,
+ revision);
+
+ String longDesc = String.format(
+ "%1$s\n" +
+ "[*] Addon failed to load: %2$s",
+ shortDesc,
+ error);
+
+ int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;
+
+ try {
+ apiLevel = Integer.parseInt(api);
+ } catch(NumberFormatException e) {
+ // ignore
+ }
+
+ return new BrokenPackage(null/*props*/, shortDesc, longDesc,
+ IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
+ apiLevel,
+ archiveOsPath);
+ }
+
+ @Override
+ public int getExactApiLevel() {
+ return mVersion.getApiLevel();
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ mVersion.saveProperties(props);
+ mLayoutlibVersion.saveProperties(props);
+
+ props.setProperty(PkgProps.ADDON_NAME_ID, mNameId);
+ props.setProperty(PkgProps.ADDON_NAME_DISPLAY, mDisplayName);
+ props.setProperty(PkgProps.ADDON_VENDOR_ID, mVendorId);
+ props.setProperty(PkgProps.ADDON_VENDOR_DISPLAY, mVendorDisplay);
+ }
+
+ /**
+ * Parses a <libs> element.
+ */
+ private Lib[] parseLibs(Node libsNode) {
+ ArrayList<Lib> libs = new ArrayList<Lib>();
+
+ if (libsNode != null) {
+ String nsUri = libsNode.getNamespaceURI();
+ for(Node child = libsNode.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI()) &&
+ SdkRepoConstants.NODE_LIB.equals(child.getLocalName())) {
+ libs.add(parseLib(child));
+ }
+ }
+ }
+
+ return libs.toArray(new Lib[libs.size()]);
+ }
+
+ /**
+ * Parses a <lib> element from a <libs> container.
+ */
+ private Lib parseLib(Node libNode) {
+ return new Lib(PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),
+ PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));
+ }
+
+ /** Returns the vendor id, a string, for add-on packages. */
+ public @NonNull String getVendorId() {
+ return mVendorId;
+ }
+
+ /** Returns the vendor, a string for display purposes. */
+ public @NonNull String getDisplayVendor() {
+ return mVendorDisplay;
+ }
+
+ /** Returns the name id, a string, for add-on packages or for libraries. */
+ public @NonNull String getNameId() {
+ return mNameId;
+ }
+
+ /** Returns the name, a string for display purposes. */
+ public @NonNull String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * Returns the version of the platform dependency of this package.
+ * <p/>
+ * An add-on has the same {@link AndroidVersion} as the platform it depends on.
+ */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /** Returns the libs defined in this add-on. Can be an empty array but not null. */
+ public @NonNull Lib[] getLibs() {
+ return mLibs;
+ }
+
+ /**
+ * Returns the layoutlib version.
+ * <p/>
+ * The first integer is the API of layoublib, which should be > 0.
+ * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
+ * if the layoutlib version isn't specified.
+ * <p/>
+ * The second integer is the revision for that given API. It is >= 0
+ * and works as a minor revision number, incremented for the same API level.
+ *
+ * @since sdk-addon-2.xsd
+ */
+ @Override
+ public @NonNull Pair<Integer, Integer> getLayoutlibVersion() {
+ return mLayoutlibVersion.getLayoutlibVersion();
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For add-ons, we use "addon-vendor-name-N" where N is the base platform API.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public @NonNull String installId() {
+ return encodeAddonName();
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return String.format("%1$s%2$s",
+ getDisplayName(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ return String.format("%1$s, Android API %2$s, revision %3$s%4$s",
+ getDisplayName(),
+ mVersion.getApiString(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = String.format("%1$s, Android API %2$s, revision %3$s%4$s\nBy %5$s",
+ getDisplayName(),
+ mVersion.getApiString(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
+ getDisplayVendor());
+
+ String d = getDescription();
+ if (d != null && d.length() > 0) {
+ s += '\n' + d;
+ }
+
+ s += String.format("\nRequires SDK Platform Android API %1$s",
+ mVersion.getApiString());
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * 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.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
+
+ // First find if this add-on is already installed. If so, reuse the same directory.
+ for (IAndroidTarget target : sdkManager.getTargets()) {
+ if (!target.isPlatform() && target.getVersion().equals(mVersion)) {
+ // Starting with addon-4.xsd, the addon source.properties differentiate
+ // between ids and display strings. However the addon target which relies
+ // on the manifest.ini does not so we need to cover both cases.
+ // TODO fix when we get rid of manifest.ini for addons
+ if ((target.getName().equals(getNameId()) &&
+ target.getVendor().equals(getVendorId())) ||
+ (target.getName().equals(getDisplayName()) &&
+ target.getVendor().equals(getDisplayVendor()))) {
+ return new File(target.getLocation());
+ }
+ }
+ }
+
+ // Compute a folder directory using the addon declared name and vendor strings.
+ String name = encodeAddonName();
+
+ for (int i = 0; i < 100; i++) {
+ String name2 = i == 0 ? name : String.format("%s-%d", name, i); //$NON-NLS-1$
+ File folder = new File(addons, name2);
+ if (!folder.exists()) {
+ return folder;
+ }
+ }
+
+ // We shouldn't really get here. I mean, seriously, we tried hard enough.
+ return null;
+ }
+
+ private String encodeAddonName() {
+ String name = String.format("addon-%s-%s-%s", //$NON-NLS-1$
+ getNameId(), getVendorId(), mVersion.getApiString());
+ name = name.toLowerCase(Locale.US);
+ name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ return name;
+ }
+
+ /**
+ * Computes a sanitized name-id based on an addon name-display.
+ * This is used to provide compatibility with older addons that lacks the new fields.
+ *
+ * @param displayName A name-display field or a old-style name field.
+ * @return A non-null sanitized name-id that fits in the {@code [a-zA-Z0-9_-]+} pattern.
+ */
+ private String sanitizeDisplayToNameId(String displayName) {
+ String name = displayName.toLowerCase(Locale.US);
+ name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // Trim leading and trailing underscores
+ if (name.length() > 1) {
+ name = name.replaceAll("^_+", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name.length() > 1) {
+ name = name.replaceAll("_+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return name;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof AddonPackage) {
+ AddonPackage newPkg = (AddonPackage)pkg;
+
+ // check they are the same add-on.
+ if (getNameId().equals(newPkg.getNameId()) &&
+ getAndroidVersion().equals(newPkg.getAndroidVersion())) {
+ // Check the vendor-id field.
+ if (getVendorId().equals(newPkg.getVendorId())) {
+ return true;
+ }
+
+ // When loading addons from the v3 schema that only had a <vendor>
+ // field, the vendor field has been converted to vendor-display so
+ // as a transition mechanism we should test this also.
+ // TODO: in a couple iterations of the SDK Manager, remove this check
+ // and only compare using the vendor-id field.
+ return getDisplayVendor().equals(newPkg.getDisplayVendor());
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ result = prime * result + Arrays.hashCode(mLibs);
+ result = prime * result + ((mDisplayName == null) ? 0 : mDisplayName.hashCode());
+ result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof AddonPackage)) {
+ return false;
+ }
+ AddonPackage other = (AddonPackage) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ if (!Arrays.equals(mLibs, other.mLibs)) {
+ return false;
+ }
+ if (mNameId == null) {
+ if (other.mNameId != null) {
+ return false;
+ }
+ } else if (!mNameId.equals(other.mNameId)) {
+ return false;
+ }
+ if (mVendorId == null) {
+ if (other.mVendorId != null) {
+ return false;
+ }
+ } else if (!mVendorId.equals(other.mVendorId)) {
+ return false;
+ }
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * For addon packages, we want to add vendor|name to the sorting key
+ * <em>before<em/> the revision number.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ protected String comparisonKey() {
+ String s = super.comparisonKey();
+ int pos = s.indexOf("|r:"); //$NON-NLS-1$
+ assert pos > 0;
+ s = s.substring(0, pos) +
+ "|vid:" + getVendorId() + //$NON-NLS-1$
+ "|nid:" + getNameId() + //$NON-NLS-1$
+ s.substring(pos);
+ return s;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
index 6a7e9c6..e2c11a0 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
@@ -1,201 +1,201 @@
-/*
- * Copyright (C) 2010 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.packages;
-
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * Represents an SDK repository package that is incomplete.
- * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.
- */
-public class BrokenPackage extends Package
- implements IExactApiLevelDependency, IMinApiLevelDependency {
-
- /**
- * The minimal API level required by this package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- /**
- * The exact API level required by this package, if > 0,
- * or {@link #API_LEVEL_INVALID} if there is no such requirement.
- */
- private final int mExactApiLevel;
-
- private final String mShortDescription;
- private final String mLongDescription;
-
- /**
- * Creates a new "broken" package that represents a package that we failed to load,
- * for whatever error indicated in {@code longDescription}.
- * There is also an <em>optional</em> API level dependency that can be specified.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- BrokenPackage(Properties props,
- String shortDescription,
- String longDescription,
- int minApiLevel,
- int exactApiLevel,
- String archiveOsPath) {
- super( null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- longDescription, //description
- null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
- archiveOsPath //archiveOsPath
- );
- mShortDescription = shortDescription;
- mLongDescription = longDescription;
- mMinApiLevel = minApiLevel;
- mExactApiLevel = exactApiLevel;
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- * <p/>
- * Base implementation override: We don't actually save properties for a broken package.
- */
- @Override
- public void saveProperties(Properties props) {
- // Nop. We don't actually save properties for a broken package.
- }
-
- /**
- * Returns the minimal API level required by this package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /**
- * Returns the exact API level required by this package, if > 0,
- * or {@link #API_LEVEL_INVALID} if the value was missing.
- */
- @Override
- public int getExactApiLevel() {
- return mExactApiLevel;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For broken packages, we return an empty string. These are not installable.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- return mShortDescription;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- return mShortDescription;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description uses what was given to the constructor.
- * If it's missing, it will use whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
-
- String s = mLongDescription;
- if (s != null && s.length() != 0) {
- return s;
- }
-
- s = getDescription();
- if (s != null && s.length() != 0) {
- return s;
- }
- return getShortDescription();
- }
-
- /**
- * We should not be attempting to install a broken package.
- *
- * {@inheritDoc}
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- // We should not be attempting to install a broken package.
- return null;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof BrokenPackage) {
- return mShortDescription.equals(((BrokenPackage) pkg).mShortDescription) &&
- getDescription().equals(pkg.getDescription()) &&
- getMinApiLevel() == ((BrokenPackage) pkg).getMinApiLevel();
- }
-
- return false;
- }
-
- @Override
- public boolean preInstallHook(Archive archive,
- ITaskMonitor monitor,
- String osSdkRoot,
- File installFolder) {
- // Nothing specific to do.
- return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
- }
-
- /**
- * Computes a hash of the installed content (in case of successful install.)
- *
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- // Nothing specific to do.
- super.postInstallHook(archive, monitor, installFolder);
- }
-}
+/*
+ * Copyright (C) 2010 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.packages;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Represents an SDK repository package that is incomplete.
+ * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.
+ */
+public class BrokenPackage extends MajorRevisionPackage
+ implements IExactApiLevelDependency, IMinApiLevelDependency {
+
+ /**
+ * The minimal API level required by this package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final int mMinApiLevel;
+
+ /**
+ * The exact API level required by this package, if > 0,
+ * or {@link #API_LEVEL_INVALID} if there is no such requirement.
+ */
+ private final int mExactApiLevel;
+
+ private final String mShortDescription;
+ private final String mLongDescription;
+
+ /**
+ * Creates a new "broken" package that represents a package that we failed to load,
+ * for whatever error indicated in {@code longDescription}.
+ * There is also an <em>optional</em> API level dependency that can be specified.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ BrokenPackage(Properties props,
+ String shortDescription,
+ String longDescription,
+ int minApiLevel,
+ int exactApiLevel,
+ String archiveOsPath) {
+ super( null, //source
+ props, //properties
+ 0, //revision will be taken from props
+ null, //license
+ longDescription, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ archiveOsPath //archiveOsPath
+ );
+ mShortDescription = shortDescription;
+ mLongDescription = longDescription;
+ mMinApiLevel = minApiLevel;
+ mExactApiLevel = exactApiLevel;
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ * <p/>
+ * Base implementation override: We don't actually save properties for a broken package.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ // Nop. We don't actually save properties for a broken package.
+ }
+
+ /**
+ * Returns the minimal API level required by this package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public int getMinApiLevel() {
+ return mMinApiLevel;
+ }
+
+ /**
+ * Returns the exact API level required by this package, if > 0,
+ * or {@link #API_LEVEL_INVALID} if the value was missing.
+ */
+ @Override
+ public int getExactApiLevel() {
+ return mExactApiLevel;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For broken packages, we return an empty string. These are not installable.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return mShortDescription;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ return mShortDescription;
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description uses what was given to the constructor.
+ * If it's missing, it will use whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+
+ String s = mLongDescription;
+ if (s != null && s.length() != 0) {
+ return s;
+ }
+
+ s = getDescription();
+ if (s != null && s.length() != 0) {
+ return s;
+ }
+ return getShortDescription();
+ }
+
+ /**
+ * We should not be attempting to install a broken package.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ // We should not be attempting to install a broken package.
+ return null;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof BrokenPackage) {
+ return mShortDescription.equals(((BrokenPackage) pkg).mShortDescription) &&
+ getDescription().equals(pkg.getDescription()) &&
+ getMinApiLevel() == ((BrokenPackage) pkg).getMinApiLevel();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean preInstallHook(Archive archive,
+ ITaskMonitor monitor,
+ String osSdkRoot,
+ File installFolder) {
+ // Nothing specific to do.
+ return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
+ }
+
+ /**
+ * Computes a hash of the installed content (in case of successful install.)
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
+ // Nothing specific to do.
+ super.postInstallHook(archive, monitor, installFolder);
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
index 54dfc5e..927d361 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
@@ -1,302 +1,307 @@
-/*
- * 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.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a doc XML node in an SDK repository.
- * <p/>
- * Note that a doc package has a version and thus implements {@link IPackageVersion}.
- * However there is no mandatory dependency that limits installation so this does not
- * implement {@link IPlatformDependency}.
- */
-public class DocPackage extends Package implements IPackageVersion {
-
- private final AndroidVersion mVersion;
-
- /**
- * Creates a new doc package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public DocPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.length() == 0) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(SdkSource source,
- Properties props,
- int apiLevel,
- String codename,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- return new DocPackage(source, props, apiLevel, codename, revision, license, description,
- descUrl, archiveOs, archiveArch, archiveOsPath);
- }
-
- private DocPackage(SdkSource source,
- Properties props,
- int apiLevel,
- String codename,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOs,
- archiveArch,
- archiveOsPath);
- mVersion = new AndroidVersion(props, apiLevel, codename);
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- }
-
- /**
- * Returns the version, for platform, add-on and doc packages.
- * Can be 0 if this is a local package of unknown api-level.
- */
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For docs, we use "doc-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "doc-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- if (mVersion.isPreview()) {
- return String.format("Documentation for Android '%1$s' Preview SDK%2$s",
- mVersion.getCodename(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Documentation for Android SDK%2$s",
- mVersion.getApiLevel(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- if (mVersion.isPreview()) {
- return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",
- mVersion.getCodename(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Documentation for Android SDK, API %1$d, revision %2$s%3$s",
- mVersion.getApiLevel(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "doc" package should always be located in SDK/docs.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(osSdkRoot, SdkConstants.FD_DOCS);
- }
-
- /**
- * Consider doc packages to be the same if they cover the same API level,
- * regardless of their revision number.
- */
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof DocPackage) {
- AndroidVersion rev2 = ((DocPackage) pkg).getVersion();
- return this.getVersion().equals(rev2);
- }
-
- return false;
- }
-
- /**
- * {@inheritDoc}
- * <hr>
- * Doc packages are a bit different since there can only be one doc installed at
- * the same time.
- * <p/>
- * We now consider that docs for different APIs are NOT updates, e.g. doc for API N+1
- * is no longer considered an update for doc API N.
- * However docs that have the same API version (API level + codename) are considered
- * updates if they have a higher revision number (so 15 rev 2 is an update for 15 rev 1,
- * but is not an update for 14 rev 1.)
- */
- @Override
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- // check they are the same kind of object
- if (!(replacementPackage instanceof DocPackage)) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- DocPackage replacementDoc = (DocPackage)replacementPackage;
-
- AndroidVersion replacementVersion = replacementDoc.getVersion();
-
- // Check if they're the same exact (api and codename)
- if (replacementVersion.equals(mVersion)) {
- // exact same version, so check the revision level
- if (replacementPackage.getRevision() > this.getRevision()) {
- return UpdateInfo.UPDATE;
- }
- } else {
- // not the same version? we check if they have the same api level and the new one
- // is a preview, in which case it's also an update (since preview have the api level
- // of the _previous_ version.)
- if (replacementVersion.getApiLevel() == mVersion.getApiLevel() &&
- replacementVersion.isPreview()) {
- return UpdateInfo.UPDATE;
- }
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof DocPackage)) {
- return false;
- }
- DocPackage other = (DocPackage) obj;
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a doc XML node in an SDK repository.
+ * <p/>
+ * Note that a doc package has a version and thus implements {@link IAndroidVersionProvider}.
+ * However there is no mandatory dependency that limits installation so this does not
+ * implement {@link IPlatformDependency}.
+ */
+public class DocPackage extends MajorRevisionPackage implements IAndroidVersionProvider {
+
+ private final AndroidVersion mVersion;
+
+ /**
+ * Creates a new doc package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public DocPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ if (codeName.length() == 0) {
+ codeName = null;
+ }
+ mVersion = new AndroidVersion(apiLevel, codeName);
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(SdkSource source,
+ Properties props,
+ int apiLevel,
+ String codename,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new DocPackage(source, props, apiLevel, codename, revision, license, description,
+ descUrl, archiveOs, archiveArch, archiveOsPath);
+ }
+
+ private DocPackage(SdkSource source,
+ Properties props,
+ int apiLevel,
+ String codename,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source,
+ props,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
+ mVersion = new AndroidVersion(props, apiLevel, codename);
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be give the constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ mVersion.saveProperties(props);
+ }
+
+ /**
+ * Returns the version, for platform, add-on and doc packages.
+ * Can be 0 if this is a local package of unknown api-level.
+ */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For docs, we use "doc-N" where N is the API or the preview codename.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return "doc-" + mVersion.getApiString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ if (mVersion.isPreview()) {
+ return String.format("Documentation for Android '%1$s' Preview SDK%2$s",
+ mVersion.getCodename(),
+ isObsolete() ? " (Obsolete)" : "");
+ } else {
+ return String.format("Documentation for Android SDK%2$s",
+ mVersion.getApiLevel(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ if (mVersion.isPreview()) {
+ return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",
+ mVersion.getCodename(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ } else {
+ return String.format("Documentation for Android SDK, API %1$d, revision %2$s%3$s",
+ mVersion.getApiLevel(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A "doc" package should always be located in SDK/docs.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ return new File(osSdkRoot, SdkConstants.FD_DOCS);
+ }
+
+ /**
+ * Consider doc packages to be the same if they cover the same API level,
+ * regardless of their revision number.
+ */
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof DocPackage) {
+ AndroidVersion rev2 = ((DocPackage) pkg).getAndroidVersion();
+ return this.getAndroidVersion().equals(rev2);
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <hr>
+ * Doc packages are a bit different since there can only be one doc installed at
+ * the same time.
+ * <p/>
+ * We now consider that docs for different APIs are NOT updates, e.g. doc for API N+1
+ * is no longer considered an update for doc API N.
+ * However docs that have the same API version (API level + codename) are considered
+ * updates if they have a higher revision number (so 15 rev 2 is an update for 15 rev 1,
+ * but is not an update for 14 rev 1.)
+ */
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ // check they are the same kind of object
+ if (!(replacementPackage instanceof DocPackage)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ DocPackage replacementDoc = (DocPackage)replacementPackage;
+
+ AndroidVersion replacementVersion = replacementDoc.getAndroidVersion();
+
+ // Check if they're the same exact (api and codename)
+ if (replacementVersion.equals(mVersion)) {
+ // exact same version, so check the revision level
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+ } else {
+ // not the same version? we check if they have the same api level and the new one
+ // is a preview, in which case it's also an update (since preview have the api level
+ // of the _previous_ version.)
+ if (replacementVersion.getApiLevel() == mVersion.getApiLevel() &&
+ replacementVersion.isPreview()) {
+ return UpdateInfo.UPDATE;
+ }
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof DocPackage)) {
+ return false;
+ }
+ DocPackage other = (DocPackage) obj;
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
index cc27853..78a2450 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
@@ -1,750 +1,751 @@
-/*
- * 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.packages;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.NullSdkLog;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.LocalSdkParser;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.RepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Pattern;
-
-/**
- * Represents a extra XML node in an SDK repository.
- */
-public class ExtraPackage extends MinToolsPackage
- implements IMinApiLevelDependency {
-
- /**
- * The extra display name. Used in the UI to represent the package. It can be anything.
- */
- private final String mDisplayName;
-
- /**
- * The vendor id name. It is a simple alphanumeric string [a-zA-Z0-9_-].
- */
- private final String mVendorId;
-
- /**
- * The vendor display name. Used in the UI to represent the vendor. It can be anything.
- */
- private final String mVendorDisplay;
-
- /**
- * The sub-folder name. It must be a non-empty single-segment path.
- */
- private final String mPath;
-
- /**
- * The optional old_paths, if any. If present, this is a list of old "path" values that
- * we'd like to migrate to the current "path" name for this extra.
- */
- private final String mOldPaths;
-
- /**
- * The minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- /**
- * The project-files listed by this extra package.
- * The array can be empty but not null.
- */
- private final String[] mProjectFiles;
-
- /**
- * Creates a new tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public ExtraPackage(
- SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mPath = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
-
- // Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.
- // These are not optional, they are mandatory in addon-4 but we still treat them
- // as optional so that we can fallback on using <vendor> which was the only one
- // defined in addon-3.xsd.
- String name = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);
- String vname = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);
- String vid = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);
-
- if (vid.length() == 0) {
- // If vid is missing, use the old <vendor> attribute.
- // Note that in a valid XML, vendor-id cannot be an empty string.
- // The only reason vid can be empty is when <vendor-id> is missing, which
- // happens in an addon-3 schema, in which case the old <vendor> needs to be used.
- String vendor = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);
- vid = sanitizeLegacyVendor(vendor);
- if (vname.length() == 0) {
- vname = vendor;
- }
- }
- if (vname.length() == 0) {
- // The vendor-display name can be empty, in which case we use the vendor-id.
- vname = vid;
- }
- mVendorDisplay = vname.trim();
- mVendorId = vid.trim();
-
- if (name.length() == 0) {
- // If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = getPrettyName();
- }
- mDisplayName = name.trim();
-
- mMinApiLevel = XmlParserUtils.getXmlInt(
- packageNode, RepoConstants.NODE_MIN_API_LEVEL, MIN_API_LEVEL_NOT_SPECIFIED);
-
- mProjectFiles = parseProjectFiles(
- XmlParserUtils.getFirstChild(packageNode, RepoConstants.NODE_PROJECT_FILES));
-
- mOldPaths = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
- }
-
- private String[] parseProjectFiles(Node projectFilesNode) {
- ArrayList<String> paths = new ArrayList<String>();
-
- if (projectFilesNode != null) {
- String nsUri = projectFilesNode.getNamespaceURI();
- for(Node child = projectFilesNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- RepoConstants.NODE_PATH.equals(child.getLocalName())) {
- String path = child.getTextContent();
- if (path != null) {
- path = path.trim();
- if (path.length() > 0) {
- paths.add(path);
- }
- }
- }
- }
- }
-
- return paths.toArray(new String[paths.size()]);
- }
-
- /**
- * Manually create a new package with one archive and the given attributes or properties.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(SdkSource source,
- Properties props,
- String vendor,
- String path,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- ExtraPackage ep = new ExtraPackage(source, props, vendor, path, revision, license,
- description, descUrl, archiveOs, archiveArch, archiveOsPath);
- return ep;
- }
-
- /**
- * Constructor used to create a mock {@link ExtraPackage}.
- * Most of the attributes here are optional.
- * When not defined, they will be extracted from the {@code props} properties.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected ExtraPackage(SdkSource source,
- Properties props,
- String vendorId,
- String path,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOs,
- archiveArch,
- archiveOsPath);
-
- // The path argument comes before whatever could be in the properties
- mPath = path != null ? path : getProperty(props, PkgProps.EXTRA_PATH, path);
-
- String name = getProperty(props, PkgProps.EXTRA_NAME_DISPLAY, ""); //$NON-NLS-1$
- String vname = getProperty(props, PkgProps.EXTRA_VENDOR_DISPLAY, ""); //$NON-NLS-1$
- String vid = vendorId != null ? vendorId :
- getProperty(props, PkgProps.EXTRA_VENDOR_ID, ""); //$NON-NLS-1$
-
- if (vid.length() == 0) {
- // If vid is missing, use the old <vendor> attribute.
- // <vendor> did not exist prior to schema repo-v3 and tools r8.
- String vendor = getProperty(props, PkgProps.EXTRA_VENDOR, ""); //$NON-NLS-1$
- vid = sanitizeLegacyVendor(vendor);
- if (vname.length() == 0) {
- vname = vendor;
- }
- }
- if (vname.length() == 0) {
- // The vendor-display name can be empty, in which case we use the vendor-id.
- vname = vid;
- }
- mVendorDisplay = vname.trim();
- mVendorId = vid.trim();
-
- if (name.length() == 0) {
- // If name is missing, use the <path> attribute as done in an addon-3 schema.
- name = getPrettyName();
- }
- mDisplayName = name.trim();
-
- mOldPaths = getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
-
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.EXTRA_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
-
- String projectFiles = getProperty(props, PkgProps.EXTRA_PROJECT_FILES, null);
- ArrayList<String> filePaths = new ArrayList<String>();
- if (projectFiles != null && projectFiles.length() > 0) {
- for (String filePath : projectFiles.split(Pattern.quote(File.pathSeparator))) {
- filePath = filePath.trim();
- if (filePath.length() > 0) {
- filePaths.add(filePath);
- }
- }
- }
- mProjectFiles = filePaths.toArray(new String[filePaths.size()]);
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- props.setProperty(PkgProps.EXTRA_PATH, mPath);
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, mDisplayName);
- props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, mVendorDisplay);
- props.setProperty(PkgProps.EXTRA_VENDOR_ID, mVendorId);
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
- }
-
- if (mProjectFiles.length > 0) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mProjectFiles.length; i++) {
- if (i > 0) {
- sb.append(File.pathSeparatorChar);
- }
- sb.append(mProjectFiles[i]);
- }
- props.setProperty(PkgProps.EXTRA_PROJECT_FILES, sb.toString());
- }
-
- if (mOldPaths != null && mOldPaths.length() > 0) {
- props.setProperty(PkgProps.EXTRA_OLD_PATHS, mOldPaths);
- }
- }
-
- /**
- * Returns the minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /**
- * The project-files listed by this extra package.
- * The array can be empty but not null.
- * <p/>
- * IMPORTANT: directory separators are NOT translated and may not match
- * the {@link File#separatorChar} of the current platform. It's up to the
- * user to adequately interpret the paths.
- * Similarly, no guarantee is made on the validity of the paths.
- * Users are expected to apply all usual sanity checks such as removing
- * "./" and "../" and making sure these paths don't reference files outside
- * of the installed archive.
- *
- * @since sdk-repository-4.xsd or sdk-addon-2.xsd
- */
- public String[] getProjectFiles() {
- return mProjectFiles;
- }
-
- /**
- * Returns the old_paths, a list of obsolete path names for the extra package.
- * <p/>
- * These can be used by the installer to migrate an extra package using one of the
- * old paths into the new path.
- * <p/>
- * These can also be used to recognize "old" renamed packages as the same as
- * the current one.
- *
- * @return A list of old paths. Can be empty but not null.
- */
- public String[] getOldPaths() {
- if (mOldPaths == null || mOldPaths.length() == 0) {
- return new String[0];
- }
- return mOldPaths.split(";"); //$NON-NLS-1$
- }
-
- /**
- * Returns the sanitized path folder name. It is a single-segment path.
- * <p/>
- * The package is installed in SDK/extras/vendor_name/path_name.
- */
- public String getPath() {
- // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
- // and cannot be empty. Let's be defensive and enforce that anyway since things
- // like "____" are still valid values that we don't want to allow.
-
- // Sanitize the path
- String path = mPath.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
- if (path.length() == 0 || path.equals("_")) { //$NON-NLS-1$
- int h = path.hashCode();
- path = String.format("extra%08x", h); //$NON-NLS-1$
- }
-
- return path;
- }
-
- /**
- * Returns the vendor id.
- */
- public String getVendorId() {
- return mVendorId;
- }
-
- public String getVendorDisplay() {
- return mVendorDisplay;
- }
-
- public String getDisplayName() {
- return mDisplayName;
- }
-
- /** Transforms the legacy vendor name into a usable vendor id. */
- private String sanitizeLegacyVendor(String vendorDisplay) {
- // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
- // and cannot be empty. Let's be defensive and enforce that anyway since things
- // like "____" are still valid values that we don't want to allow.
-
- if (vendorDisplay != null && vendorDisplay.length() > 0) {
- String vendor = vendorDisplay.trim();
- // Sanitize the vendor
- vendor = vendor.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
- if (vendor.equals("_")) { //$NON-NLS-1$
- int h = vendor.hashCode();
- vendor = String.format("vendor%08x", h); //$NON-NLS-1$
- }
-
- return vendor;
- }
-
- return ""; //$NON-NLS-1$
-
- }
-
- /**
- * Used to produce a suitable name-display based on the current {@link #mPath}
- * and {@link #mVendorDisplay} in addon-3 schemas.
- */
- private String getPrettyName() {
- String name = mPath;
-
- // In the past, we used to save the extras in a folder vendor-path,
- // and that "vendor" would end up in the path when we reload the extra from
- // disk. Detect this and compensate.
- if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
- if (name.startsWith(mVendorDisplay + "-")) { //$NON-NLS-1$
- name = name.substring(mVendorDisplay.length() + 1);
- }
- }
-
- // Uniformize all spaces in the name
- if (name != null) {
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
- if (name == null || name.length() == 0) {
- name = "Unknown Extra";
- }
-
- if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
- name = mVendorDisplay + " " + name; //$NON-NLS-1$
- name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- // Look at all lower case characters in range [1..n-1] and replace them by an upper
- // case if they are preceded by a space. Also upper cases the first character of the
- // string.
- boolean changed = false;
- char[] chars = name.toCharArray();
- for (int n = chars.length - 1, i = 0; i < n; i++) {
- if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
- chars[i] = Character.toUpperCase(chars[i]);
- changed = true;
- }
- }
- if (changed) {
- name = new String(chars);
- }
-
- // Special case: reformat a few typical acronyms.
- name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
- name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
-
- return name;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For extras, we use "extra-vendor-path".
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return String.format("extra-%1$s-%2$s", //$NON-NLS-1$
- getVendorId(),
- getPath());
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String s = String.format("%1$s%2$s",
- getDisplayName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
-
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String s = String.format("%1$s, revision %2$d%3$s",
- getDisplayName(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
-
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = String.format("%1$s, revision %2$d%3$s\nBy %4$s",
- getDisplayName(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
- getVendorDisplay());
-
- String d = getDescription();
- if (d != null && d.length() > 0) {
- s += '\n' + d;
- }
-
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- s += String.format("\nRequires tools revision %1$d", getMinToolsRevision());
- }
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- s += String.format("\nRequires SDK Platform Android API %1$s", getMinApiLevel());
- }
-
- File localPath = getLocalArchivePath();
- if (localPath != null) {
- // For a local archive, also put the install path in the long description.
- // This should help users locate the extra on their drive.
- s += String.format("\nLocation: %1$s", localPath.getAbsolutePath());
- } else {
- // For a non-installed archive, indicate where it would be installed.
- s += String.format("\nInstall path: %1$s",
- getInstallSubFolder(null/*sdk root*/).getPath());
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "tool" package should always be located in SDK/tools.
- *
- * @param osSdkRoot The OS path of the SDK root folder. Must NOT be null.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * Not used in this implementation.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // First find if this extra is already installed. If so, reuse the same directory.
- LocalSdkParser localParser = new LocalSdkParser();
- Package[] pkgs = localParser.parseSdk(
- osSdkRoot,
- sdkManager,
- LocalSdkParser.PARSE_EXTRAS,
- new NullTaskMonitor(new NullSdkLog()));
-
- for (Package pkg : pkgs) {
- if (sameItemAs(pkg) && pkg instanceof ExtraPackage) {
- File localPath = ((ExtraPackage) pkg).getLocalArchivePath();
- if (localPath != null) {
- return localPath;
- }
- }
- }
-
- return getInstallSubFolder(osSdkRoot);
- }
-
- /**
- * Computes the "sub-folder" install path, relative to the given SDK root.
- * For an extra package, this is generally ".../extra/vendor-id/path".
- *
- * @param osSdkRoot The OS path of the SDK root folder if known.
- * This CAN be null, in which case the path will start at /extra.
- * @return Either /extra/vendor/path or sdk-root/extra/vendor-id/path.
- */
- private File getInstallSubFolder(@Nullable String osSdkRoot) {
- // The /extras dir at the root of the SDK
- File path = new File(osSdkRoot, SdkConstants.FD_EXTRAS);
-
- String vendor = getVendorId();
- if (vendor != null && vendor.length() > 0) {
- path = new File(path, vendor);
- }
-
- String name = getPath();
- if (name != null && name.length() > 0) {
- path = new File(path, name);
- }
-
- return path;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- // Extra packages are similar if they have the same path and vendor
- if (pkg instanceof ExtraPackage) {
- ExtraPackage ep = (ExtraPackage) pkg;
-
- String[] epOldPaths = ep.getOldPaths();
- int lenEpOldPaths = epOldPaths.length;
- for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
- if (sameVendorAndPath(
- mVendorId, mPath,
- ep.mVendorId, indexEp < 0 ? ep.mPath : epOldPaths[indexEp])) {
- return true;
- }
- }
-
- String[] thisOldPaths = getOldPaths();
- int lenThisOldPaths = thisOldPaths.length;
- for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
- if (sameVendorAndPath(
- mVendorId, indexThis < 0 ? mPath : thisOldPaths[indexThis],
- ep.mVendorId, ep.mPath)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static boolean sameVendorAndPath(
- String thisVendor, String thisPath,
- String otherVendor, String otherPath) {
- // To be backward compatible, we need to support the old vendor-path form
- // in either the current or the remote package.
- //
- // The vendor test below needs to account for an old installed package
- // (e.g. with an install path of vendor-name) that has then been updated
- // in-place and thus when reloaded contains the vendor name in both the
- // path and the vendor attributes.
- if (otherPath != null && thisPath != null && thisVendor != null) {
- if (otherPath.equals(thisVendor + '-' + thisPath) &&
- (otherVendor == null ||
- otherVendor.length() == 0 ||
- otherVendor.equals(thisVendor))) {
- return true;
- }
- }
- if (thisPath != null && otherPath != null && otherVendor != null) {
- if (thisPath.equals(otherVendor + '-' + otherPath) &&
- (thisVendor == null ||
- thisVendor.length() == 0 ||
- thisVendor.equals(otherVendor))) {
- return true;
- }
- }
-
-
- if (thisPath != null && thisPath.equals(otherPath)) {
- if ((thisVendor == null && otherVendor == null) ||
- (thisVendor != null && thisVendor.equals(otherVendor))) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * For extra packages, we want to add vendor|path to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|ve:" + getVendorId() + //$NON-NLS-1$
- "|pa:" + getPath() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-
- // ---
-
- /**
- * If this package is installed, returns the install path of the archive if valid.
- * Returns null if not installed or if the path does not exist.
- */
- private File getLocalArchivePath() {
- Archive[] archives = getArchives();
- if (archives.length == 1 && archives[0].isLocal()) {
- File path = new File(archives[0].getLocalOsPath());
- if (path.isDirectory()) {
- return path;
- }
- }
-
- return null;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + mMinApiLevel;
- result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
- result = prime * result + Arrays.hashCode(mProjectFiles);
- result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof ExtraPackage)) {
- return false;
- }
- ExtraPackage other = (ExtraPackage) obj;
- if (mMinApiLevel != other.mMinApiLevel) {
- return false;
- }
- if (mPath == null) {
- if (other.mPath != null) {
- return false;
- }
- } else if (!mPath.equals(other.mPath)) {
- return false;
- }
- if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) {
- return false;
- }
- if (mVendorDisplay == null) {
- if (other.mVendorDisplay != null) {
- return false;
- }
- } else if (!mVendorDisplay.equals(other.mVendorDisplay)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.LocalSdkParser;
+import com.android.sdklib.internal.repository.NullTaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.RepoConstants;
+import com.android.utils.NullLogger;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a extra XML node in an SDK repository.
+ */
+public class ExtraPackage extends MinToolsPackage
+ implements IMinApiLevelDependency {
+
+ /**
+ * The extra display name. Used in the UI to represent the package. It can be anything.
+ */
+ private final String mDisplayName;
+
+ /**
+ * The vendor id name. It is a simple alphanumeric string [a-zA-Z0-9_-].
+ */
+ private final String mVendorId;
+
+ /**
+ * The vendor display name. Used in the UI to represent the vendor. It can be anything.
+ */
+ private final String mVendorDisplay;
+
+ /**
+ * The sub-folder name. It must be a non-empty single-segment path.
+ */
+ private final String mPath;
+
+ /**
+ * The optional old_paths, if any. If present, this is a list of old "path" values that
+ * we'd like to migrate to the current "path" name for this extra.
+ */
+ private final String mOldPaths;
+
+ /**
+ * The minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final int mMinApiLevel;
+
+ /**
+ * The project-files listed by this extra package.
+ * The array can be empty but not null.
+ */
+ private final String[] mProjectFiles;
+
+ /**
+ * Creates a new tool package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public ExtraPackage(
+ SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mPath = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
+
+ // Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.
+ // These are not optional, they are mandatory in addon-4 but we still treat them
+ // as optional so that we can fallback on using <vendor> which was the only one
+ // defined in addon-3.xsd.
+ String name =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);
+ String vname =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);
+ String vid =
+ PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);
+
+ if (vid.length() == 0) {
+ // If vid is missing, use the old <vendor> attribute.
+ // Note that in a valid XML, vendor-id cannot be an empty string.
+ // The only reason vid can be empty is when <vendor-id> is missing, which
+ // happens in an addon-3 schema, in which case the old <vendor> needs to be used.
+ String vendor = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);
+ vid = sanitizeLegacyVendor(vendor);
+ if (vname.length() == 0) {
+ vname = vendor;
+ }
+ }
+ if (vname.length() == 0) {
+ // The vendor-display name can be empty, in which case we use the vendor-id.
+ vname = vid;
+ }
+ mVendorDisplay = vname.trim();
+ mVendorId = vid.trim();
+
+ if (name.length() == 0) {
+ // If name is missing, use the <path> attribute as done in an addon-3 schema.
+ name = getPrettyName();
+ }
+ mDisplayName = name.trim();
+
+ mMinApiLevel = PackageParserUtils.getXmlInt(
+ packageNode, RepoConstants.NODE_MIN_API_LEVEL, MIN_API_LEVEL_NOT_SPECIFIED);
+
+ mProjectFiles = parseProjectFiles(
+ PackageParserUtils.findChildElement(packageNode, RepoConstants.NODE_PROJECT_FILES));
+
+ mOldPaths = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);
+ }
+
+ private String[] parseProjectFiles(Node projectFilesNode) {
+ ArrayList<String> paths = new ArrayList<String>();
+
+ if (projectFilesNode != null) {
+ String nsUri = projectFilesNode.getNamespaceURI();
+ for(Node child = projectFilesNode.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI()) &&
+ RepoConstants.NODE_PATH.equals(child.getLocalName())) {
+ String path = child.getTextContent();
+ if (path != null) {
+ path = path.trim();
+ if (path.length() > 0) {
+ paths.add(path);
+ }
+ }
+ }
+ }
+ }
+
+ return paths.toArray(new String[paths.size()]);
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes or properties.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(SdkSource source,
+ Properties props,
+ String vendor,
+ String path,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ ExtraPackage ep = new ExtraPackage(source, props, vendor, path, revision, license,
+ description, descUrl, archiveOs, archiveArch, archiveOsPath);
+ return ep;
+ }
+
+ /**
+ * Constructor used to create a mock {@link ExtraPackage}.
+ * Most of the attributes here are optional.
+ * When not defined, they will be extracted from the {@code props} properties.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected ExtraPackage(SdkSource source,
+ Properties props,
+ String vendorId,
+ String path,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source,
+ props,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
+
+ // The path argument comes before whatever could be in the properties
+ mPath = path != null ? path : getProperty(props, PkgProps.EXTRA_PATH, path);
+
+ String name = getProperty(props, PkgProps.EXTRA_NAME_DISPLAY, ""); //$NON-NLS-1$
+ String vname = getProperty(props, PkgProps.EXTRA_VENDOR_DISPLAY, ""); //$NON-NLS-1$
+ String vid = vendorId != null ? vendorId :
+ getProperty(props, PkgProps.EXTRA_VENDOR_ID, ""); //$NON-NLS-1$
+
+ if (vid.length() == 0) {
+ // If vid is missing, use the old <vendor> attribute.
+ // <vendor> did not exist prior to schema repo-v3 and tools r8.
+ String vendor = getProperty(props, PkgProps.EXTRA_VENDOR, ""); //$NON-NLS-1$
+ vid = sanitizeLegacyVendor(vendor);
+ if (vname.length() == 0) {
+ vname = vendor;
+ }
+ }
+ if (vname.length() == 0) {
+ // The vendor-display name can be empty, in which case we use the vendor-id.
+ vname = vid;
+ }
+ mVendorDisplay = vname.trim();
+ mVendorId = vid.trim();
+
+ if (name.length() == 0) {
+ // If name is missing, use the <path> attribute as done in an addon-3 schema.
+ name = getPrettyName();
+ }
+ mDisplayName = name.trim();
+
+ mOldPaths = getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);
+
+ mMinApiLevel = getPropertyInt(props, PkgProps.EXTRA_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
+
+ String projectFiles = getProperty(props, PkgProps.EXTRA_PROJECT_FILES, null);
+ ArrayList<String> filePaths = new ArrayList<String>();
+ if (projectFiles != null && projectFiles.length() > 0) {
+ for (String filePath : projectFiles.split(Pattern.quote(File.pathSeparator))) {
+ filePath = filePath.trim();
+ if (filePath.length() > 0) {
+ filePaths.add(filePath);
+ }
+ }
+ }
+ mProjectFiles = filePaths.toArray(new String[filePaths.size()]);
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be give the constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ props.setProperty(PkgProps.EXTRA_PATH, mPath);
+ props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, mDisplayName);
+ props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, mVendorDisplay);
+ props.setProperty(PkgProps.EXTRA_VENDOR_ID, mVendorId);
+
+ if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
+ props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
+ }
+
+ if (mProjectFiles.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mProjectFiles.length; i++) {
+ if (i > 0) {
+ sb.append(File.pathSeparatorChar);
+ }
+ sb.append(mProjectFiles[i]);
+ }
+ props.setProperty(PkgProps.EXTRA_PROJECT_FILES, sb.toString());
+ }
+
+ if (mOldPaths != null && mOldPaths.length() > 0) {
+ props.setProperty(PkgProps.EXTRA_OLD_PATHS, mOldPaths);
+ }
+ }
+
+ /**
+ * Returns the minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public int getMinApiLevel() {
+ return mMinApiLevel;
+ }
+
+ /**
+ * The project-files listed by this extra package.
+ * The array can be empty but not null.
+ * <p/>
+ * IMPORTANT: directory separators are NOT translated and may not match
+ * the {@link File#separatorChar} of the current platform. It's up to the
+ * user to adequately interpret the paths.
+ * Similarly, no guarantee is made on the validity of the paths.
+ * Users are expected to apply all usual sanity checks such as removing
+ * "./" and "../" and making sure these paths don't reference files outside
+ * of the installed archive.
+ *
+ * @since sdk-repository-4.xsd or sdk-addon-2.xsd
+ */
+ public String[] getProjectFiles() {
+ return mProjectFiles;
+ }
+
+ /**
+ * Returns the old_paths, a list of obsolete path names for the extra package.
+ * <p/>
+ * These can be used by the installer to migrate an extra package using one of the
+ * old paths into the new path.
+ * <p/>
+ * These can also be used to recognize "old" renamed packages as the same as
+ * the current one.
+ *
+ * @return A list of old paths. Can be empty but not null.
+ */
+ public String[] getOldPaths() {
+ if (mOldPaths == null || mOldPaths.length() == 0) {
+ return new String[0];
+ }
+ return mOldPaths.split(";"); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the sanitized path folder name. It is a single-segment path.
+ * <p/>
+ * The package is installed in SDK/extras/vendor_name/path_name.
+ */
+ public String getPath() {
+ // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
+ // and cannot be empty. Let's be defensive and enforce that anyway since things
+ // like "____" are still valid values that we don't want to allow.
+
+ // Sanitize the path
+ String path = mPath.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
+ if (path.length() == 0 || path.equals("_")) { //$NON-NLS-1$
+ int h = path.hashCode();
+ path = String.format("extra%08x", h); //$NON-NLS-1$
+ }
+
+ return path;
+ }
+
+ /**
+ * Returns the vendor id.
+ */
+ public String getVendorId() {
+ return mVendorId;
+ }
+
+ public String getVendorDisplay() {
+ return mVendorDisplay;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /** Transforms the legacy vendor name into a usable vendor id. */
+ private String sanitizeLegacyVendor(String vendorDisplay) {
+ // The XSD specifies the XML vendor and path should only contain [a-zA-Z0-9]+
+ // and cannot be empty. Let's be defensive and enforce that anyway since things
+ // like "____" are still valid values that we don't want to allow.
+
+ if (vendorDisplay != null && vendorDisplay.length() > 0) {
+ String vendor = vendorDisplay.trim();
+ // Sanitize the vendor
+ vendor = vendor.replaceAll("[^a-zA-Z0-9-]+", "_"); //$NON-NLS-1$
+ if (vendor.equals("_")) { //$NON-NLS-1$
+ int h = vendor.hashCode();
+ vendor = String.format("vendor%08x", h); //$NON-NLS-1$
+ }
+
+ return vendor;
+ }
+
+ return ""; //$NON-NLS-1$
+
+ }
+
+ /**
+ * Used to produce a suitable name-display based on the current {@link #mPath}
+ * and {@link #mVendorDisplay} in addon-3 schemas.
+ */
+ private String getPrettyName() {
+ String name = mPath;
+
+ // In the past, we used to save the extras in a folder vendor-path,
+ // and that "vendor" would end up in the path when we reload the extra from
+ // disk. Detect this and compensate.
+ if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
+ if (name.startsWith(mVendorDisplay + "-")) { //$NON-NLS-1$
+ name = name.substring(mVendorDisplay.length() + 1);
+ }
+ }
+
+ // Uniformize all spaces in the name
+ if (name != null) {
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (name == null || name.length() == 0) {
+ name = "Unknown Extra";
+ }
+
+ if (mVendorDisplay != null && mVendorDisplay.length() > 0) {
+ name = mVendorDisplay + " " + name; //$NON-NLS-1$
+ name = name.replaceAll("[ _\t\f-]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // Look at all lower case characters in range [1..n-1] and replace them by an upper
+ // case if they are preceded by a space. Also upper cases the first character of the
+ // string.
+ boolean changed = false;
+ char[] chars = name.toCharArray();
+ for (int n = chars.length - 1, i = 0; i < n; i++) {
+ if (Character.isLowerCase(chars[i]) && (i == 0 || chars[i - 1] == ' ')) {
+ chars[i] = Character.toUpperCase(chars[i]);
+ changed = true;
+ }
+ }
+ if (changed) {
+ name = new String(chars);
+ }
+
+ // Special case: reformat a few typical acronyms.
+ name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
+ name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
+
+ return name;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For extras, we use "extra-vendor-path".
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return String.format("extra-%1$s-%2$s", //$NON-NLS-1$
+ getVendorId(),
+ getPath());
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s = String.format("%1$s%2$s",
+ getDisplayName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ String s = String.format("%1$s, revision %2$s%3$s",
+ getDisplayName(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+
+ return s;
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = String.format("%1$s, revision %2$s%3$s\nBy %4$s",
+ getDisplayName(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "", //$NON-NLS-2$
+ getVendorDisplay());
+
+ String d = getDescription();
+ if (d != null && d.length() > 0) {
+ s += '\n' + d;
+ }
+
+ if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
+ s += String.format("\nRequires tools revision %1$s",
+ getMinToolsRevision().toShortString());
+ }
+
+ if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
+ s += String.format("\nRequires SDK Platform Android API %1$s", getMinApiLevel());
+ }
+
+ File localPath = getLocalArchivePath();
+ if (localPath != null) {
+ // For a local archive, also put the install path in the long description.
+ // This should help users locate the extra on their drive.
+ s += String.format("\nLocation: %1$s", localPath.getAbsolutePath());
+ } else {
+ // For a non-installed archive, indicate where it would be installed.
+ s += String.format("\nInstall path: %1$s",
+ getInstallSubFolder(null/*sdk root*/).getPath());
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A "tool" package should always be located in SDK/tools.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder. Must NOT be null.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * Not used in this implementation.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+
+ // First find if this extra is already installed. If so, reuse the same directory.
+ LocalSdkParser localParser = new LocalSdkParser();
+ Package[] pkgs = localParser.parseSdk(
+ osSdkRoot,
+ sdkManager,
+ LocalSdkParser.PARSE_EXTRAS,
+ new NullTaskMonitor(NullLogger.getLogger()));
+
+ for (Package pkg : pkgs) {
+ if (sameItemAs(pkg) && pkg instanceof ExtraPackage) {
+ File localPath = ((ExtraPackage) pkg).getLocalArchivePath();
+ if (localPath != null) {
+ return localPath;
+ }
+ }
+ }
+
+ return getInstallSubFolder(osSdkRoot);
+ }
+
+ /**
+ * Computes the "sub-folder" install path, relative to the given SDK root.
+ * For an extra package, this is generally ".../extra/vendor-id/path".
+ *
+ * @param osSdkRoot The OS path of the SDK root folder if known.
+ * This CAN be null, in which case the path will start at /extra.
+ * @return Either /extra/vendor/path or sdk-root/extra/vendor-id/path.
+ */
+ private File getInstallSubFolder(@Nullable String osSdkRoot) {
+ // The /extras dir at the root of the SDK
+ File path = new File(osSdkRoot, SdkConstants.FD_EXTRAS);
+
+ String vendor = getVendorId();
+ if (vendor != null && vendor.length() > 0) {
+ path = new File(path, vendor);
+ }
+
+ String name = getPath();
+ if (name != null && name.length() > 0) {
+ path = new File(path, name);
+ }
+
+ return path;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ // Extra packages are similar if they have the same path and vendor
+ if (pkg instanceof ExtraPackage) {
+ ExtraPackage ep = (ExtraPackage) pkg;
+
+ String[] epOldPaths = ep.getOldPaths();
+ int lenEpOldPaths = epOldPaths.length;
+ for (int indexEp = -1; indexEp < lenEpOldPaths; indexEp++) {
+ if (sameVendorAndPath(
+ mVendorId, mPath,
+ ep.mVendorId, indexEp < 0 ? ep.mPath : epOldPaths[indexEp])) {
+ return true;
+ }
+ }
+
+ String[] thisOldPaths = getOldPaths();
+ int lenThisOldPaths = thisOldPaths.length;
+ for (int indexThis = -1; indexThis < lenThisOldPaths; indexThis++) {
+ if (sameVendorAndPath(
+ mVendorId, indexThis < 0 ? mPath : thisOldPaths[indexThis],
+ ep.mVendorId, ep.mPath)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean sameVendorAndPath(
+ String thisVendor, String thisPath,
+ String otherVendor, String otherPath) {
+ // To be backward compatible, we need to support the old vendor-path form
+ // in either the current or the remote package.
+ //
+ // The vendor test below needs to account for an old installed package
+ // (e.g. with an install path of vendor-name) that has then been updated
+ // in-place and thus when reloaded contains the vendor name in both the
+ // path and the vendor attributes.
+ if (otherPath != null && thisPath != null && thisVendor != null) {
+ if (otherPath.equals(thisVendor + '-' + thisPath) &&
+ (otherVendor == null ||
+ otherVendor.length() == 0 ||
+ otherVendor.equals(thisVendor))) {
+ return true;
+ }
+ }
+ if (thisPath != null && otherPath != null && otherVendor != null) {
+ if (thisPath.equals(otherVendor + '-' + otherPath) &&
+ (thisVendor == null ||
+ thisVendor.length() == 0 ||
+ thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+
+ if (thisPath != null && thisPath.equals(otherPath)) {
+ if ((thisVendor == null && otherVendor == null) ||
+ (thisVendor != null && thisVendor.equals(otherVendor))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * For extra packages, we want to add vendor|path to the sorting key
+ * <em>before<em/> the revision number.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ protected String comparisonKey() {
+ String s = super.comparisonKey();
+ int pos = s.indexOf("|r:"); //$NON-NLS-1$
+ assert pos > 0;
+ s = s.substring(0, pos) +
+ "|ve:" + getVendorId() + //$NON-NLS-1$
+ "|pa:" + getPath() + //$NON-NLS-1$
+ s.substring(pos);
+ return s;
+ }
+
+ // ---
+
+ /**
+ * If this package is installed, returns the install path of the archive if valid.
+ * Returns null if not installed or if the path does not exist.
+ */
+ private File getLocalArchivePath() {
+ Archive[] archives = getArchives();
+ if (archives.length == 1 && archives[0].isLocal()) {
+ File path = new File(archives[0].getLocalOsPath());
+ if (path.isDirectory()) {
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + mMinApiLevel;
+ result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
+ result = prime * result + Arrays.hashCode(mProjectFiles);
+ result = prime * result + ((mVendorDisplay == null) ? 0 : mVendorDisplay.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof ExtraPackage)) {
+ return false;
+ }
+ ExtraPackage other = (ExtraPackage) obj;
+ if (mMinApiLevel != other.mMinApiLevel) {
+ return false;
+ }
+ if (mPath == null) {
+ if (other.mPath != null) {
+ return false;
+ }
+ } else if (!mPath.equals(other.mPath)) {
+ return false;
+ }
+ if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) {
+ return false;
+ }
+ if (mVendorDisplay == null) {
+ if (other.mVendorDisplay != null) {
+ return false;
+ }
+ } else if (!mVendorDisplay.equals(other.mVendorDisplay)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
new file mode 100755
index 0000000..4c4387b
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2012 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.packages;
+
+import com.android.annotations.NonNull;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Package multi-part revision number composed of a tuple
+ * (major.minor.micro) and an optional preview revision
+ * (the lack of a preview number indicates it's not a preview
+ * but a final package.)
+ *
+ * @see MajorRevision
+ */
+public class FullRevision implements Comparable<FullRevision> {
+
+ public static final int MISSING_MAJOR_REV = 0;
+ public static final int IMPLICIT_MINOR_REV = 0;
+ public static final int IMPLICIT_MICRO_REV = 0;
+ public static final int NOT_A_PREVIEW = 0;
+
+ private final static Pattern FULL_REVISION_PATTERN =
+ // 1=major 2=minor 3=micro 4=preview
+ Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?\\s*(?:rc([0-9]+))?\\s*");
+
+ private final int mMajor;
+ private final int mMinor;
+ private final int mMicro;
+ private final int mPreview;
+
+ public FullRevision(int major) {
+ this(major, 0, 0);
+ }
+
+ public FullRevision(int major, int minor, int micro) {
+ this(major, minor, micro, NOT_A_PREVIEW);
+ }
+
+ public FullRevision(int major, int minor, int micro, int preview) {
+ mMajor = major;
+ mMinor = minor;
+ mMicro = micro;
+ mPreview = preview;
+ }
+
+ public int getMajor() {
+ return mMajor;
+ }
+
+ public int getMinor() {
+ return mMinor;
+ }
+
+ public int getMicro() {
+ return mMicro;
+ }
+
+ public boolean isPreview() {
+ return mPreview > NOT_A_PREVIEW;
+ }
+
+ public int getPreview() {
+ return mPreview;
+ }
+
+ /**
+ * Parses a string of format "major.minor.micro rcPreview" and returns
+ * a new {@link FullRevision} for it. All the fields except major are
+ * optional.
+ * <p/>
+ * The parsing is equivalent to the pseudo-BNF/regexp:
+ * <pre>
+ * Major/Minor/Micro/Preview := [0-9]+
+ * Revision := Major ('.' Minor ('.' Micro)? )? \s* ('rc'Preview)?
+ * </pre>
+ *
+ * @param revision A non-null revision to parse.
+ * @return A new non-null {@link FullRevision}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ public static @NonNull FullRevision parseRevision(@NonNull String revision)
+ throws NumberFormatException {
+
+ if (revision == null) {
+ throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$
+ }
+
+ Throwable cause = null;
+ try {
+ Matcher m = FULL_REVISION_PATTERN.matcher(revision);
+ if (m != null && m.matches()) {
+ int major = Integer.parseInt(m.group(1));
+ String s = m.group(2);
+ int minor = s == null ? IMPLICIT_MINOR_REV : Integer.parseInt(s);
+ s = m.group(3);
+ int micro = s == null ? IMPLICIT_MICRO_REV : Integer.parseInt(s);
+ s = m.group(4);
+ int preview = s == null ? NOT_A_PREVIEW : Integer.parseInt(s);
+
+ return new FullRevision(major, minor, micro, preview);
+ }
+ } catch (Throwable t) {
+ cause = t;
+ }
+
+ NumberFormatException n = new NumberFormatException(
+ "Invalid full revision: " + revision); //$NON-NLS-1$
+ n.initCause(cause);
+ throw n;
+ }
+
+ /**
+ * Returns the version in a fixed format major.minor.micro
+ * with an optional "rc preview#". For example it would
+ * return "18.0.0", "18.1.0" or "18.1.2 rc5".
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mMajor)
+ .append('.').append(mMinor)
+ .append('.').append(mMicro);
+
+ if (mPreview != NOT_A_PREVIEW) {
+ sb.append(" rc").append(mPreview);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the version in a dynamic format "major.minor.micro rc#".
+ * This is similar to {@link #toString()} except it omits minor, micro
+ * or preview versions when they are zero.
+ * For example it would return "18 rc1" instead of "18.0.0 rc1",
+ * or "18.1 rc2" instead of "18.1.0 rc2".
+ */
+ public String toShortString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mMajor);
+ if (mMinor > 0 || mMicro > 0) {
+ sb.append('.').append(mMinor);
+ }
+ if (mMicro > 0) {
+ sb.append('.').append(mMicro);
+ }
+ if (mPreview != NOT_A_PREVIEW) {
+ sb.append(" rc").append(mPreview);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mMajor;
+ result = prime * result + mMinor;
+ result = prime * result + mMicro;
+ result = prime * result + mPreview;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) {
+ return true;
+ }
+ if (rhs == null) {
+ return false;
+ }
+ if (!(rhs instanceof FullRevision)) {
+ return false;
+ }
+ FullRevision other = (FullRevision) rhs;
+ if (mMajor != other.mMajor) {
+ return false;
+ }
+ if (mMinor != other.mMinor) {
+ return false;
+ }
+ if (mMicro != other.mMicro) {
+ return false;
+ }
+ if (mPreview != other.mPreview) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.
+ *
+ * Note that preview/release candidate are released before their final version,
+ * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the
+ * lack of preview number was "+inf":
+ * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"
+ * and more than "18.1.2.4"
+ */
+ @Override
+ public int compareTo(FullRevision rhs) {
+ int delta = mMajor - rhs.mMajor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMinor - rhs.mMinor;
+ if (delta != 0) {
+ return delta;
+ }
+
+ delta = mMicro - rhs.mMicro;
+ if (delta != 0) {
+ return delta;
+ }
+
+ int p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview;
+ int p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview;
+ delta = p1 - p2;
+ return delta;
+ }
+
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
new file mode 100755
index 0000000..88827f5
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 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.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a package in an SDK repository that has a {@link FullRevision},
+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
+ */
+public abstract class FullRevisionPackage extends Package
+ implements IFullRevisionProvider {
+
+ private final FullRevision mPreviewVersion;
+
+ /**
+ * 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.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ FullRevisionPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mPreviewVersion = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public FullRevisionPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ FullRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+ if (rev == null) {
+ rev = new FullRevision(revision);
+ }
+
+ mPreviewVersion = rev;
+ }
+
+ @Override
+ public FullRevision getRevision() {
+ return mPreviewVersion;
+ }
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ props.setProperty(PkgProps.PKG_REVISION, mPreviewVersion.toShortString());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mPreviewVersion == null) ? 0 : mPreviewVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof FullRevisionPackage)) {
+ return false;
+ }
+ FullRevisionPackage other = (FullRevisionPackage) obj;
+ if (mPreviewVersion == null) {
+ if (other.mPreviewVersion != null) {
+ return false;
+ }
+ } else if (!mPreviewVersion.equals(other.mPreviewVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Computes whether the given package is a suitable update for the current package.
+ * <p/>
+ * A specific case here is that a release package can update a preview, whereas
+ * a preview can only update another preview.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ if (replacementPackage == null) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check they are the same item, ignoring the preview bit.
+ if (!sameItemAs(replacementPackage, true /*ignorePreviews*/)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // a preview cannot update a non-preview
+ if (!getRevision().isPreview() && replacementPackage.getRevision().isPreview()) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check revision number
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
index 77a6a1d..14d6214 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
@@ -1,36 +1,37 @@
-/*
- * 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.packages;
-
-import com.android.sdklib.AndroidVersion;
-
-/**
- * Interface for packages that provide an {@link AndroidVersion}.
- * <p/>
- * Note that {@link IPlatformDependency} is a similar interface, but with a different semantic.
- * The {@link IPlatformDependency} denotes that a given package can only be installed if the
- * requested platform is present, whereas this interface denotes that the given package simply
- * has a version, which is not necessarily a dependency.
- */
-public interface IPackageVersion {
-
- /**
- * Returns the version, for platform, add-on and doc packages.
- * Can be 0 if this is a local package of unknown api-level.
- */
- public abstract AndroidVersion getVersion();
-}
+/*
+ * 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.packages;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+
+/**
+ * Interface for packages that provide an {@link AndroidVersion}.
+ * <p/>
+ * Note that {@link IPlatformDependency} is a similar interface, but with a different semantic.
+ * The {@link IPlatformDependency} denotes that a given package can only be installed if the
+ * requested platform is present, whereas this interface denotes that the given package simply
+ * has a version, which is not necessarily a dependency.
+ */
+public interface IAndroidVersionProvider {
+
+ /**
+ * Returns the android version, for platform, add-on and doc packages.
+ * Can be 0 if this is a local package of unknown api-level.
+ */
+ public abstract @NonNull AndroidVersion getAndroidVersion();
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
new file mode 100755
index 0000000..e4ec292
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.packages;
+
+
+
+/**
+ * Interface for packages that provide a {@link FullRevision},
+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.
+ * <p/>
+ * This interface is a tag. It indicates that {@link Package#getRevision()} returns a
+ * {@link FullRevision} instead of a limited {@link MajorRevision}. <br/>
+ * The preview version number is available via {@link Package#getRevision()}.
+ */
+public interface IFullRevisionProvider {
+
+ /**
+ * Returns whether the give package represents the same item as the current package.
+ * <p/>
+ * Two packages are considered the same if they represent the same thing, except for the
+ * revision number.
+ * @param pkg the package to compare
+ * @param ignorePreviews true if 2 packages should be considered the same even if one
+ * is a preview and the other one is not.
+ * @return true if the item are the same.
+ */
+ public abstract boolean sameItemAs(Package pkg, boolean ignorePreviews);
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java
index 74b18bf..39c1dc2 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ILayoutlibVersion.java
@@ -1,42 +1,42 @@
-/*
- * Copyright (C) 2011 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.packages;
-
-import com.android.util.Pair;
-
-/**
- * Interface used to decorate a {@link Package} that provides a version for layout lib.
- */
-public interface ILayoutlibVersion {
-
- public static final int LAYOUTLIB_API_NOT_SPECIFIED = 0;
- public static final int LAYOUTLIB_REV_NOT_SPECIFIED = 0;
-
- /**
- * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd
- */
- public Pair<Integer, Integer> getLayoutlibVersion();
-}
+/*
+ * Copyright (C) 2011 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.packages;
+
+import com.android.utils.Pair;
+
+/**
+ * Interface used to decorate a {@link Package} that provides a version for layout lib.
+ */
+public interface ILayoutlibVersion {
+
+ public static final int LAYOUTLIB_API_NOT_SPECIFIED = 0;
+ public static final int LAYOUTLIB_REV_NOT_SPECIFIED = 0;
+
+ /**
+ * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
+ * <p/>
+ * The first integer is the API of layoublib, which should be > 0.
+ * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
+ * version isn't specified.
+ * <p/>
+ * The second integer is the revision for that given API. It is >= 0
+ * and works as a minor revision number, incremented for the same API level.
+ *
+ * @since sdk-repository-4.xsd
+ */
+ public Pair<Integer, Integer> getLayoutlibVersion();
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
index 32468a4..d17b800 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
@@ -34,7 +34,8 @@ public interface IMinPlatformToolsDependency {
* Since this is a required attribute in the XML schema, it can only happen when dealing
* with an invalid repository XML.
*/
- public static final int MIN_PLATFORM_TOOLS_REV_INVALID = 0;
+ public static final FullRevision MIN_PLATFORM_TOOLS_REV_INVALID =
+ new FullRevision(FullRevision.MISSING_MAJOR_REV);
/**
* The minimal revision of the tools package required by this package if > 0,
@@ -43,6 +44,6 @@ public interface IMinPlatformToolsDependency {
* This attribute is mandatory and should not be normally missing.
* It can only happen when dealing with an invalid repository XML.
*/
- public abstract int getMinPlatformToolsRevision();
+ public abstract FullRevision getMinPlatformToolsRevision();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
index 76cdd66..064f1d3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
@@ -31,12 +31,12 @@ public interface IMinToolsDependency {
* The value of {@link #getMinToolsRevision()} when the
* {@link SdkRepoConstants#NODE_MIN_TOOLS_REV} was not specified in the XML source.
*/
- public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
+ public static final FullRevision MIN_TOOLS_REV_NOT_SPECIFIED =
+ new FullRevision(FullRevision.MISSING_MAJOR_REV);
/**
* The minimal revision of the tools package required by this extra package if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
- public abstract int getMinToolsRevision();
-
+ public abstract FullRevision getMinToolsRevision();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
index a61fbea..9665528 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
@@ -25,13 +25,13 @@ import com.android.sdklib.AndroidVersion;
* A package that has this dependency can only be installed if a platform with at least the
* requested API level is present or installed at the same time.
* <p/>
- * Note that although this interface looks like {@link IPackageVersion}, it does not convey
- * the same semantic, that is {@link IPackageVersion} does <em>not</em> imply any dependency being
- * a limiting factor as far as installation is concerned.
+ * Note that although this interface looks like {@link IAndroidVersionProvider}, it does
+ * not convey the same semantic since {@link IAndroidVersionProvider} does <em>not</em>
+ * imply any dependency being a limiting factor as far as installation is concerned.
*/
public interface IPlatformDependency {
/** Returns the version of the platform dependency of this package. */
- AndroidVersion getVersion();
+ AndroidVersion getAndroidVersion();
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
index 5b582c1..ab9a31e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
@@ -1,133 +1,131 @@
-/*
- * Copyright (C) 2011 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.packages;
-
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.util.Pair;
-
-import org.w3c.dom.Node;
-
-import java.util.Properties;
-
-/**
- * Helper class to handle the layoutlib version provided by a package.
- */
-public class LayoutlibVersionMixin implements ILayoutlibVersion {
-
- /**
- * The layoutlib version.
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- */
- private final Pair<Integer, Integer> mLayoutlibVersion;
-
- /**
- * Parses an XML node to process the {@code <layoutlib>} element.
- *
- * The layoutlib element is new in the XSD rev 4, so we need to cope with it missing
- * in earlier XMLs.
- */
- public LayoutlibVersionMixin(Node pkgNode) {
-
- int api = LAYOUTLIB_API_NOT_SPECIFIED;
- int rev = LAYOUTLIB_REV_NOT_SPECIFIED;
-
- Node layoutlibNode = XmlParserUtils.getFirstChild(pkgNode, RepoConstants.NODE_LAYOUT_LIB);
-
- if (layoutlibNode != null) {
- api = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);
- rev = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);
- }
-
- mLayoutlibVersion = Pair.of(api, rev);
- }
-
- /**
- * Parses the layoutlib version optionally available in the given {@link Properties}.
- */
- public LayoutlibVersionMixin(Properties props) {
- int layoutlibApi = Integer.parseInt(
- Package.getProperty(props, PkgProps.LAYOUTLIB_API,
- Integer.toString(LAYOUTLIB_API_NOT_SPECIFIED)));
- int layoutlibRev = Integer.parseInt(
- Package.getProperty(props, PkgProps.LAYOUTLIB_REV,
- Integer.toString(LAYOUTLIB_REV_NOT_SPECIFIED)));
- mLayoutlibVersion = Pair.of(layoutlibApi, layoutlibRev);
- }
-
- /**
- * Stores the layoutlib version in the given {@link Properties}.
- */
- void saveProperties(Properties props) {
- if (mLayoutlibVersion.getFirst().intValue() != LAYOUTLIB_API_NOT_SPECIFIED) {
- props.setProperty(PkgProps.LAYOUTLIB_API, mLayoutlibVersion.getFirst().toString());
- props.setProperty(PkgProps.LAYOUTLIB_REV, mLayoutlibVersion.getSecond().toString());
- }
- }
-
- /**
- * Returns the layoutlib version.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
- * version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd and sdk-addon-2.xsd
- */
- @Override
- public Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof LayoutlibVersionMixin)) {
- return false;
- }
- LayoutlibVersionMixin other = (LayoutlibVersionMixin) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * Copyright (C) 2011 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.packages;
+
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.RepoConstants;
+import com.android.utils.Pair;
+
+import org.w3c.dom.Node;
+
+import java.util.Properties;
+
+/**
+ * Helper class to handle the layoutlib version provided by a package.
+ */
+public class LayoutlibVersionMixin implements ILayoutlibVersion {
+
+ /**
+ * The layoutlib version.
+ * The first integer is the API of layoublib, which should be > 0.
+ * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
+ * version isn't specified.
+ * The second integer is the revision for that given API. It is >= 0
+ * and works as a minor revision number, incremented for the same API level.
+ */
+ private final Pair<Integer, Integer> mLayoutlibVersion;
+
+ /**
+ * Parses an XML node to process the {@code <layoutlib>} element.
+ *
+ * The layoutlib element is new in the XSD rev 4, so we need to cope with it missing
+ * in earlier XMLs.
+ */
+ public LayoutlibVersionMixin(Node pkgNode) {
+
+ int api = LAYOUTLIB_API_NOT_SPECIFIED;
+ int rev = LAYOUTLIB_REV_NOT_SPECIFIED;
+
+ Node layoutlibNode =
+ PackageParserUtils.findChildElement(pkgNode, RepoConstants.NODE_LAYOUT_LIB);
+
+ if (layoutlibNode != null) {
+ api = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);
+ rev = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);
+ }
+
+ mLayoutlibVersion = Pair.of(api, rev);
+ }
+
+ /**
+ * Parses the layoutlib version optionally available in the given {@link Properties}.
+ */
+ public LayoutlibVersionMixin(Properties props) {
+ int layoutlibApi = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_API,
+ LAYOUTLIB_API_NOT_SPECIFIED);
+ int layoutlibRev = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_REV,
+ LAYOUTLIB_REV_NOT_SPECIFIED);
+ mLayoutlibVersion = Pair.of(layoutlibApi, layoutlibRev);
+ }
+
+ /**
+ * Stores the layoutlib version in the given {@link Properties}.
+ */
+ void saveProperties(Properties props) {
+ if (mLayoutlibVersion.getFirst().intValue() != LAYOUTLIB_API_NOT_SPECIFIED) {
+ props.setProperty(PkgProps.LAYOUTLIB_API, mLayoutlibVersion.getFirst().toString());
+ props.setProperty(PkgProps.LAYOUTLIB_REV, mLayoutlibVersion.getSecond().toString());
+ }
+ }
+
+ /**
+ * Returns the layoutlib version.
+ * <p/>
+ * The first integer is the API of layoublib, which should be > 0.
+ * It will be equal to {@link #LAYOUTLIB_API_NOT_SPECIFIED} (0) if the layoutlib
+ * version isn't specified.
+ * <p/>
+ * The second integer is the revision for that given API. It is >= 0
+ * and works as a minor revision number, incremented for the same API level.
+ *
+ * @since sdk-repository-4.xsd and sdk-addon-2.xsd
+ */
+ @Override
+ public Pair<Integer, Integer> getLayoutlibVersion() {
+ return mLayoutlibVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof LayoutlibVersionMixin)) {
+ return false;
+ }
+ LayoutlibVersionMixin other = (LayoutlibVersionMixin) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
new file mode 100755
index 0000000..ad33ed4
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.packages;
+
+import com.android.annotations.NonNull;
+
+
+/**
+ * Package revision number composed of a <em>single</em> major revision.
+ * <p/>
+ * Contrary to a {@link FullRevision}, a {@link MajorRevision} does not
+ * provide minor, micro and preview revision numbers -- these are all
+ * set to zero.
+ */
+public class MajorRevision extends FullRevision {
+
+ public MajorRevision(int major) {
+ super(major, 0, 0);
+ }
+
+ @Override
+ public String toString() {
+ return super.toShortString();
+ }
+
+ /**
+ * Parses a single-integer string and returns a new {@link MajorRevision} for it.
+ *
+ * @param revision A non-null revision to parse.
+ * @return A new non-null {@link MajorRevision}.
+ * @throws NumberFormatException if the parsing failed.
+ */
+ public static @NonNull MajorRevision parseRevision(@NonNull String revision)
+ throws NumberFormatException {
+
+ if (revision == null) {
+ throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$
+ }
+
+ return new MajorRevision(Integer.parseInt(revision.trim()));
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
new file mode 100755
index 0000000..4591297
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 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.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a package in an SDK repository that has a {@link MajorRevision},
+ * which is a single major revision number (not minor, micro or previews).
+ */
+public abstract class MajorRevisionPackage extends Package {
+
+ private final MajorRevision mRevision;
+
+ /**
+ * 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.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ MajorRevisionPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mRevision = new MajorRevision(
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_REVISION, 0));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MajorRevisionPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ MajorRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = MajorRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+ if (rev == null) {
+ rev = new MajorRevision(revision);
+ }
+
+ mRevision = rev;
+ }
+
+ /**
+ * 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.
+ */
+ @Override
+ public FullRevision getRevision() {
+ return mRevision;
+ }
+
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());
+ }
+
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ if (replacementPackage == null) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check they are the same item.
+ if (!sameItemAs(replacementPackage)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check revision number
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
index 99602c8..a608a3c 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
@@ -1,132 +1,139 @@
-/*
- * 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.packages;
-
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents an XML node in an SDK repository that has a min-tools-rev requirement.
- */
-public abstract class MinToolsPackage extends Package implements IMinToolsDependency {
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinToolsRevision;
-
- /**
- * 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.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode,
- SdkRepoConstants.NODE_MIN_TOOLS_REV,
- MIN_TOOLS_REV_NOT_SPECIFIED);
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public MinToolsPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- super(source, props, revision, license, description, descUrl,
- archiveOs, archiveArch, archiveOsPath);
-
- mMinToolsRevision = Integer.parseInt(
- getProperty(props,
- PkgProps.MIN_TOOLS_REV,
- Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
- }
-
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinToolsRevision() {
- return mMinToolsRevision;
- }
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- props.setProperty(PkgProps.MIN_TOOLS_REV,
- Integer.toString(getMinToolsRevision()));
- }
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + mMinToolsRevision;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof MinToolsPackage)) {
- return false;
- }
- MinToolsPackage other = (MinToolsPackage) obj;
- if (mMinToolsRevision != other.mMinToolsRevision) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents an XML node in an SDK repository that has a min-tools-rev requirement.
+ */
+public abstract class MinToolsPackage extends MajorRevisionPackage implements IMinToolsDependency {
+
+ /**
+ * The minimal revision of the tools package required by this extra package, if > 0,
+ * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final FullRevision mMinToolsRevision;
+
+ /**
+ * 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.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MinToolsPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.MIN_TOOLS_REV, null);
+
+ FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ mMinToolsRevision = rev;
+ }
+
+ /**
+ * The minimal revision of the tools package required by this extra package, if > 0,
+ * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public FullRevision getMinToolsRevision() {
+ return mMinToolsRevision;
+ }
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
+ props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof MinToolsPackage)) {
+ return false;
+ }
+ MinToolsPackage other = (MinToolsPackage) obj;
+ if (mMinToolsRevision == null) {
+ if (other.mMinToolsRevision != null) {
+ return false;
+ }
+ } else if (!mMinToolsRevision.equals(other.mMinToolsRevision)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
index 0e2b615..feab109 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
@@ -1,797 +1,823 @@
-/*
- * 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.packages;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkAddonSource;
-import com.android.sdklib.internal.repository.sources.SdkRepoSource;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * A {@link Package} is the base class for "something" that can be downloaded from
- * the SDK repository.
- * <p/>
- * A package has some attributes (revision, description) and a list of archives
- * which represent the downloadable bits.
- * <p/>
- * Packages are contained by a {@link SdkSource} (a download site).
- * <p/>
- * Derived classes must implement the {@link IDescription} methods.
- */
-public abstract class Package implements IDescription, Comparable<Package> {
-
- private final int mRevision;
- private final String mObsolete;
- private final String mLicense;
- private final String mDescription;
- private final String mDescUrl;
- private final String mReleaseNote;
- private final String mReleaseUrl;
- private final Archive[] mArchives;
- private final SdkSource mSource;
-
-
- // figure if we'll need to set the unix permissions
- private static final boolean sUsingUnixPerm =
- SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
- SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
-
-
- /**
- * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
- * differentiate between a package that is totally incompatible, and one that is the same item
- * but just not an update.
- * @see #canBeUpdatedBy(Package)
- */
- public static enum UpdateInfo {
- /** Means that the 2 packages are not the same thing */
- INCOMPATIBLE,
- /** Means that the 2 packages are the same thing but one does not upgrade the other.
- * </p>
- * TODO: this name is confusing. We need to dig deeper. */
- NOT_UPDATE,
- /** Means that the 2 packages are the same thing, and one is the upgrade of the other */
- UPDATE;
- }
-
- /**
- * 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.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- mSource = source;
- mRevision = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_REVISION, 0);
- mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
- mDescUrl = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
- mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
- mReleaseUrl = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
- mObsolete = XmlParserUtils.getOptionalXmlString(
- packageNode, SdkRepoConstants.NODE_OBSOLETE);
-
- mLicense = parseLicense(packageNode, licenses);
- mArchives = parseArchives(XmlParserUtils.getFirstChild(
- packageNode, SdkRepoConstants.NODE_ARCHIVES));
- }
-
- /**
- * Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * Properties from props are used first when possible, e.g. if props is non null.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public Package(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
-
- if (description == null) {
- description = "";
- }
- if (descUrl == null) {
- descUrl = "";
- }
-
- mRevision = Integer.parseInt(
- getProperty(props, PkgProps.PKG_REVISION, Integer.toString(revision)));
- mLicense = getProperty(props, PkgProps.PKG_LICENSE, license);
- mDescription = getProperty(props, PkgProps.PKG_DESC, description);
- mDescUrl = getProperty(props, PkgProps.PKG_DESC_URL, descUrl);
- mReleaseNote = getProperty(props, PkgProps.PKG_RELEASE_NOTE, ""); //$NON-NLS-1$
- mReleaseUrl = getProperty(props, PkgProps.PKG_RELEASE_URL, ""); //$NON-NLS-1$
- mObsolete = getProperty(props, PkgProps.PKG_OBSOLETE, null);
-
- // If source is null and we can find a source URL in the properties, generate
- // a dummy source just to store the URL. This allows us to easily remember where
- // a package comes from.
- String srcUrl = getProperty(props, PkgProps.PKG_SOURCE_URL, null);
- if (props != null && source == null && srcUrl != null) {
- // Both Addon and Extra packages can come from an addon source.
- // For Extras, we can tell by looking at the source URL.
- if (this instanceof AddonPackage ||
- ((this instanceof ExtraPackage) &&
- srcUrl.endsWith(SdkAddonConstants.URL_DEFAULT_FILENAME))) {
- source = new SdkAddonSource(srcUrl, null /*uiName*/);
- } else {
- source = new SdkRepoSource(srcUrl, null /*uiName*/);
- }
- }
- mSource = source;
-
- assert archiveOsPath != null;
- mArchives = initializeArchives(props, archiveOs, archiveArch, archiveOsPath);
- }
-
- /**
- * Called by the constructor to get the initial {@link #mArchives} array.
- * <p/>
- * This is invoked by the local-package constructor and allows mock testing
- * classes to override the archives created.
- * This is an <em>implementation</em> details and clients must <em>not</em>
- * rely on this.
- *
- * @return Always return a non-null array. The array may be empty.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Archive[] initializeArchives(
- Properties props,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- return new Archive[] {
- new Archive(this,
- props,
- archiveOs,
- archiveArch,
- archiveOsPath) };
- }
-
- /**
- * Utility method that returns a property from a {@link Properties} object.
- * Returns the default value if props is null or if the property is not defined.
- *
- * @param props The {@link Properties} to search into.
- * If null, the default value is returned.
- * @param propKey The name of the property. Must not be null.
- * @param defaultValue The default value to return if {@code props} is null or if the
- * key is not found. Can be null.
- * @return The string value of the given key in the properties, or null if the key
- * isn't found or if {@code props} is null.
- */
- static String getProperty(Properties props, String propKey, String defaultValue) {
- if (props == null) {
- return defaultValue;
- }
- return props.getProperty(propKey, defaultValue);
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be give the constructor that takes a {@link Properties} object.
- */
- public void saveProperties(Properties props) {
- props.setProperty(PkgProps.PKG_REVISION, Integer.toString(mRevision));
- if (mLicense != null && mLicense.length() > 0) {
- props.setProperty(PkgProps.PKG_LICENSE, mLicense);
- }
-
- if (mDescription != null && mDescription.length() > 0) {
- props.setProperty(PkgProps.PKG_DESC, mDescription);
- }
- if (mDescUrl != null && mDescUrl.length() > 0) {
- props.setProperty(PkgProps.PKG_DESC_URL, mDescUrl);
- }
-
- if (mReleaseNote != null && mReleaseNote.length() > 0) {
- props.setProperty(PkgProps.PKG_RELEASE_NOTE, mReleaseNote);
- }
- if (mReleaseUrl != null && mReleaseUrl.length() > 0) {
- props.setProperty(PkgProps.PKG_RELEASE_URL, mReleaseUrl);
- }
- if (mObsolete != null) {
- props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);
- }
-
- if (mSource != null) {
- props.setProperty(PkgProps.PKG_SOURCE_URL, mSource.getUrl());
- }
- }
-
- /**
- * Parses the uses-licence node of this package, if any, and returns the license
- * definition if there's one. Returns null if there's no uses-license element or no
- * license of this name defined.
- */
- private String parseLicense(Node packageNode, Map<String, String> licenses) {
- Node usesLicense = XmlParserUtils.getFirstChild(
- packageNode, SdkRepoConstants.NODE_USES_LICENSE);
- if (usesLicense != null) {
- Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);
- if (ref != null) {
- String licenseRef = ref.getNodeValue();
- return licenses.get(licenseRef);
- }
- }
- return null;
- }
-
- /**
- * Parses an XML node to process the <archives> element.
- * Always return a non-null array. The array may be empty.
- */
- private Archive[] parseArchives(Node archivesNode) {
- ArrayList<Archive> archives = new ArrayList<Archive>();
-
- if (archivesNode != null) {
- String nsUri = archivesNode.getNamespaceURI();
- for(Node child = archivesNode.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
-
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- SdkRepoConstants.NODE_ARCHIVE.equals(child.getLocalName())) {
- archives.add(parseArchive(child));
- }
- }
- }
-
- return archives.toArray(new Archive[archives.size()]);
- }
-
- /**
- * Parses one <archive> element from an <archives> container.
- */
- private Archive parseArchive(Node archiveNode) {
- Archive a = new Archive(
- this,
- (Os) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_OS,
- Os.values(), null),
- (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_ARCH,
- Arch.values(), Arch.ANY),
- XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
- XmlParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
- XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
- );
-
- return a;
- }
-
- /**
- * Returns the source that created (and owns) this package. Can be null.
- */
- public SdkSource getParentSource() {
- return mSource;
- }
-
- /**
- * Returns true if the package is deemed obsolete, that is it contains an
- * actual <code>&lt;obsolete&gt;</code> element.
- */
- public boolean isObsolete() {
- return mObsolete != null;
- }
-
- /**
- * 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. It is null if the element has not been specified in the repository XML.
- */
- public String getLicense() {
- return mLicense;
- }
-
- /**
- * 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.
- */
- public String getDescUrl() {
- return mDescUrl;
- }
-
- /**
- * Returns the optional release note for all packages (platform, add-on, tool, doc) or
- * for a lib. Can be empty but not null.
- */
- public String getReleaseNote() {
- return mReleaseNote;
- }
-
- /**
- * Returns the optional release note URL for all packages (platform, add-on, tool, doc).
- * Can be empty but not null.
- */
- public String getReleaseNoteUrl() {
- return mReleaseUrl;
- }
-
- /**
- * Returns the archives defined in this package.
- * Can be an empty array but not null.
- */
- public Archive[] getArchives() {
- return mArchives;
- }
-
- /**
- * Returns true if this package contains the exact given archive.
- * Important: This compares object references, not object equality.
- */
- public boolean hasArchive(Archive archive) {
- for (Archive a : mArchives) {
- if (a == archive) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns whether the {@link Package} has at least one {@link Archive} compatible with
- * the host platform.
- */
- public boolean hasCompatibleArchive() {
- for (Archive archive : mArchives) {
- if (archive.isCompatible()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns a short, reasonably unique string identifier that can be used
- * to identify this package when installing from the command-line interface.
- * {@code 'android list sdk'} will show these IDs and then in turn they can
- * be provided to {@code 'android update sdk --no-ui --filter'} to select
- * some specific packages.
- * <p/>
- * The identifiers must have the following properties: <br/>
- * - They must contain only simple alphanumeric characters. <br/>
- * - Commas, whitespace and any special character that could be obviously problematic
- * to a shell interface should be avoided (so dash/underscore are OK, but things
- * like colon, pipe or dollar should be avoided.) <br/>
- * - The name must be consistent across calls and reasonably unique for the package
- * type. Collisions can occur but should be rare. <br/>
- * - Different package types should have a clearly different name pattern. <br/>
- * - The revision number should not be included, as this would prevent updates
- * from being automated (which is the whole point.) <br/>
- * - It must remain reasonably human readable. <br/>
- * - If no such id can exist (for example for a local package that cannot be installed)
- * then an empty string should be returned. Don't return null.
- * <p/>
- * Important: This is <em>not</em> a strong unique identifier for the package.
- * If you need a strong unique identifier, you should use {@link #comparisonKey()}
- * and the {@link Comparable} interface.
- */
- public abstract String installId();
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * Should not be empty. Must never be null.
- * <p/>
- * Note that this is the "base" name for the package
- * with no specific revision nor API mentionned.
- * In contrast, {@link #getShortDescription()} should be used if you want more details
- * such as the package revision number or the API, if applicable.
- */
- public abstract String getListDescription();
-
- /**
- * Returns a short description for an {@link IDescription}.
- * Can be empty but not null.
- */
- @Override
- public abstract String getShortDescription();
-
- /**
- * Returns a long description for an {@link IDescription}.
- * Can be empty but not null.
- */
- @Override
- public String getLongDescription() {
- StringBuilder sb = new StringBuilder();
-
- String s = getDescription();
- if (s != null) {
- sb.append(s);
- }
- if (sb.length() > 0) {
- sb.append("\n");
- }
-
- sb.append(String.format("Revision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : ""));
-
- s = getDescUrl();
- if (s != null && s.length() > 0) {
- sb.append(String.format("\n\nMore information at %1$s", s));
- }
-
- s = getReleaseNote();
- if (s != null && s.length() > 0) {
- sb.append("\n\nRelease note:\n").append(s);
- }
-
- s = getReleaseNoteUrl();
- if (s != null && s.length() > 0) {
- sb.append("\nRelease note URL: ").append(s);
- }
-
- return sb.toString();
- }
-
- /**
- * A package is local (that is 'installed locally') if it contains a single
- * archive that is local. If not local, it's a remote package, only available
- * on a remote source for download and installation.
- */
- public boolean isLocal() {
- return mArchives.length == 1 && mArchives[0].isLocal();
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * 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. <br/>
- * 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.
- * <p/>
- * Note that the installer *will* create all directories returned here just before
- * installation so this method must not attempt to create them.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
-
- /**
- * Hook called right before an archive is installed. The archive has already
- * been downloaded successfully and will be installed in the directory specified by
- * <var>installFolder</var> when this call returns.
- * <p/>
- * The hook lets the package decide if installation of this specific archive should
- * be continue. The installer will still install the remaining packages if possible.
- * <p/>
- * The base implementation always return true.
- * <p/>
- * Note that the installer *will* create all directories specified by
- * {@link #getInstallFolder} just before installation, so they must not be
- * created here. This is also called before the previous install dir is removed
- * so the previous content is still there during upgrade.
- *
- * @param archive The archive that will be installed
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param installFolder The folder where the archive will be installed. Note that this
- * is <em>not</em> the folder where the archive was temporary
- * unzipped. The installFolder, if it exists, contains the old
- * archive that will soon be replaced by the new one.
- * @return True if installing this archive shall continue, false if it should be skipped.
- */
- public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
- String osSdkRoot, File installFolder) {
- // Nothing to do in base class.
- return true;
- }
-
- /**
- * Hook called right after a file has been unzipped (during an install).
- * <p/>
- * The base class implementation makes sure to properly adjust set executable
- * permission on Linux and MacOS system if the zip entry was marked as +x.
- *
- * @param archive The archive that is being installed.
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param fileOp The {@link IFileOp} used by the archive installer.
- * @param unzippedFile The file that has just been unzipped in the install temp directory.
- * @param zipEntry The {@link ZipArchiveEntry} that has just been unzipped.
- */
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
-
- // if needed set the permissions.
- if (sUsingUnixPerm && fileOp.isFile(unzippedFile)) {
- // get the mode and test if it contains the executable bit
- int mode = zipEntry.getUnixMode();
- if ((mode & 0111) != 0) {
- try {
- fileOp.setExecutablePermission(unzippedFile);
- } catch (IOException ignore) {}
- }
- }
-
- }
-
- /**
- * Hook called right after an archive has been installed.
- *
- * @param archive The archive that has been installed.
- * @param monitor The {@link ITaskMonitor} to display errors.
- * @param installFolder The folder where the archive was successfully installed.
- * Null if the installation failed, in case the archive needs to
- * do some cleanup after <code>preInstallHook</code>.
- */
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- // Nothing to do in base class.
- }
-
- /**
- * Returns whether the give package represents the same item as the current package.
- * <p/>
- * Two packages are considered the same if they represent the same thing, except for the
- * revision number.
- * @param pkg the package to compare
- * @return true if the item
- */
- public abstract boolean sameItemAs(Package pkg);
-
- /**
- * Computes whether the given package is a suitable update for the current package.
- * <p/>
- * An update is just that: a new package that supersedes the current one. If the new
- * package does not represent the same item or if it has the same or lower revision as the
- * current one, it's not an update.
- *
- * @param replacementPackage The potential replacement package.
- * @return One of the {@link UpdateInfo} values.
- *
- * @see #sameItemAs(Package)
- */
- public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
- if (replacementPackage == null) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check they are the same item.
- if (sameItemAs(replacementPackage) == false) {
- return UpdateInfo.INCOMPATIBLE;
- }
-
- // check revision number
- if (replacementPackage.getRevision() > this.getRevision()) {
- return UpdateInfo.UPDATE;
- }
-
- // not an upgrade but not incompatible either.
- return UpdateInfo.NOT_UPDATE;
- }
-
- /**
- * Returns an ordering like this: <br/>
- * - Tools <br/>
- * - Platform-Tools <br/>
- * - Docs. <br/>
- * - Platform n preview <br/>
- * - Platform n <br/>
- * - Platform n-1 <br/>
- * - Samples packages <br/>
- * - Add-on based on n preview <br/>
- * - Add-on based on n <br/>
- * - Add-on based on n-1 <br/>
- * - Extra packages <br/>
- * <p/>
- * Important: this must NOT be used to compare if two packages are the same thing.
- * This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.
- * <p/>
- * This {@link #compareTo(Package)} method is purely an implementation detail to
- * perform the right ordering of the packages in the list of available or installed packages.
- * <p/>
- * <em>Important</em>: Derived classes should consider overriding {@link #comparisonKey()}
- * instead of this method.
- */
- @Override
- public int compareTo(Package other) {
- String s1 = this.comparisonKey();
- String s2 = other.comparisonKey();
-
- return s1.compareTo(s2);
- }
-
- /**
- * Computes a comparison key for each package used by {@link #compareTo(Package)}.
- * The key is a string.
- * The base package class return a string that encodes the package type,
- * the revision number and the platform version, if applicable, in the form:
- * <pre>
- * t:N|v:NNNN.P|r:NNNN|
- * </pre>
- * All fields must start by a "letter colon" prefix and end with a vertical pipe (|, ASCII 124).
- * <p/>
- * The string format <em>may</em> change between releases and clients should not
- * store them outside of the session or expect them to be consistent between
- * different releases. They are purely an internal implementation details of the
- * {@link #compareTo(Package)} method.
- * <p/>
- * Derived classes should get the string from the super class and then append
- * or <em>insert</em> their own |-separated content.
- * For example an extra vendor name & path can be inserted before the revision
- * number, since it has more sorting weight.
- */
- protected String comparisonKey() {
-
- StringBuilder sb = new StringBuilder();
-
- sb.append("t:"); //$NON-NLS-1$
- if (this instanceof ToolPackage) {
- sb.append(0);
- } else if (this instanceof PlatformToolPackage) {
- sb.append(1);
- } else if (this instanceof DocPackage) {
- sb.append(2);
- } else if (this instanceof PlatformPackage) {
- sb.append(3);
- } else if (this instanceof SamplePackage) {
- sb.append(4);
- } else if (this instanceof SystemImagePackage) {
- sb.append(5);
- } else if (this instanceof AddonPackage) {
- sb.append(6);
- } else {
- // extras and everything else
- sb.append(9);
- }
-
-
- // We insert the package version here because it is more important
- // than the revision number. We want package version to be sorted
- // top-down, so we'll use 10k-api as the sorting key. The day we
- // get reach 10k APIs, we'll need to revisit this.
- sb.append("|v:"); //$NON-NLS-1$
- if (this instanceof IPackageVersion) {
- AndroidVersion v = ((IPackageVersion) this).getVersion();
-
- sb.append(String.format("%1$04d.%2$d", //$NON-NLS-1$
- 10000 - v.getApiLevel(),
- v.isPreview() ? 1 : 0
- ));
- }
-
- // Append revision number
- sb.append("|r:"); //$NON-NLS-1$
- sb.append(String.format("%1$04d", getRevision())); //$NON-NLS-1$
-
- sb.append('|');
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + Arrays.hashCode(mArchives);
- result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
- result = prime * result + mRevision;
- result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof Package)) {
- return false;
- }
- Package other = (Package) obj;
- if (!Arrays.equals(mArchives, other.mArchives)) {
- return false;
- }
- if (mObsolete == null) {
- if (other.mObsolete != null) {
- return false;
- }
- } else if (!mObsolete.equals(other.mObsolete)) {
- return false;
- }
- if (mRevision != other.mRevision) {
- return false;
- }
- if (mSource == null) {
- if (other.mSource != null) {
- return false;
- }
- } else if (!mSource.equals(other.mSource)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkAddonSource;
+import com.android.sdklib.internal.repository.sources.SdkRepoSource;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkAddonConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A {@link Package} is the base class for "something" that can be downloaded from
+ * the SDK repository.
+ * <p/>
+ * A package has some attributes (revision, description) and a list of archives
+ * which represent the downloadable bits.
+ * <p/>
+ * Packages are contained by a {@link SdkSource} (a download site).
+ * <p/>
+ * Derived classes must implement the {@link IDescription} methods.
+ */
+public abstract class Package implements IDescription, Comparable<Package> {
+
+ private final String mObsolete;
+ private final String mLicense;
+ private final String mDescription;
+ private final String mDescUrl;
+ private final String mReleaseNote;
+ private final String mReleaseUrl;
+ private final Archive[] mArchives;
+ private final SdkSource mSource;
+
+
+ // figure if we'll need to set the unix permissions
+ private static final boolean sUsingUnixPerm =
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ||
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX;
+
+
+ /**
+ * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
+ * differentiate between a package that is totally incompatible, and one that is the same item
+ * but just not an update.
+ * @see #canBeUpdatedBy(Package)
+ */
+ public static enum UpdateInfo {
+ /** Means that the 2 packages are not the same thing */
+ INCOMPATIBLE,
+ /** Means that the 2 packages are the same thing but one does not upgrade the other.
+ * </p>
+ * TODO: this name is confusing. We need to dig deeper. */
+ NOT_UPDATE,
+ /** Means that the 2 packages are the same thing, and one is the upgrade of the other */
+ UPDATE;
+ }
+
+ /**
+ * 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.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
+ mSource = source;
+ mDescription =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
+ mDescUrl =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
+ mReleaseNote =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
+ mReleaseUrl =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
+ mObsolete =
+ PackageParserUtils.getOptionalXmlString(packageNode, SdkRepoConstants.NODE_OBSOLETE);
+
+ mLicense = parseLicense(packageNode, licenses);
+ mArchives = parseArchives(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_ARCHIVES));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public Package(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+
+ if (description == null) {
+ description = "";
+ }
+ if (descUrl == null) {
+ descUrl = "";
+ }
+
+ mLicense = getProperty(props, PkgProps.PKG_LICENSE, license);
+ mDescription = getProperty(props, PkgProps.PKG_DESC, description);
+ mDescUrl = getProperty(props, PkgProps.PKG_DESC_URL, descUrl);
+ mReleaseNote = getProperty(props, PkgProps.PKG_RELEASE_NOTE, ""); //$NON-NLS-1$
+ mReleaseUrl = getProperty(props, PkgProps.PKG_RELEASE_URL, ""); //$NON-NLS-1$
+ mObsolete = getProperty(props, PkgProps.PKG_OBSOLETE, null);
+
+ // If source is null and we can find a source URL in the properties, generate
+ // a dummy source just to store the URL. This allows us to easily remember where
+ // a package comes from.
+ String srcUrl = getProperty(props, PkgProps.PKG_SOURCE_URL, null);
+ if (props != null && source == null && srcUrl != null) {
+ // Both Addon and Extra packages can come from an addon source.
+ // For Extras, we can tell by looking at the source URL.
+ if (this instanceof AddonPackage ||
+ ((this instanceof ExtraPackage) &&
+ srcUrl.endsWith(SdkAddonConstants.URL_DEFAULT_FILENAME))) {
+ source = new SdkAddonSource(srcUrl, null /*uiName*/);
+ } else {
+ source = new SdkRepoSource(srcUrl, null /*uiName*/);
+ }
+ }
+ mSource = source;
+
+ assert archiveOsPath != null;
+ mArchives = initializeArchives(props, archiveOs, archiveArch, archiveOsPath);
+ }
+
+ /**
+ * Called by the constructor to get the initial {@link #mArchives} array.
+ * <p/>
+ * This is invoked by the local-package constructor and allows mock testing
+ * classes to override the archives created.
+ * This is an <em>implementation</em> details and clients must <em>not</em>
+ * rely on this.
+ *
+ * @return Always return a non-null array. The array may be empty.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Archive[] initializeArchives(
+ Properties props,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new Archive[] {
+ new Archive(this,
+ props,
+ archiveOs,
+ archiveArch,
+ archiveOsPath) };
+ }
+
+ /**
+ * Utility method that returns a property from a {@link Properties} object.
+ * Returns the default value if props is null or if the property is not defined.
+ *
+ * @param props The {@link Properties} to search into.
+ * If null, the default value is returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the
+ * key is not found. Can be null.
+ * @return The string value of the given key in the properties, or null if the key
+ * isn't found or if {@code props} is null.
+ */
+ @Nullable
+ static String getProperty(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ @Nullable String defaultValue) {
+ if (props == null) {
+ return defaultValue;
+ }
+ return props.getProperty(propKey, defaultValue);
+ }
+
+ /**
+ * Utility method that returns an integer property from a {@link Properties} object.
+ * Returns the default value if props is null or if the property is not defined or
+ * cannot be parsed to an integer.
+ *
+ * @param props The {@link Properties} to search into.
+ * If null, the default value is returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the
+ * key is not found. Can be null.
+ * @return The integer value of the given key in the properties, or the {@code defaultValue}.
+ */
+ static int getPropertyInt(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ int defaultValue) {
+ String s = props != null ? props.getProperty(propKey, null) : null;
+ if (s != null) {
+ try {
+ return Integer.parseInt(s);
+ } catch (Exception ignore) {}
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be give the constructor that takes a {@link Properties} object.
+ */
+ public void saveProperties(Properties props) {
+ if (mLicense != null && mLicense.length() > 0) {
+ props.setProperty(PkgProps.PKG_LICENSE, mLicense);
+ }
+ if (mDescription != null && mDescription.length() > 0) {
+ props.setProperty(PkgProps.PKG_DESC, mDescription);
+ }
+ if (mDescUrl != null && mDescUrl.length() > 0) {
+ props.setProperty(PkgProps.PKG_DESC_URL, mDescUrl);
+ }
+
+ if (mReleaseNote != null && mReleaseNote.length() > 0) {
+ props.setProperty(PkgProps.PKG_RELEASE_NOTE, mReleaseNote);
+ }
+ if (mReleaseUrl != null && mReleaseUrl.length() > 0) {
+ props.setProperty(PkgProps.PKG_RELEASE_URL, mReleaseUrl);
+ }
+ if (mObsolete != null) {
+ props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);
+ }
+ if (mSource != null) {
+ props.setProperty(PkgProps.PKG_SOURCE_URL, mSource.getUrl());
+ }
+ }
+
+ /**
+ * Parses the uses-licence node of this package, if any, and returns the license
+ * definition if there's one. Returns null if there's no uses-license element or no
+ * license of this name defined.
+ */
+ private String parseLicense(Node packageNode, Map<String, String> licenses) {
+ Node usesLicense =
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_USES_LICENSE);
+ if (usesLicense != null) {
+ Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);
+ if (ref != null) {
+ String licenseRef = ref.getNodeValue();
+ return licenses.get(licenseRef);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parses an XML node to process the <archives> element.
+ * Always return a non-null array. The array may be empty.
+ */
+ private Archive[] parseArchives(Node archivesNode) {
+ ArrayList<Archive> archives = new ArrayList<Archive>();
+
+ if (archivesNode != null) {
+ String nsUri = archivesNode.getNamespaceURI();
+ for(Node child = archivesNode.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI()) &&
+ SdkRepoConstants.NODE_ARCHIVE.equals(child.getLocalName())) {
+ archives.add(parseArchive(child));
+ }
+ }
+ }
+
+ return archives.toArray(new Archive[archives.size()]);
+ }
+
+ /**
+ * Parses one <archive> element from an <archives> container.
+ */
+ private Archive parseArchive(Node archiveNode) {
+ Archive a = new Archive(
+ this,
+ (Os) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.ATTR_OS, Os.values(), null),
+ (Arch) PackageParserUtils.getEnumAttribute(
+ archiveNode, SdkRepoConstants.ATTR_ARCH, Arch.values(), Arch.ANY),
+ PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
+ PackageParserUtils.getXmlLong (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
+ PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
+ );
+
+ return a;
+ }
+
+ /**
+ * Returns the source that created (and owns) this package. Can be null.
+ */
+ public SdkSource getParentSource() {
+ return mSource;
+ }
+
+ /**
+ * Returns true if the package is deemed obsolete, that is it contains an
+ * actual <code>&lt;obsolete&gt;</code> element.
+ */
+ public boolean isObsolete() {
+ return mObsolete != null;
+ }
+
+ /**
+ * 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 abstract FullRevision getRevision();
+
+ /**
+ * Returns the optional description for all packages (platform, add-on, tool, doc) or
+ * for a lib. It is null if the element has not been specified in the repository XML.
+ */
+ public String getLicense() {
+ return mLicense;
+ }
+
+ /**
+ * 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.
+ */
+ public String getDescUrl() {
+ return mDescUrl;
+ }
+
+ /**
+ * Returns the optional release note for all packages (platform, add-on, tool, doc) or
+ * for a lib. Can be empty but not null.
+ */
+ public String getReleaseNote() {
+ return mReleaseNote;
+ }
+
+ /**
+ * Returns the optional release note URL for all packages (platform, add-on, tool, doc).
+ * Can be empty but not null.
+ */
+ public String getReleaseNoteUrl() {
+ return mReleaseUrl;
+ }
+
+ /**
+ * Returns the archives defined in this package.
+ * Can be an empty array but not null.
+ */
+ public Archive[] getArchives() {
+ return mArchives;
+ }
+
+ /**
+ * Returns true if this package contains the exact given archive.
+ * Important: This compares object references, not object equality.
+ */
+ public boolean hasArchive(Archive archive) {
+ for (Archive a : mArchives) {
+ if (a == archive) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the {@link Package} has at least one {@link Archive} compatible with
+ * the host platform.
+ */
+ public boolean hasCompatibleArchive() {
+ for (Archive archive : mArchives) {
+ if (archive.isCompatible()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a short, reasonably unique string identifier that can be used
+ * to identify this package when installing from the command-line interface.
+ * {@code 'android list sdk'} will show these IDs and then in turn they can
+ * be provided to {@code 'android update sdk --no-ui --filter'} to select
+ * some specific packages.
+ * <p/>
+ * The identifiers must have the following properties: <br/>
+ * - They must contain only simple alphanumeric characters. <br/>
+ * - Commas, whitespace and any special character that could be obviously problematic
+ * to a shell interface should be avoided (so dash/underscore are OK, but things
+ * like colon, pipe or dollar should be avoided.) <br/>
+ * - The name must be consistent across calls and reasonably unique for the package
+ * type. Collisions can occur but should be rare. <br/>
+ * - Different package types should have a clearly different name pattern. <br/>
+ * - The revision number should not be included, as this would prevent updates
+ * from being automated (which is the whole point.) <br/>
+ * - It must remain reasonably human readable. <br/>
+ * - If no such id can exist (for example for a local package that cannot be installed)
+ * then an empty string should be returned. Don't return null.
+ * <p/>
+ * Important: This is <em>not</em> a strong unique identifier for the package.
+ * If you need a strong unique identifier, you should use {@link #comparisonKey()}
+ * and the {@link Comparable} interface.
+ */
+ public abstract String installId();
+
+ /**
+ * Returns the short description of the source, if not null.
+ * Otherwise returns the default Object toString result.
+ * <p/>
+ * This is mostly helpful for debugging.
+ * For UI display, use the {@link IDescription} interface.
+ */
+ @Override
+ public String toString() {
+ String s = getShortDescription();
+ if (s != null) {
+ return s;
+ }
+ return super.toString();
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * Should not be empty. Must never be null.
+ * <p/>
+ * Note that this is the "base" name for the package
+ * with no specific revision nor API mentionned.
+ * In contrast, {@link #getShortDescription()} should be used if you want more details
+ * such as the package revision number or the API, if applicable.
+ */
+ public abstract String getListDescription();
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ * Can be empty but not null.
+ */
+ @Override
+ public abstract String getShortDescription();
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ * Can be empty but not null.
+ */
+ @Override
+ public String getLongDescription() {
+ StringBuilder sb = new StringBuilder();
+
+ String s = getDescription();
+ if (s != null) {
+ sb.append(s);
+ }
+ if (sb.length() > 0) {
+ sb.append("\n");
+ }
+
+ sb.append(String.format("Revision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : ""));
+
+ s = getDescUrl();
+ if (s != null && s.length() > 0) {
+ sb.append(String.format("\n\nMore information at %1$s", s));
+ }
+
+ s = getReleaseNote();
+ if (s != null && s.length() > 0) {
+ sb.append("\n\nRelease note:\n").append(s);
+ }
+
+ s = getReleaseNoteUrl();
+ if (s != null && s.length() > 0) {
+ sb.append("\nRelease note URL: ").append(s);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * A package is local (that is 'installed locally') if it contains a single
+ * archive that is local. If not local, it's a remote package, only available
+ * on a remote source for download and installation.
+ */
+ public boolean isLocal() {
+ return mArchives.length == 1 && mArchives[0].isLocal();
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * 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. <br/>
+ * 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.
+ * <p/>
+ * Note that the installer *will* create all directories returned here just before
+ * installation so this method must not attempt to create them.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
+
+ /**
+ * Hook called right before an archive is installed. The archive has already
+ * been downloaded successfully and will be installed in the directory specified by
+ * <var>installFolder</var> when this call returns.
+ * <p/>
+ * The hook lets the package decide if installation of this specific archive should
+ * be continue. The installer will still install the remaining packages if possible.
+ * <p/>
+ * The base implementation always return true.
+ * <p/>
+ * Note that the installer *will* create all directories specified by
+ * {@link #getInstallFolder} just before installation, so they must not be
+ * created here. This is also called before the previous install dir is removed
+ * so the previous content is still there during upgrade.
+ *
+ * @param archive The archive that will be installed
+ * @param monitor The {@link ITaskMonitor} to display errors.
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param installFolder The folder where the archive will be installed. Note that this
+ * is <em>not</em> the folder where the archive was temporary
+ * unzipped. The installFolder, if it exists, contains the old
+ * archive that will soon be replaced by the new one.
+ * @return True if installing this archive shall continue, false if it should be skipped.
+ */
+ public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
+ String osSdkRoot, File installFolder) {
+ // Nothing to do in base class.
+ return true;
+ }
+
+ /**
+ * Hook called right after a file has been unzipped (during an install).
+ * <p/>
+ * The base class implementation makes sure to properly adjust set executable
+ * permission on Linux and MacOS system if the zip entry was marked as +x.
+ *
+ * @param archive The archive that is being installed.
+ * @param monitor The {@link ITaskMonitor} to display errors.
+ * @param fileOp The {@link IFileOp} used by the archive installer.
+ * @param unzippedFile The file that has just been unzipped in the install temp directory.
+ * @param zipEntry The {@link ZipArchiveEntry} that has just been unzipped.
+ */
+ public void postUnzipFileHook(
+ Archive archive,
+ ITaskMonitor monitor,
+ IFileOp fileOp,
+ File unzippedFile,
+ ZipArchiveEntry zipEntry) {
+
+ // if needed set the permissions.
+ if (sUsingUnixPerm && fileOp.isFile(unzippedFile)) {
+ // get the mode and test if it contains the executable bit
+ int mode = zipEntry.getUnixMode();
+ if ((mode & 0111) != 0) {
+ try {
+ fileOp.setExecutablePermission(unzippedFile);
+ } catch (IOException ignore) {}
+ }
+ }
+
+ }
+
+ /**
+ * Hook called right after an archive has been installed.
+ *
+ * @param archive The archive that has been installed.
+ * @param monitor The {@link ITaskMonitor} to display errors.
+ * @param installFolder The folder where the archive was successfully installed.
+ * Null if the installation failed, in case the archive needs to
+ * do some cleanup after <code>preInstallHook</code>.
+ */
+ public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
+ // Nothing to do in base class.
+ }
+
+ /**
+ * Returns whether the give package represents the same item as the current package.
+ * <p/>
+ * Two packages are considered the same if they represent the same thing, except for the
+ * revision number.
+ * @param pkg the package to compare.
+ * @return true if the item as equivalent.
+ */
+ public abstract boolean sameItemAs(Package pkg);
+
+ /**
+ * Computes whether the given package is a suitable update for the current package.
+ * <p/>
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package does not represent the same item or if it has the same or lower revision as the
+ * current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return One of the {@link UpdateInfo} values.
+ *
+ * @see #sameItemAs(Package)
+ */
+ public abstract UpdateInfo canBeUpdatedBy(Package replacementPackage);
+
+ /**
+ * Returns an ordering <b>suitable for display</b> like this: <br/>
+ * - Tools <br/>
+ * - Platform-Tools <br/>
+ * - Docs. <br/>
+ * - Platform n preview <br/>
+ * - Platform n <br/>
+ * - Platform n-1 <br/>
+ * - Samples packages <br/>
+ * - Add-on based on n preview <br/>
+ * - Add-on based on n <br/>
+ * - Add-on based on n-1 <br/>
+ * - Extra packages <br/>
+ * <p/>
+ * Important: this must NOT be used to compare if two packages are the same thing.
+ * This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.
+ * <p/>
+ * The order done here is suitable for display, and this may not be the appropriate
+ * order when comparing whether packages are equal or of greater revision -- if you need
+ * to compare revisions, then use {@link #getRevision()}{@code .compareTo(rev)} directly.
+ * <p/>
+ * This {@link #compareTo(Package)} method is purely an implementation detail to
+ * perform the right ordering of the packages in the list of available or installed packages.
+ * <p/>
+ * <em>Important</em>: Derived classes should consider overriding {@link #comparisonKey()}
+ * instead of this method.
+ */
+ @Override
+ public int compareTo(Package other) {
+ String s1 = this.comparisonKey();
+ String s2 = other.comparisonKey();
+
+ int r = s1.compareTo(s2);
+ return r;
+ }
+
+ /**
+ * Computes a comparison key for each package used by {@link #compareTo(Package)}.
+ * The key is a string.
+ * The base package class return a string that encodes the package type,
+ * the revision number and the platform version, if applicable, in the form:
+ * <pre>
+ * t:N|v:NNNN.P|r:NNNN|
+ * </pre>
+ * All fields must start by a "letter colon" prefix and end with a vertical pipe (|, ASCII 124).
+ * <p/>
+ * The string format <em>may</em> change between releases and clients should not
+ * store them outside of the session or expect them to be consistent between
+ * different releases. They are purely an internal implementation details of the
+ * {@link #compareTo(Package)} method.
+ * <p/>
+ * Derived classes should get the string from the super class and then append
+ * or <em>insert</em> their own |-separated content.
+ * For example an extra vendor name & path can be inserted before the revision
+ * number, since it has more sorting weight.
+ */
+ protected String comparisonKey() {
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("t:"); //$NON-NLS-1$
+ if (this instanceof ToolPackage) {
+ sb.append(0);
+ } else if (this instanceof PlatformToolPackage) {
+ sb.append(1);
+ } else if (this instanceof DocPackage) {
+ sb.append(2);
+ } else if (this instanceof PlatformPackage) {
+ sb.append(3);
+ } else if (this instanceof SamplePackage) {
+ sb.append(4);
+ } else if (this instanceof SystemImagePackage) {
+ sb.append(5);
+ } else if (this instanceof AddonPackage) {
+ sb.append(6);
+ } else {
+ // extras and everything else
+ sb.append(9);
+ }
+
+
+ // We insert the package version here because it is more important
+ // than the revision number.
+ // In the list display, we want package version to be sorted
+ // top-down, so we'll use 10k-api as the sorting key. The day we
+ // reach 10k APIs, we'll need to revisit this.
+ sb.append("|v:"); //$NON-NLS-1$
+ if (this instanceof IAndroidVersionProvider) {
+ AndroidVersion v = ((IAndroidVersionProvider) this).getAndroidVersion();
+
+ sb.append(String.format("%1$04d.%2$d", //$NON-NLS-1$
+ 10000 - v.getApiLevel(),
+ v.isPreview() ? 1 : 0
+ ));
+ }
+
+ // Append revision number
+ sb.append("|r:"); //$NON-NLS-1$
+ FullRevision rev = getRevision();
+ sb.append(rev.getMajor()).append('.')
+ .append(rev.getMinor()).append('.')
+ .append(rev.getMicro()).append('.');
+ // Hack: When comparing packages for installation purposes, we want to treat
+ // "final releases" packages as more important than rc/preview packages.
+ // However like for the API level above, when sorting for list display purposes
+ // we want the final release package listed before its rc/preview packages.
+ if (rev.isPreview()) {
+ sb.append(rev.getPreview());
+ } else {
+ sb.append('0'); // 0=Final (!preview), to make "18.0" < "18.1" (18 Final < 18 RC1)
+ }
+
+ sb.append('|');
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(mArchives);
+ result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
+ result = prime * result + getRevision().hashCode();
+ result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Package)) {
+ return false;
+ }
+ Package other = (Package) obj;
+ if (!Arrays.equals(mArchives, other.mArchives)) {
+ return false;
+ }
+ if (mObsolete == null) {
+ if (other.mObsolete != null) {
+ return false;
+ }
+ } else if (!mObsolete.equals(other.mObsolete)) {
+ return false;
+ }
+ if (!getRevision().equals(other.getRevision())) {
+ return false;
+ }
+ if (mSource == null) {
+ if (other.mSource != null) {
+ return false;
+ }
+ } else if (!mSource.equals(other.mSource)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
index e4f2419..a6986e1 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
@@ -1,135 +1,180 @@
-/*
- * 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 org.w3c.dom.Node;
-
-/**
- * Misc utilities to help extracting elements and attributes out of an XML document.
- */
-public class XmlParserUtils {
-
- /**
- * Returns the first child element with the given XML local name.
- * If xmlLocalName is null, returns the very first child element.
- */
- public static Node getFirstChild(Node node, String xmlLocalName) {
-
- String nsUri = node.getNamespaceURI();
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
- return child;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns an empty string whether the element is missing or empty,
- * so you can't tell the difference.
- * <p/>
- * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns an empty string whether the element
- * is missing or empty, so you can't tell the difference.
- */
- public static String getXmlString(Node node, String xmlLocalName) {
- Node child = getFirstChild(node, xmlLocalName);
-
- return child == null ? "" : child.getTextContent(); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as a string.
- * Returns null when the element is missing, so you can tell between a missing element
- * and an empty one.
- * <p/>
- * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the
- * element is missing versus empty.
- *
- * @param node The XML <em>parent</em> node to parse.
- * @param xmlLocalName The XML local name to find in the parent node.
- * @return The text content of the element. Returns null when the element is missing.
- * Returns an empty string whether the element is present but empty.
- */
- public static String getOptionalXmlString(Node node, String xmlLocalName) {
- Node child = getFirstChild(node, xmlLocalName);
-
- return child == null ? null : child.getTextContent(); //$NON-NLS-1$
- }
-
- /**
- * Retrieves the value of that XML element as an integer.
- * Returns the default value when the element is missing or is not an integer.
- */
- public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {
- String s = getXmlString(node, xmlLocalName);
- try {
- return Integer.parseInt(s);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Retrieves the value of that XML element as a long.
- * Returns the default value when the element is missing or is not an integer.
- */
- public 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.
- *
- * Returns defaultValue if the attribute does not exist or its value does not match
- * the given enum values.
- */
- public static Object getEnumAttribute(
- Node archiveNode,
- String attrName,
- Object[] values,
- Object defaultValue) {
-
- Node attr = archiveNode.getAttributes().getNamedItem(attrName);
- if (attr != null) {
- String found = attr.getNodeValue();
- for (Object value : values) {
- if (value.toString().equalsIgnoreCase(found)) {
- return value;
- }
- }
- }
-
- return defaultValue;
- }
-
-}
+/*
+ * 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.packages;
+
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+/**
+ * Misc utilities to help extracting elements and attributes out of an XML document.
+ */
+public class PackageParserUtils {
+
+ /**
+ * Parses a full revision element such as <revision> or <min-tools-rev>.
+ * This supports both the single-integer format as well as the full revision
+ * format with major/minor/micro/preview sub-elements.
+ *
+ * @param revisionNode The node to parse.
+ * @return A new {@link FullRevision}. If parsing failed, major is set to
+ * {@link FullRevision#MISSING_MAJOR_REV}.
+ */
+ public static FullRevision parseFullRevisionElement(Node revisionNode) {
+ // This needs to support two modes:
+ // - For repository XSD >= 7, <revision> contains sub-elements such as <major> or <minor>.
+ // - Otherwise for repository XSD < 7, <revision> contains an integer.
+ // The <major> element is mandatory, so it's easy to distinguish between both cases.
+ int major = FullRevision.MISSING_MAJOR_REV,
+ minor = FullRevision.IMPLICIT_MINOR_REV,
+ micro = FullRevision.IMPLICIT_MICRO_REV,
+ preview = FullRevision.NOT_A_PREVIEW;
+
+ if (revisionNode != null) {
+ if (PackageParserUtils.findChildElement(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV) != null) {
+ // <revision> has a <major> sub-element, so it's a repository XSD >= 7.
+ major = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);
+ minor = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);
+ micro = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);
+ preview = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_PREVIEW, FullRevision.NOT_A_PREVIEW);
+ } else {
+ try {
+ String majorStr = revisionNode.getTextContent().trim();
+ major = Integer.parseInt(majorStr);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ return new FullRevision(major, minor, micro, preview);
+ }
+
+ /**
+ * Returns the first child element with the given XML local name.
+ * If xmlLocalName is null, returns the very first child element.
+ */
+ public static Node findChildElement(Node node, String xmlLocalName) {
+ if (node != null) {
+ String nsUri = node.getNamespaceURI();
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+ if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {
+ return child;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the value of that XML element as a string.
+ * Returns an empty string whether the element is missing or empty,
+ * so you can't tell the difference.
+ * <p/>
+ * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the
+ * element is missing versus empty.
+ *
+ * @param node The XML <em>parent</em> node to parse.
+ * @param xmlLocalName The XML local name to find in the parent node.
+ * @return The text content of the element. Returns an empty string whether the element
+ * is missing or empty, so you can't tell the difference.
+ */
+ public static String getXmlString(Node node, String xmlLocalName) {
+ Node child = findChildElement(node, xmlLocalName);
+
+ return child == null ? "" : child.getTextContent(); //$NON-NLS-1$
+ }
+
+ /**
+ * Retrieves the value of that XML element as a string.
+ * Returns null when the element is missing, so you can tell between a missing element
+ * and an empty one.
+ * <p/>
+ * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the
+ * element is missing versus empty.
+ *
+ * @param node The XML <em>parent</em> node to parse.
+ * @param xmlLocalName The XML local name to find in the parent node.
+ * @return The text content of the element. Returns null when the element is missing.
+ * Returns an empty string whether the element is present but empty.
+ */
+ public static String getOptionalXmlString(Node node, String xmlLocalName) {
+ Node child = findChildElement(node, xmlLocalName);
+
+ return child == null ? null : child.getTextContent(); //$NON-NLS-1$
+ }
+
+ /**
+ * Retrieves the value of that XML element as an integer.
+ * Returns the default value when the element is missing or is not an integer.
+ */
+ public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {
+ String s = getXmlString(node, xmlLocalName);
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Retrieves the value of that XML element as a long.
+ * Returns the default value when the element is missing or is not an integer.
+ */
+ public 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.
+ *
+ * Returns defaultValue if the attribute does not exist or its value does not match
+ * the given enum values.
+ */
+ public static Object getEnumAttribute(
+ Node archiveNode,
+ String attrName,
+ Object[] values,
+ Object defaultValue) {
+
+ Node attr = archiveNode.getAttributes().getNamedItem(attrName);
+ if (attr != null) {
+ String found = attr.getNodeValue();
+ for (Object value : values) {
+ if (value.toString().equalsIgnoreCase(found)) {
+ return value;
+ }
+ }
+ }
+
+ return defaultValue;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
index 391c32e..71d91ef 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
@@ -1,345 +1,353 @@
-/*
- * 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.packages;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.util.Pair;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a platform XML node in an SDK repository.
- */
-public class PlatformPackage extends MinToolsPackage implements IPackageVersion, ILayoutlibVersion {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- /** The version, a string, for platform packages. */
- private final String mVersionName;
-
- /** The ABI of the system-image included in this platform. Can be null but not empty. */
- private final String mIncludedAbi;
-
- /** The helper handling the layoutlib version. */
- private final LayoutlibVersionMixin mLayoutlibVersion;
-
- /**
- * Creates a new platform package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public PlatformPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mVersionName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);
-
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.length() == 0) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mIncludedAbi = XmlParserUtils.getOptionalXmlString(
- packageNode, SdkRepoConstants.NODE_ABI_INCLUDED);
-
- mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
- }
-
- /**
- * Creates a new platform package based on an actual {@link IAndroidTarget} (which
- * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(IAndroidTarget target, Properties props) {
- return new PlatformPackage(target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(IAndroidTarget target, Properties props) {
- this(null /*source*/, target, props);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
- super( source, //source
- props, //properties
- target.getRevision(), //revision
- null, //license
- target.getDescription(), //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- target.getLocation() //archiveOsPath
- );
-
- mVersion = target.getVersion();
- mVersionName = target.getVersionName();
- mLayoutlibVersion = new LayoutlibVersionMixin(props);
- mIncludedAbi = props == null ? null : props.getProperty(PkgProps.PLATFORM_INCLUDED_ABI);
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- mLayoutlibVersion.saveProperties(props);
-
- if (mVersionName != null) {
- props.setProperty(PkgProps.PLATFORM_VERSION, mVersionName);
- }
-
- if (mIncludedAbi != null) {
- props.setProperty(PkgProps.PLATFORM_INCLUDED_ABI, mIncludedAbi);
- }
-
- }
-
- /** Returns the version, a string, for platform packages. */
- public String getVersionName() {
- return mVersionName;
- }
-
- /** Returns the package version, for platform, add-on and doc packages. */
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- /**
- * Returns the ABI of the system-image included in this platform.
- *
- * @return Null if the platform does not include any system-image.
- * Otherwise should be a valid non-empty ABI string (e.g. "x86" or "armeabi-v7a").
- */
- public String getIncludedAbi() {
- return mIncludedAbi;
- }
-
- /**
- * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
- * <p/>
- * The first integer is the API of layoublib, which should be > 0.
- * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
- * if the layoutlib version isn't specified.
- * <p/>
- * The second integer is the revision for that given API. It is >= 0
- * and works as a minor revision number, incremented for the same API level.
- *
- * @since sdk-repository-4.xsd
- */
- @Override
- public Pair<Integer, Integer> getLayoutlibVersion() {
- return mLayoutlibVersion.getLayoutlibVersion();
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For platforms, we use "android-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "android-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String s;
-
- if (mVersion.isPreview()) {
- s = String.format("SDK Platform Android %1$s Preview%2$s",
- getVersionName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- } else {
- s = String.format("SDK Platform Android %1$s%2$s",
- getVersionName(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- }
-
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String s;
-
- if (mVersion.isPreview()) {
- s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
- getVersionName(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- } else {
- s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",
- getVersionName(),
- mVersion.getApiLevel(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
- }
-
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * 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.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // First find if this platform is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (target.isPlatform() && target.getVersion().equals(mVersion)) {
- return new File(target.getLocation());
- }
- }
-
- File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);
- File folder = new File(platforms,
- String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$
-
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof PlatformPackage) {
- PlatformPackage newPkg = (PlatformPackage)pkg;
-
- // check they are the same version.
- return newPkg.getVersion().equals(this.getVersion());
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result +
- ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- result = prime * result + ((mVersionName == null) ? 0 : mVersionName.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof PlatformPackage)) {
- return false;
- }
- PlatformPackage other = (PlatformPackage) obj;
- if (mLayoutlibVersion == null) {
- if (other.mLayoutlibVersion != null) {
- return false;
- }
- } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- if (mVersionName == null) {
- if (other.mVersionName != null) {
- return false;
- }
- } else if (!mVersionName.equals(other.mVersionName)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.utils.Pair;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a platform XML node in an SDK repository.
+ */
+public class PlatformPackage extends MinToolsPackage
+ implements IAndroidVersionProvider, ILayoutlibVersion {
+
+ /** The package version, for platform, add-on and doc packages. */
+ private final AndroidVersion mVersion;
+
+ /** The version, a string, for platform packages. */
+ private final String mVersionName;
+
+ /** The ABI of the system-image included in this platform. Can be null but not empty. */
+ private final String mIncludedAbi;
+
+ /** The helper handling the layoutlib version. */
+ private final LayoutlibVersionMixin mLayoutlibVersion;
+
+ /**
+ * Creates a new platform package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public PlatformPackage(
+ SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mVersionName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ if (codeName.length() == 0) {
+ codeName = null;
+ }
+ mVersion = new AndroidVersion(apiLevel, codeName);
+
+ mIncludedAbi = PackageParserUtils.getOptionalXmlString(packageNode,
+ SdkRepoConstants.NODE_ABI_INCLUDED);
+
+ mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
+ }
+
+ /**
+ * Creates a new platform package based on an actual {@link IAndroidTarget} (which
+ * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual target location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(IAndroidTarget target, Properties props) {
+ return new PlatformPackage(target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected PlatformPackage(IAndroidTarget target, Properties props) {
+ this(null /*source*/, target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected PlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ super( source, //source
+ props, //properties
+ target.getRevision(), //revision
+ null, //license
+ target.getDescription(), //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ target.getLocation() //archiveOsPath
+ );
+
+ mVersion = target.getVersion();
+ mVersionName = target.getVersionName();
+ mLayoutlibVersion = new LayoutlibVersionMixin(props);
+ mIncludedAbi = props == null ? null : props.getProperty(PkgProps.PLATFORM_INCLUDED_ABI);
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ mVersion.saveProperties(props);
+ mLayoutlibVersion.saveProperties(props);
+
+ if (mVersionName != null) {
+ props.setProperty(PkgProps.PLATFORM_VERSION, mVersionName);
+ }
+
+ if (mIncludedAbi != null) {
+ props.setProperty(PkgProps.PLATFORM_INCLUDED_ABI, mIncludedAbi);
+ }
+
+ }
+
+ /** Returns the version, a string, for platform packages. */
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ /** Returns the package version, for platform, add-on and doc packages. */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns the ABI of the system-image included in this platform.
+ *
+ * @return Null if the platform does not include any system-image.
+ * Otherwise should be a valid non-empty ABI string (e.g. "x86" or "armeabi-v7a").
+ */
+ public String getIncludedAbi() {
+ return mIncludedAbi;
+ }
+
+ /**
+ * Returns the layoutlib version. Mandatory starting with repository XSD rev 4.
+ * <p/>
+ * The first integer is the API of layoublib, which should be > 0.
+ * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)
+ * if the layoutlib version isn't specified.
+ * <p/>
+ * The second integer is the revision for that given API. It is >= 0
+ * and works as a minor revision number, incremented for the same API level.
+ *
+ * @since sdk-repository-4.xsd
+ */
+ @Override
+ public Pair<Integer, Integer> getLayoutlibVersion() {
+ return mLayoutlibVersion.getLayoutlibVersion();
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For platforms, we use "android-N" where N is the API or the preview codename.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return "android-" + mVersion.getApiString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s;
+
+ if (mVersion.isPreview()) {
+ s = String.format("SDK Platform Android %1$s Preview%2$s",
+ getVersionName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ } else {
+ s = String.format("SDK Platform Android %1$s%2$s",
+ getVersionName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ }
+
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ String s;
+
+ if (mVersion.isPreview()) {
+ s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
+ getVersionName(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ } else {
+ s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",
+ getVersionName(),
+ mVersion.getApiLevel(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ }
+
+ return s;
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * 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.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+
+ // First find if this platform is already installed. If so, reuse the same directory.
+ for (IAndroidTarget target : sdkManager.getTargets()) {
+ if (target.isPlatform() && target.getVersion().equals(mVersion)) {
+ return new File(target.getLocation());
+ }
+ }
+
+ File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);
+ File folder = new File(platforms,
+ String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
+
+ return folder;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof PlatformPackage) {
+ PlatformPackage newPkg = (PlatformPackage)pkg;
+
+ // check they are the same version.
+ return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result +
+ ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ result = prime * result + ((mVersionName == null) ? 0 : mVersionName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof PlatformPackage)) {
+ return false;
+ }
+ PlatformPackage other = (PlatformPackage) obj;
+ if (mLayoutlibVersion == null) {
+ if (other.mLayoutlibVersion != null) {
+ return false;
+ }
+ } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
+ return false;
+ }
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ if (mVersionName == null) {
+ if (other.mVersionName != null) {
+ return false;
+ }
+ } else if (!mVersionName.equals(other.mVersionName)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
index 6070593..c46e940 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
@@ -16,9 +16,9 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.SdkConstants;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.AdbWrapper;
import com.android.sdklib.internal.repository.IDescription;
@@ -39,10 +39,12 @@ import java.util.Set;
/**
* Represents a platform-tool XML node in an SDK repository.
*/
-public class PlatformToolPackage extends Package {
+public class PlatformToolPackage extends FullRevisionPackage {
/** The value returned by {@link PlatformToolPackage#installId()}. */
public static final String INSTALL_ID = "platform-tools"; //$NON-NLS-1$
+ /** The value returned by {@link PlatformToolPackage#installId()}. */
+ public static final String INSTALL_ID_PREVIEW = "platform-tools-preview"; //$NON-NLS-1$
/**
* Creates a new platform-tool package from the attributes and elements of the given XML node.
@@ -153,13 +155,18 @@ public class PlatformToolPackage extends Package {
/**
* Returns a string identifier to install this package from the command line.
- * For platform-tools, we use "platform-tools" since this package type is unique.
+ * For platform-tools, we use "platform-tools" or "platform-tools-preview" since
+ * this package type is unique.
* <p/>
* {@inheritDoc}
*/
@Override
public String installId() {
- return INSTALL_ID;
+ if (getRevision().isPreview()) {
+ return INSTALL_ID_PREVIEW;
+ } else {
+ return INSTALL_ID;
+ }
}
/**
@@ -178,8 +185,8 @@ public class PlatformToolPackage extends Package {
*/
@Override
public String getShortDescription() {
- return String.format("Android SDK Platform-tools, revision %1$d%2$s",
- getRevision(),
+ return String.format("Android SDK Platform-tools, revision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -192,8 +199,8 @@ public class PlatformToolPackage extends Package {
}
if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
isObsolete() ? " (Obsolete)" : "");
}
@@ -215,10 +222,28 @@ public class PlatformToolPackage extends Package {
return new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
}
+ /**
+ * Check whether 2 platform-tool packages are the same <em>and</em> have the
+ * same preview bit.
+ */
@Override
public boolean sameItemAs(Package pkg) {
+ return sameItemAs(pkg, false /*ignorePreviews*/);
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg, boolean ignorePreviews) {
// only one platform-tool package so any platform-tool package is the same item.
- return pkg instanceof PlatformToolPackage;
+ if (pkg instanceof PlatformToolPackage) {
+ if (ignorePreviews) {
+ return true;
+ } else {
+ // however previews can only match previews by default, unless we ignore that check.
+ return ((PlatformToolPackage) pkg).getRevision().isPreview() ==
+ getRevision().isPreview();
+ }
+ }
+ return false;
}
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
index ed5eda5..06eabb9 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
@@ -1,533 +1,536 @@
-/*
- * 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.packages;
-
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a sample XML node in an SDK repository.
- */
-public class SamplePackage extends MinToolsPackage
- implements IPackageVersion, IMinApiLevelDependency {
-
- /** The matching platform version. */
- private final AndroidVersion mVersion;
-
- /**
- * The minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- private final int mMinApiLevel;
-
- /**
- * Creates a new sample package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SamplePackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.length() == 0) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mMinApiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_MIN_API_LEVEL,
- MIN_API_LEVEL_NOT_SPECIFIED);
- }
-
- /**
- * Creates a new sample package based on an actual {@link IAndroidTarget} (which
- * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
- * <p/>
- * The target <em>must</em> have an existing sample directory that uses the /samples
- * root form rather than the old form where the samples dir was located under the
- * platform dir.
- * <p/>
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual samples path location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(IAndroidTarget target, Properties props) {
- return new SamplePackage(target, props);
- }
-
- private SamplePackage(IAndroidTarget target, Properties props) {
- super( null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- null, //description
- null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
- target.getPath(IAndroidTarget.SAMPLES) //archiveOsPath
- );
-
- mVersion = target.getVersion();
-
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.SAMPLE_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
- }
-
- /**
- * Creates a new sample package from an actual directory path and previously
- * saved properties.
- * <p/>
- * This is used to list local SDK folders in which case there is one archive which
- * URL is the actual samples path location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- *
- * @throws AndroidVersionException if the {@link AndroidVersion} can't be restored
- * from properties.
- */
- public static Package create(String archiveOsPath, Properties props) throws AndroidVersionException {
- return new SamplePackage(archiveOsPath, props);
- }
-
- private SamplePackage(String archiveOsPath, Properties props) throws AndroidVersionException {
- super(null, //source
- props, //properties
- 0, //revision will be taken from props
- null, //license
- null, //description
- null, //descUrl
- Os.ANY, //archiveOs
- Arch.ANY, //archiveArch
- archiveOsPath //archiveOsPath
- );
-
- mVersion = new AndroidVersion(props);
-
- mMinApiLevel = Integer.parseInt(
- getProperty(props,
- PkgProps.SAMPLE_MIN_API_LEVEL,
- Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
-
- if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
- props.setProperty(PkgProps.SAMPLE_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
- }
- }
-
- /**
- * Returns the minimal API level required by this extra package, if > 0,
- * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
- */
- @Override
- public int getMinApiLevel() {
- return mMinApiLevel;
- }
-
- /** Returns the matching platform version. */
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For samples, we use "sample-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "sample-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- String s = String.format("Samples for SDK API %1$s%2$s%3$s",
- mVersion.getApiString(),
- mVersion.isPreview() ? " Preview" : "",
- isObsolete() ? " (Obsolete)" : "");
- return s;
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- String s = String.format("Samples for SDK API %1$s%2$s, revision %3$d%4$s",
- mVersion.getApiString(),
- mVersion.isPreview() ? " Preview" : "",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- return s;
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the &lt;description&gt; field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A sample package is typically installed in SDK/samples/android-"version".
- * However if we can find a different directory that already has this sample
- * version installed, we'll use that one.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
-
- // The /samples dir at the root of the SDK
- File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
-
- // First find if this sample is already installed. If so, reuse the same directory.
- for (IAndroidTarget target : sdkManager.getTargets()) {
- if (target.isPlatform() &&
- target.getVersion().equals(mVersion)) {
- String p = target.getPath(IAndroidTarget.SAMPLES);
- File f = new File(p);
- if (f.isDirectory()) {
- // We *only* use this directory if it's using the "new" location
- // under SDK/samples. We explicitly do not reuse the "old" location
- // under SDK/platform/android-N/samples.
- if (f.getParentFile().equals(samplesRoot)) {
- return f;
- }
- }
- }
- }
-
- // Otherwise, get a suitable default
- File folder = new File(samplesRoot,
- String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$
-
- for (int n = 1; folder.exists(); n++) {
- // Keep trying till we find an unused directory.
- folder = new File(samplesRoot,
- String.format("android-%s_%d", getVersion().getApiString(), n)); //$NON-NLS-1$
- }
-
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SamplePackage) {
- SamplePackage newPkg = (SamplePackage)pkg;
-
- // check they are the same version.
- return newPkg.getVersion().equals(this.getVersion());
- }
-
- return false;
- }
-
- /**
- * Makes sure the base /samples folder exists before installing.
- *
- * {@inheritDoc}
- */
- @Override
- public boolean preInstallHook(Archive archive,
- ITaskMonitor monitor,
- String osSdkRoot,
- File installFolder) {
-
- if (installFolder != null && installFolder.isDirectory()) {
- // Get the hash computed during the last installation
- String storedHash = readContentHash(installFolder);
- if (storedHash != null && storedHash.length() > 0) {
-
- // Get the hash of the folder now
- String currentHash = computeContentHash(installFolder);
-
- if (!storedHash.equals(currentHash)) {
- // The hashes differ. The content was modified.
- // Ask the user if we should still wipe the old samples.
-
- String pkgName = archive.getParentPackage().getShortDescription();
-
- String msg = String.format(
- "-= Warning ! =-\n" +
- "You are about to replace the content of the folder:\n " +
- " %1$s\n" +
- "by the new package:\n" +
- " %2$s.\n" +
- "\n" +
- "However it seems that the content of the existing samples " +
- "has been modified since it was last installed. Are you sure " +
- "you want to DELETE the existing samples? This cannot be undone.\n" +
- "Please select YES to delete the existing sample and replace them " +
- "by the new ones.\n" +
- "Please select NO to skip this package. You can always install it later.",
- installFolder.getAbsolutePath(),
- pkgName);
-
- // Returns true if we can wipe & replace.
- return monitor.displayPrompt("SDK Manager: overwrite samples?", msg);
- }
- }
- }
-
- // The default is to allow installation
- return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
- }
-
- /**
- * Computes a hash of the installed content (in case of successful install.)
- *
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
- super.postInstallHook(archive, monitor, installFolder);
-
- if (installFolder != null) {
- String h = computeContentHash(installFolder);
- saveContentHash(installFolder, h);
- }
- }
-
- /**
- * Set all the files from a sample package as read-only so that
- * users don't end up modifying sources by mistake in Eclipse
- * (samples are copied if using the NPW > Create from sample.)
- */
- @Override
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
- super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
-
- if (fileOp.isFile(unzippedFile) &&
- !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
- fileOp.setReadOnly(unzippedFile);
- }
- }
-
- /**
- * Reads the hash from the properties file, if it exists.
- * Returns null if something goes wrong, e.g. there's no property file or
- * it doesn't contain our hash. Returns an empty string if the hash wasn't
- * correctly computed last time by {@link #saveContentHash(File, String)}.
- */
- private String readContentHash(File folder) {
- Properties props = new Properties();
-
- FileInputStream fis = null;
- try {
- File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
- if (f.isFile()) {
- fis = new FileInputStream(f);
- props.load(fis);
- return props.getProperty("content-hash", null); //$NON-NLS-1$
- }
- } catch (Exception e) {
- // ignore
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
-
- return null;
- }
-
- /**
- * Saves the hash using a properties file
- */
- private void saveContentHash(File folder, String hash) {
- Properties props = new Properties();
-
- props.setProperty("content-hash", hash == null ? "" : hash); //$NON-NLS-1$ //$NON-NLS-2$
-
- FileOutputStream fos = null;
- try {
- File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
- fos = new FileOutputStream(f);
- props.store( fos, "## Android - hash of this archive."); //$NON-NLS-1$
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- /**
- * Computes a hash of the files names and sizes installed in the folder
- * using the SHA-1 digest.
- * Returns null if the digest algorithm is not available.
- */
- private String computeContentHash(File installFolder) {
- MessageDigest md = null;
- try {
- // SHA-1 is a standard algorithm.
- // http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppB
- md = MessageDigest.getInstance("SHA-1"); //$NON-NLS-1$
- } catch (NoSuchAlgorithmException e) {
- // We're unlikely to get there unless this JVM is not spec conforming
- // in which case there won't be any hash available.
- }
-
- if (md != null) {
- hashDirectoryContent(installFolder, md);
- return getDigestHexString(md);
- }
-
- return null;
- }
-
- /**
- * Computes a hash of the *content* of this directory. The hash only uses
- * the files names and the file sizes.
- */
- private void hashDirectoryContent(File folder, MessageDigest md) {
- if (folder == null || md == null || !folder.isDirectory()) {
- return;
- }
-
- for (File f : folder.listFiles()) {
- if (f.isDirectory()) {
- hashDirectoryContent(f, md);
-
- } else {
- String name = f.getName();
-
- // Skip the file we use to store the content hash
- if (name == null || SdkConstants.FN_CONTENT_HASH_PROP.equals(name)) {
- continue;
- }
-
- try {
- md.update(name.getBytes("UTF-8")); //$NON-NLS-1$
- } catch (UnsupportedEncodingException e) {
- // There is no valid reason for UTF-8 to be unsupported. Ignore.
- }
- try {
- long len = f.length();
- md.update((byte) (len & 0x0FF));
- md.update((byte) ((len >> 8) & 0x0FF));
- md.update((byte) ((len >> 16) & 0x0FF));
- md.update((byte) ((len >> 24) & 0x0FF));
-
- } catch (SecurityException e) {
- // Might happen if file is not readable. Ignore.
- }
- }
- }
- }
-
- /**
- * Returns a digest as an hex string.
- */
- private String getDigestHexString(MessageDigest digester) {
- // Create an hex string from the digest
- byte[] digest = digester.digest();
- int n = digest.length;
- String hex = "0123456789abcdef"; //$NON-NLS-1$
- char[] hexDigest = new char[n * 2];
- for (int i = 0; i < n; i++) {
- int b = digest[i] & 0x0FF;
- hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
- hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
- }
-
- return new String(hexDigest);
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a sample XML node in an SDK repository.
+ */
+public class SamplePackage extends MinToolsPackage
+ implements IAndroidVersionProvider, IMinApiLevelDependency {
+
+ /** The matching platform version. */
+ private final AndroidVersion mVersion;
+
+ /**
+ * The minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final int mMinApiLevel;
+
+ /**
+ * Creates a new sample package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public SamplePackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ if (codeName.length() == 0) {
+ codeName = null;
+ }
+ mVersion = new AndroidVersion(apiLevel, codeName);
+
+ mMinApiLevel = PackageParserUtils.getXmlInt(packageNode,
+ SdkRepoConstants.NODE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
+ }
+
+ /**
+ * Creates a new sample package based on an actual {@link IAndroidTarget} (which
+ * must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
+ * <p/>
+ * The target <em>must</em> have an existing sample directory that uses the /samples
+ * root form rather than the old form where the samples dir was located under the
+ * platform dir.
+ * <p/>
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual samples path location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(IAndroidTarget target, Properties props) {
+ return new SamplePackage(target, props);
+ }
+
+ private SamplePackage(IAndroidTarget target, Properties props) {
+ super( null, //source
+ props, //properties
+ 0, //revision will be taken from props
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ target.getPath(IAndroidTarget.SAMPLES) //archiveOsPath
+ );
+
+ mVersion = target.getVersion();
+
+ mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
+ }
+
+ /**
+ * Creates a new sample package from an actual directory path and previously
+ * saved properties.
+ * <p/>
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual samples path location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ *
+ * @throws AndroidVersionException if the {@link AndroidVersion} can't be restored
+ * from properties.
+ */
+ public static Package create(String archiveOsPath, Properties props)
+ throws AndroidVersionException {
+ return new SamplePackage(archiveOsPath, props);
+ }
+
+ private SamplePackage(String archiveOsPath, Properties props) throws AndroidVersionException {
+ super(null, //source
+ props, //properties
+ 0, //revision will be taken from props
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ archiveOsPath //archiveOsPath
+ );
+
+ mVersion = new AndroidVersion(props);
+
+ mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ mVersion.saveProperties(props);
+
+ if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
+ props.setProperty(PkgProps.SAMPLE_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
+ }
+ }
+
+ /**
+ * Returns the minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public int getMinApiLevel() {
+ return mMinApiLevel;
+ }
+
+ /** Returns the matching platform version. */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For samples, we use "sample-N" where N is the API or the preview codename.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return "sample-" + mVersion.getApiString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s = String.format("Samples for SDK API %1$s%2$s%3$s",
+ mVersion.getApiString(),
+ mVersion.isPreview() ? " Preview" : "",
+ isObsolete() ? " (Obsolete)" : "");
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ String s = String.format("Samples for SDK API %1$s%2$s, revision %3$s%4$s",
+ mVersion.getApiString(),
+ mVersion.isPreview() ? " Preview" : "",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ return s;
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the &lt;description&gt; field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A sample package is typically installed in SDK/samples/android-"version".
+ * However if we can find a different directory that already has this sample
+ * version installed, we'll use that one.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+
+ // The /samples dir at the root of the SDK
+ File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
+
+ // First find if this sample is already installed. If so, reuse the same directory.
+ for (IAndroidTarget target : sdkManager.getTargets()) {
+ if (target.isPlatform() &&
+ target.getVersion().equals(mVersion)) {
+ String p = target.getPath(IAndroidTarget.SAMPLES);
+ File f = new File(p);
+ if (f.isDirectory()) {
+ // We *only* use this directory if it's using the "new" location
+ // under SDK/samples. We explicitly do not reuse the "old" location
+ // under SDK/platform/android-N/samples.
+ if (f.getParentFile().equals(samplesRoot)) {
+ return f;
+ }
+ }
+ }
+ }
+
+ // Otherwise, get a suitable default
+ File folder = new File(samplesRoot,
+ String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$
+
+ for (int n = 1; folder.exists(); n++) {
+ // Keep trying till we find an unused directory.
+ folder = new File(samplesRoot,
+ String.format("android-%s_%d", getAndroidVersion().getApiString(), n)); //$NON-NLS-1$
+ }
+
+ return folder;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof SamplePackage) {
+ SamplePackage newPkg = (SamplePackage)pkg;
+
+ // check they are the same version.
+ return newPkg.getAndroidVersion().equals(this.getAndroidVersion());
+ }
+
+ return false;
+ }
+
+ /**
+ * Makes sure the base /samples folder exists before installing.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean preInstallHook(Archive archive,
+ ITaskMonitor monitor,
+ String osSdkRoot,
+ File installFolder) {
+
+ if (installFolder != null && installFolder.isDirectory()) {
+ // Get the hash computed during the last installation
+ String storedHash = readContentHash(installFolder);
+ if (storedHash != null && storedHash.length() > 0) {
+
+ // Get the hash of the folder now
+ String currentHash = computeContentHash(installFolder);
+
+ if (!storedHash.equals(currentHash)) {
+ // The hashes differ. The content was modified.
+ // Ask the user if we should still wipe the old samples.
+
+ String pkgName = archive.getParentPackage().getShortDescription();
+
+ String msg = String.format(
+ "-= Warning ! =-\n" +
+ "You are about to replace the content of the folder:\n " +
+ " %1$s\n" +
+ "by the new package:\n" +
+ " %2$s.\n" +
+ "\n" +
+ "However it seems that the content of the existing samples " +
+ "has been modified since it was last installed. Are you sure " +
+ "you want to DELETE the existing samples? This cannot be undone.\n" +
+ "Please select YES to delete the existing sample and replace them " +
+ "by the new ones.\n" +
+ "Please select NO to skip this package. You can always install it later.",
+ installFolder.getAbsolutePath(),
+ pkgName);
+
+ // Returns true if we can wipe & replace.
+ return monitor.displayPrompt("SDK Manager: overwrite samples?", msg);
+ }
+ }
+ }
+
+ // The default is to allow installation
+ return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
+ }
+
+ /**
+ * Computes a hash of the installed content (in case of successful install.)
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
+ super.postInstallHook(archive, monitor, installFolder);
+
+ if (installFolder != null) {
+ String h = computeContentHash(installFolder);
+ saveContentHash(installFolder, h);
+ }
+ }
+
+ /**
+ * Set all the files from a sample package as read-only so that
+ * users don't end up modifying sources by mistake in Eclipse
+ * (samples are copied if using the NPW > Create from sample.)
+ */
+ @Override
+ public void postUnzipFileHook(
+ Archive archive,
+ ITaskMonitor monitor,
+ IFileOp fileOp,
+ File unzippedFile,
+ ZipArchiveEntry zipEntry) {
+ super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
+
+ if (fileOp.isFile(unzippedFile) &&
+ !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
+ fileOp.setReadOnly(unzippedFile);
+ }
+ }
+
+ /**
+ * Reads the hash from the properties file, if it exists.
+ * Returns null if something goes wrong, e.g. there's no property file or
+ * it doesn't contain our hash. Returns an empty string if the hash wasn't
+ * correctly computed last time by {@link #saveContentHash(File, String)}.
+ */
+ private String readContentHash(File folder) {
+ Properties props = new Properties();
+
+ FileInputStream fis = null;
+ try {
+ File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
+ if (f.isFile()) {
+ fis = new FileInputStream(f);
+ props.load(fis);
+ return props.getProperty("content-hash", null); //$NON-NLS-1$
+ }
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Saves the hash using a properties file
+ */
+ private void saveContentHash(File folder, String hash) {
+ Properties props = new Properties();
+
+ props.setProperty("content-hash", hash == null ? "" : hash); //$NON-NLS-1$ //$NON-NLS-2$
+
+ FileOutputStream fos = null;
+ try {
+ File f = new File(folder, SdkConstants.FN_CONTENT_HASH_PROP);
+ fos = new FileOutputStream(f);
+ props.store( fos, "## Android - hash of this archive."); //$NON-NLS-1$
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes a hash of the files names and sizes installed in the folder
+ * using the SHA-1 digest.
+ * Returns null if the digest algorithm is not available.
+ */
+ private String computeContentHash(File installFolder) {
+ MessageDigest md = null;
+ try {
+ // SHA-1 is a standard algorithm.
+ // http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppB
+ md = MessageDigest.getInstance("SHA-1"); //$NON-NLS-1$
+ } catch (NoSuchAlgorithmException e) {
+ // We're unlikely to get there unless this JVM is not spec conforming
+ // in which case there won't be any hash available.
+ }
+
+ if (md != null) {
+ hashDirectoryContent(installFolder, md);
+ return getDigestHexString(md);
+ }
+
+ return null;
+ }
+
+ /**
+ * Computes a hash of the *content* of this directory. The hash only uses
+ * the files names and the file sizes.
+ */
+ private void hashDirectoryContent(File folder, MessageDigest md) {
+ if (folder == null || md == null || !folder.isDirectory()) {
+ return;
+ }
+
+ for (File f : folder.listFiles()) {
+ if (f.isDirectory()) {
+ hashDirectoryContent(f, md);
+
+ } else {
+ String name = f.getName();
+
+ // Skip the file we use to store the content hash
+ if (name == null || SdkConstants.FN_CONTENT_HASH_PROP.equals(name)) {
+ continue;
+ }
+
+ try {
+ md.update(name.getBytes("UTF-8")); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ // There is no valid reason for UTF-8 to be unsupported. Ignore.
+ }
+ try {
+ long len = f.length();
+ md.update((byte) (len & 0x0FF));
+ md.update((byte) ((len >> 8) & 0x0FF));
+ md.update((byte) ((len >> 16) & 0x0FF));
+ md.update((byte) ((len >> 24) & 0x0FF));
+
+ } catch (SecurityException e) {
+ // Might happen if file is not readable. Ignore.
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a digest as an hex string.
+ */
+ private String getDigestHexString(MessageDigest digester) {
+ // Create an hex string from the digest
+ byte[] digest = digester.digest();
+ int n = digest.length;
+ String hex = "0123456789abcdef"; //$NON-NLS-1$
+ char[] hexDigest = new char[n * 2];
+ for (int i = 0; i < n; i++) {
+ int b = digest[i] & 0x0FF;
+ hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
+ hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
+ }
+
+ return new String(hexDigest);
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
index 2c577e4..fb38f40 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
@@ -1,338 +1,340 @@
-/*
- * Copyright (C) 2011 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.packages;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.io.IFileOp;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a source XML node in an SDK repository.
- * <p/>
- * Note that a source package has a version and thus implements {@link IPackageVersion}.
- * However there is no mandatory dependency that limits installation so this does not
- * implement {@link IPlatformDependency}.
- */
-public class SourcePackage extends Package implements IPackageVersion {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- /**
- * Creates a new source package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SourcePackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.length() == 0) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SourcePackage(
- AndroidVersion platformVersion,
- int revision,
- Properties props,
- String localOsPath) {
- this(null /*source*/, platformVersion, revision, props, localOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SourcePackage(
- SdkSource source,
- AndroidVersion platformVersion,
- int revision,
- Properties props,
- String localOsPath) {
- super( source, //source
- props, //properties
- revision, //revision
- null, //license
- null, //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- localOsPath //archiveOsPath
- );
- mVersion = platformVersion;
- }
-
- /**
- * Creates either a valid {@link SourcePackage} or a {@link BrokenPackage}.
- * <p/>
- * If the source directory contains valid properties, this creates a new {@link SourcePackage}
- * with the android version listed in the properties.
- * Otherwise returns a new {@link BrokenPackage} with some explanation on what failed.
- *
- * @param srcDir The SDK/sources/android-N folder
- * @param props The properties located in {@code srcDir} or null if not found.
- * @return A new {@link SourcePackage} or a new {@link BrokenPackage}.
- */
- public static Package create(File srcDir, Properties props) {
- AndroidVersion version = null;
- String error = null;
-
- // Try to load the android version from the sources.props.
- // If we don't find them, it would explain why this package is broken.
- if (props == null) {
- error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
- } else {
- try {
- version = new AndroidVersion(props);
- // The constructor will extract the revision from the properties
- // and it will not consider a missing revision as being fatal.
- return new SourcePackage(version, 0 /*revision*/, props, srcDir.getAbsolutePath());
- } catch (AndroidVersionException e) {
- error = String.format("Invalid file %1$s: %2$s",
- SdkConstants.FN_SOURCE_PROP,
- e.getMessage());
- }
- }
-
- if (version == null) {
- try {
- // Try to parse the first number out of the platform folder name.
- // This is just a wild guess in case we can create a broken package using that info.
- String platform = srcDir.getParentFile().getName();
- platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- int pos = platform.indexOf(' ');
- if (pos >= 0) {
- platform = platform.substring(0, pos);
- }
- int apiLevel = Integer.parseInt(platform);
- version = new AndroidVersion(apiLevel, null /*codename*/);
- } catch (Exception ignore) {
- }
- }
-
- StringBuilder sb = new StringBuilder("Broken Source Package");
- if (version != null) {
- sb.append(String.format(", API %1$s", version.getApiString()));
- }
-
- String shortDesc = sb.toString();
-
- if (error != null) {
- sb.append('\n').append(error);
- }
-
- String longDesc = sb.toString();
-
- return new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- srcDir.getAbsolutePath());
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
- mVersion.saveProperties(props);
- }
-
- /**
- * Returns the android version of this package.
- */
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For sources, we use "source-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "source-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- if (mVersion.isPreview()) {
- return String.format("Sources for Android '%1$s' Preview SDK%2$s",
- mVersion.getCodename(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Sources for Android SDK%2$s",
- mVersion.getApiLevel(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- if (mVersion.isPreview()) {
- return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",
- mVersion.getCodename(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- } else {
- return String.format("Sources for Android SDK, API %1$d, revision %2$s%3$s",
- mVersion.getApiLevel(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the {@code description} field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A sources package is typically installed in SDK/sources/platform.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File folder = new File(osSdkRoot, SdkConstants.FD_PKG_SOURCES);
- folder = new File(folder, "android-" + mVersion.getApiString()); //$NON-NLS-1$
- return folder;
- }
-
- /**
- * Set all the files from a source package as read-only
- * so that users don't end up modifying sources by mistake in Eclipse.
- */
- @Override
- public void postUnzipFileHook(
- Archive archive,
- ITaskMonitor monitor,
- IFileOp fileOp,
- File unzippedFile,
- ZipArchiveEntry zipEntry) {
- super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
-
- if (fileOp.isFile(unzippedFile) &&
- !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
- fileOp.setReadOnly(unzippedFile);
- }
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SourcePackage) {
- SourcePackage newPkg = (SourcePackage)pkg;
-
- // check they are the same version.
- return getVersion().equals(newPkg.getVersion());
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof SourcePackage)) {
- return false;
- }
- SourcePackage other = (SourcePackage) obj;
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * Copyright (C) 2011 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a source XML node in an SDK repository.
+ * <p/>
+ * Note that a source package has a version and thus implements {@link IAndroidVersionProvider}.
+ * However there is no mandatory dependency that limits installation so this does not
+ * implement {@link IPlatformDependency}.
+ */
+public class SourcePackage extends MajorRevisionPackage implements IAndroidVersionProvider {
+
+ /** The package version, for platform, add-on and doc packages. */
+ private final AndroidVersion mVersion;
+
+ /**
+ * Creates a new source package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public SourcePackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ if (codeName.length() == 0) {
+ codeName = null;
+ }
+ mVersion = new AndroidVersion(apiLevel, codeName);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected SourcePackage(
+ AndroidVersion platformVersion,
+ int revision,
+ Properties props,
+ String localOsPath) {
+ this(null /*source*/, platformVersion, revision, props, localOsPath);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected SourcePackage(
+ SdkSource source,
+ AndroidVersion platformVersion,
+ int revision,
+ Properties props,
+ String localOsPath) {
+ super( source, //source
+ props, //properties
+ revision, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ localOsPath //archiveOsPath
+ );
+ mVersion = platformVersion;
+ }
+
+ /**
+ * Creates either a valid {@link SourcePackage} or a {@link BrokenPackage}.
+ * <p/>
+ * If the source directory contains valid properties, this creates a new {@link SourcePackage}
+ * with the android version listed in the properties.
+ * Otherwise returns a new {@link BrokenPackage} with some explanation on what failed.
+ *
+ * @param srcDir The SDK/sources/android-N folder
+ * @param props The properties located in {@code srcDir} or null if not found.
+ * @return A new {@link SourcePackage} or a new {@link BrokenPackage}.
+ */
+ public static Package create(File srcDir, Properties props) {
+ AndroidVersion version = null;
+ String error = null;
+
+ // Try to load the android version from the sources.props.
+ // If we don't find them, it would explain why this package is broken.
+ if (props == null) {
+ error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
+ } else {
+ try {
+ version = new AndroidVersion(props);
+ // The constructor will extract the revision from the properties
+ // and it will not consider a missing revision as being fatal.
+ return new SourcePackage(version, 0 /*revision*/, props, srcDir.getAbsolutePath());
+ } catch (AndroidVersionException e) {
+ error = String.format("Invalid file %1$s: %2$s",
+ SdkConstants.FN_SOURCE_PROP,
+ e.getMessage());
+ }
+ }
+
+ if (version == null) {
+ try {
+ // Try to parse the first number out of the platform folder name.
+ // This is just a wild guess in case we can create a broken package using that info.
+ String platform = srcDir.getParentFile().getName();
+ platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ int pos = platform.indexOf(' ');
+ if (pos >= 0) {
+ platform = platform.substring(0, pos);
+ }
+ int apiLevel = Integer.parseInt(platform);
+ version = new AndroidVersion(apiLevel, null /*codename*/);
+ } catch (Exception ignore) {
+ }
+ }
+
+ StringBuilder sb = new StringBuilder("Broken Source Package");
+ if (version != null) {
+ sb.append(String.format(", API %1$s", version.getApiString()));
+ }
+
+ String shortDesc = sb.toString();
+
+ if (error != null) {
+ sb.append('\n').append(error);
+ }
+
+ String longDesc = sb.toString();
+
+ return new BrokenPackage(props, shortDesc, longDesc,
+ IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
+ version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
+ srcDir.getAbsolutePath());
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ mVersion.saveProperties(props);
+ }
+
+ /**
+ * Returns the android version of this package.
+ */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For sources, we use "source-N" where N is the API or the preview codename.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return "source-" + mVersion.getApiString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ if (mVersion.isPreview()) {
+ return String.format("Sources for Android '%1$s' Preview SDK%2$s",
+ mVersion.getCodename(),
+ isObsolete() ? " (Obsolete)" : "");
+ } else {
+ return String.format("Sources for Android SDK%2$s",
+ mVersion.getApiLevel(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ if (mVersion.isPreview()) {
+ return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",
+ mVersion.getCodename(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ } else {
+ return String.format("Sources for Android SDK, API %1$d, revision %2$s%3$s",
+ mVersion.getApiLevel(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the {@code description} field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A sources package is typically installed in SDK/sources/platform.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ File folder = new File(osSdkRoot, SdkConstants.FD_PKG_SOURCES);
+ folder = new File(folder, "android-" + mVersion.getApiString()); //$NON-NLS-1$
+ return folder;
+ }
+
+ /**
+ * Set all the files from a source package as read-only
+ * so that users don't end up modifying sources by mistake in Eclipse.
+ */
+ @Override
+ public void postUnzipFileHook(
+ Archive archive,
+ ITaskMonitor monitor,
+ IFileOp fileOp,
+ File unzippedFile,
+ ZipArchiveEntry zipEntry) {
+ super.postUnzipFileHook(archive, monitor, fileOp, unzippedFile, zipEntry);
+
+ if (fileOp.isFile(unzippedFile) &&
+ !SdkConstants.FN_SOURCE_PROP.equals(unzippedFile.getName())) {
+ fileOp.setReadOnly(unzippedFile);
+ }
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof SourcePackage) {
+ SourcePackage newPkg = (SourcePackage)pkg;
+
+ // check they are the same version.
+ return getAndroidVersion().equals(newPkg.getAndroidVersion());
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof SourcePackage)) {
+ return false;
+ }
+ SourcePackage other = (SourcePackage) obj;
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
index c862882..69335a5 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
@@ -1,377 +1,379 @@
-/*
- * Copyright (C) 2011 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.packages;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.AndroidVersion;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.SystemImage;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Represents a system-image XML node in an SDK repository.
- */
-public class SystemImagePackage extends Package
- implements IPackageVersion, IPlatformDependency {
-
- /** The package version, for platform, add-on and doc packages. */
- private final AndroidVersion mVersion;
-
- /** The ABI of the system-image. Must not be null nor empty. */
- private final String mAbi;
-
- /**
- * Creates a new system-image package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public SystemImagePackage(SdkSource source,
- Node packageNode,
- String nsUri,
- Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
- String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
- if (codeName.length() == 0) {
- codeName = null;
- }
- mVersion = new AndroidVersion(apiLevel, codeName);
-
- mAbi = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- public SystemImagePackage(
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props,
- String localOsPath) {
- this(null /*source*/, platformVersion, revision, abi, props, localOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected SystemImagePackage(
- SdkSource source,
- AndroidVersion platformVersion,
- int revision,
- String abi,
- Properties props,
- String localOsPath) {
- super( source, //source
- props, //properties
- revision, //revision
- null, //license
- null, //description
- null, //descUrl
- Os.getCurrentOs(), //archiveOs
- Arch.getCurrentArch(), //archiveArch
- localOsPath //archiveOsPath
- );
- mVersion = platformVersion;
- if (abi == null && props != null) {
- abi = props.getProperty(PkgProps.SYS_IMG_ABI);
- }
- assert abi != null : "To use this SystemImagePackage constructor you must pass an ABI as a parameter or as a PROP_ABI property";
- mAbi = abi;
- }
-
- /**
- * Creates a {@link BrokenPackage} representing a system image that failed to load
- * with the regular {@link SdkManager} workflow.
- *
- * @param abiDir The SDK/system-images/android-N/abi folder
- * @param props The properties located in {@code abiDir} or null if not found.
- * @return A new {@link BrokenPackage} that represents this installed package.
- */
- public static Package createBroken(File abiDir, Properties props) {
- AndroidVersion version = null;
- String abiType = abiDir.getName();
- String error = null;
-
- // Try to load the android version & ABI from the sources.props.
- // If we don't find them, it would explain why this package is broken.
- if (props == null) {
- error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
- } else {
- try {
- version = new AndroidVersion(props);
-
- String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
- if (abi != null) {
- abiType = abi;
- } else {
- error = String.format("Invalid file %1$s: Missing property %2$s",
- SdkConstants.FN_SOURCE_PROP,
- PkgProps.SYS_IMG_ABI);
- }
- } catch (AndroidVersionException e) {
- error = String.format("Invalid file %1$s: %2$s",
- SdkConstants.FN_SOURCE_PROP,
- e.getMessage());
- }
- }
-
- if (version == null) {
- try {
- // Try to parse the first number out of the platform folder name.
- String platform = abiDir.getParentFile().getName();
- platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
- int pos = platform.indexOf(' ');
- if (pos >= 0) {
- platform = platform.substring(0, pos);
- }
- int apiLevel = Integer.parseInt(platform);
- version = new AndroidVersion(apiLevel, null /*codename*/);
- } catch (Exception ignore) {
- }
- }
-
- StringBuilder sb = new StringBuilder(
- String.format("Broken %1$s System Image", getAbiDisplayNameInternal(abiType)));
- if (version != null) {
- sb.append(String.format(", API %1$s", version.getApiString()));
- }
-
- String shortDesc = sb.toString();
-
- if (error != null) {
- sb.append('\n').append(error);
- }
-
- String longDesc = sb.toString();
-
- return new BrokenPackage(props, shortDesc, longDesc,
- IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
- version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
- abiDir.getAbsolutePath());
- }
-
- /**
- * Save the properties of the current packages in the given {@link Properties} object.
- * These properties will later be given to a constructor that takes a {@link Properties} object.
- */
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- mVersion.saveProperties(props);
- props.setProperty(PkgProps.SYS_IMG_ABI, mAbi);
- }
-
- /** Returns the ABI of the system-image. Cannot be null nor empty. */
- public String getAbi() {
- return mAbi;
- }
-
- /** Returns a display-friendly name for the ABI of the system-image. */
- public String getAbiDisplayName() {
- return getAbiDisplayNameInternal(mAbi);
- }
-
- private static String getAbiDisplayNameInternal(String abi) {
- return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("mips", "Mips") //$NON-NLS-1$ //$NON-NLS-2$
- .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- /**
- * Returns the version of the platform dependency of this package.
- * <p/>
- * A system-image has the same {@link AndroidVersion} as the platform it depends on.
- */
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For system images, we use "sysimg-N" where N is the API or the preview codename.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return "sysimg-" + mVersion.getApiString(); //$NON-NLS-1$
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- return String.format("%1$s System Image%2$s",
- getAbiDisplayName(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s",
- getAbiDisplayName(),
- mVersion.getApiString(),
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a long description for an {@link IDescription}.
- *
- * The long description is whatever the XML contains for the {@code description} field,
- * or the short description if the former is empty.
- */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- s += String.format("\nRequires SDK Platform Android API %1$s",
- mVersion.getApiString());
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A system-image package is typically installed in SDK/systems/platform/abi.
- * The name needs to be sanitized to be acceptable as a directory name.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES);
- folder = new File(folder, SystemImage.ANDROID_PREFIX + mVersion.getApiString());
-
- // Computes a folder directory using the sanitized abi string.
- String abi = mAbi;
- abi = abi.toLowerCase(Locale.US);
- abi = abi.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
- abi = abi.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
-
- folder = new File(folder, abi);
- return folder;
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- if (pkg instanceof SystemImagePackage) {
- SystemImagePackage newPkg = (SystemImagePackage)pkg;
-
- // check they are the same abi and version.
- return getAbi().equals(newPkg.getAbi()) &&
- getVersion().equals(newPkg.getVersion());
- }
-
- return false;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode());
- result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof SystemImagePackage)) {
- return false;
- }
- SystemImagePackage other = (SystemImagePackage) obj;
- if (mAbi == null) {
- if (other.mAbi != null) {
- return false;
- }
- } else if (!mAbi.equals(other.mAbi)) {
- return false;
- }
- if (mVersion == null) {
- if (other.mVersion != null) {
- return false;
- }
- } else if (!mVersion.equals(other.mVersion)) {
- return false;
- }
- return true;
- }
-
- /**
- * For sys img packages, we want to add abi to the sorting key
- * <em>before<em/> the revision number.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- protected String comparisonKey() {
- String s = super.comparisonKey();
- int pos = s.indexOf("|r:"); //$NON-NLS-1$
- assert pos > 0;
- s = s.substring(0, pos) +
- "|abi:" + getAbiDisplayName() + //$NON-NLS-1$
- s.substring(pos);
- return s;
- }
-}
+/*
+ * Copyright (C) 2011 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a system-image XML node in an SDK repository.
+ */
+public class SystemImagePackage extends MajorRevisionPackage
+ implements IAndroidVersionProvider, IPlatformDependency {
+
+ /** The package version, for platform, add-on and doc packages. */
+ private final AndroidVersion mVersion;
+
+ /** The ABI of the system-image. Must not be null nor empty. */
+ private final String mAbi;
+
+ /**
+ * Creates a new system-image package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public SystemImagePackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ int apiLevel =
+ PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);
+ String codeName =
+ PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);
+ if (codeName.length() == 0) {
+ codeName = null;
+ }
+ mVersion = new AndroidVersion(apiLevel, codeName);
+
+ mAbi = PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ public SystemImagePackage(
+ AndroidVersion platformVersion,
+ int revision,
+ String abi,
+ Properties props,
+ String localOsPath) {
+ this(null /*source*/, platformVersion, revision, abi, props, localOsPath);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected SystemImagePackage(
+ SdkSource source,
+ AndroidVersion platformVersion,
+ int revision,
+ String abi,
+ Properties props,
+ String localOsPath) {
+ super( source, //source
+ props, //properties
+ revision, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ localOsPath //archiveOsPath
+ );
+ mVersion = platformVersion;
+ if (abi == null && props != null) {
+ abi = props.getProperty(PkgProps.SYS_IMG_ABI);
+ }
+ assert abi != null : "To use this SystemImagePackage constructor you must pass an ABI as a parameter or as a PROP_ABI property";
+ mAbi = abi;
+ }
+
+ /**
+ * Creates a {@link BrokenPackage} representing a system image that failed to load
+ * with the regular {@link SdkManager} workflow.
+ *
+ * @param abiDir The SDK/system-images/android-N/abi folder
+ * @param props The properties located in {@code abiDir} or null if not found.
+ * @return A new {@link BrokenPackage} that represents this installed package.
+ */
+ public static Package createBroken(File abiDir, Properties props) {
+ AndroidVersion version = null;
+ String abiType = abiDir.getName();
+ String error = null;
+
+ // Try to load the android version & ABI from the sources.props.
+ // If we don't find them, it would explain why this package is broken.
+ if (props == null) {
+ error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP);
+ } else {
+ try {
+ version = new AndroidVersion(props);
+
+ String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
+ if (abi != null) {
+ abiType = abi;
+ } else {
+ error = String.format("Invalid file %1$s: Missing property %2$s",
+ SdkConstants.FN_SOURCE_PROP,
+ PkgProps.SYS_IMG_ABI);
+ }
+ } catch (AndroidVersionException e) {
+ error = String.format("Invalid file %1$s: %2$s",
+ SdkConstants.FN_SOURCE_PROP,
+ e.getMessage());
+ }
+ }
+
+ if (version == null) {
+ try {
+ // Try to parse the first number out of the platform folder name.
+ String platform = abiDir.getParentFile().getName();
+ platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$
+ int pos = platform.indexOf(' ');
+ if (pos >= 0) {
+ platform = platform.substring(0, pos);
+ }
+ int apiLevel = Integer.parseInt(platform);
+ version = new AndroidVersion(apiLevel, null /*codename*/);
+ } catch (Exception ignore) {
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(
+ String.format("Broken %1$s System Image", getAbiDisplayNameInternal(abiType)));
+ if (version != null) {
+ sb.append(String.format(", API %1$s", version.getApiString()));
+ }
+
+ String shortDesc = sb.toString();
+
+ if (error != null) {
+ sb.append('\n').append(error);
+ }
+
+ String longDesc = sb.toString();
+
+ return new BrokenPackage(props, shortDesc, longDesc,
+ IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
+ version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(),
+ abiDir.getAbsolutePath());
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ */
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ mVersion.saveProperties(props);
+ props.setProperty(PkgProps.SYS_IMG_ABI, mAbi);
+ }
+
+ /** Returns the ABI of the system-image. Cannot be null nor empty. */
+ public String getAbi() {
+ return mAbi;
+ }
+
+ /** Returns a display-friendly name for the ABI of the system-image. */
+ public String getAbiDisplayName() {
+ return getAbiDisplayNameInternal(mAbi);
+ }
+
+ private static String getAbiDisplayNameInternal(String abi) {
+ return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("mips", "MIPS") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns the version of the platform dependency of this package.
+ * <p/>
+ * A system-image has the same {@link AndroidVersion} as the platform it depends on.
+ */
+ @Override @NonNull
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For system images, we use "sysimg-N" where N is the API or the preview codename.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ return "sysimg-" + mVersion.getApiString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return String.format("%1$s System Image%2$s",
+ getAbiDisplayName(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s",
+ getAbiDisplayName(),
+ mVersion.getApiString(),
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the {@code description} field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ s += String.format("\nRequires SDK Platform Android API %1$s",
+ mVersion.getApiString());
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A system-image package is typically installed in SDK/systems/platform/abi.
+ * The name needs to be sanitized to be acceptable as a directory name.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES);
+ folder = new File(folder, SystemImage.ANDROID_PREFIX + mVersion.getApiString());
+
+ // Computes a folder directory using the sanitized abi string.
+ String abi = mAbi;
+ abi = abi.toLowerCase(Locale.US);
+ abi = abi.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+ abi = abi.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ folder = new File(folder, abi);
+ return folder;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof SystemImagePackage) {
+ SystemImagePackage newPkg = (SystemImagePackage)pkg;
+
+ // check they are the same abi and version.
+ return getAbi().equals(newPkg.getAbi()) &&
+ getAndroidVersion().equals(newPkg.getAndroidVersion());
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof SystemImagePackage)) {
+ return false;
+ }
+ SystemImagePackage other = (SystemImagePackage) obj;
+ if (mAbi == null) {
+ if (other.mAbi != null) {
+ return false;
+ }
+ } else if (!mAbi.equals(other.mAbi)) {
+ return false;
+ }
+ if (mVersion == null) {
+ if (other.mVersion != null) {
+ return false;
+ }
+ } else if (!mVersion.equals(other.mVersion)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * For sys img packages, we want to add abi to the sorting key
+ * <em>before<em/> the revision number.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ protected String comparisonKey() {
+ String s = super.comparisonKey();
+ int pos = s.indexOf("|r:"); //$NON-NLS-1$
+ assert pos > 0;
+ s = s.substring(0, pos) +
+ "|abi:" + getAbiDisplayName() + //$NON-NLS-1$
+ s.substring(pos);
+ return s;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
index e4a8fe6..8084c6b 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
@@ -1,337 +1,373 @@
-/*
- * 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.packages;
-
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.SdkManager;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.sources.SdkSource;
-import com.android.sdklib.repository.SdkRepoConstants;
-import com.android.sdklib.util.GrabProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
-import com.android.sdklib.util.GrabProcessOutput.Wait;
-
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Represents a tool XML node in an SDK repository.
- */
-public class ToolPackage extends Package implements IMinPlatformToolsDependency {
-
- /** The value returned by {@link ToolPackage#installId()}. */
- public static final String INSTALL_ID = "tools"; //$NON-NLS-1$
-
- public static final String PROP_MIN_PLATFORM_TOOLS_REV =
- "Platform.MinPlatformToolsRev"; //$NON-NLS-1$
-
- /**
- * The minimal revision of the platform-tools package required by this package
- * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
- */
- private final int mMinPlatformToolsRevision;
-
- /**
- * Creates a new tool package from the attributes and elements of the given XML node.
- * This constructor should throw an exception if the package cannot be created.
- *
- * @param source The {@link SdkSource} where this is loaded from.
- * @param packageNode The XML element being parsed.
- * @param nsUri The namespace URI of the originating XML document, to be able to deal with
- * parameters that vary according to the originating XML schema.
- * @param licenses The licenses loaded from the XML originating document.
- */
- public ToolPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
- super(source, packageNode, nsUri, licenses);
-
- mMinPlatformToolsRevision = XmlParserUtils.getXmlInt(
- packageNode,
- SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,
- MIN_PLATFORM_TOOLS_REV_INVALID);
- if (mMinPlatformToolsRevision == MIN_PLATFORM_TOOLS_REV_INVALID) {
- // This revision number is mandatory starting with sdk-repository-3.xsd
- // and did not exist before. Complain if the URI has level >= 3.
-
- boolean needRevision = false;
-
- Pattern nsPattern = Pattern.compile(SdkRepoConstants.NS_PATTERN);
- Matcher m = nsPattern.matcher(nsUri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- needRevision = Integer.parseInt(version) >= 3;
- } catch (NumberFormatException e) {
- // ignore. needRevision defaults to false
- }
- }
-
- if (needRevision) {
- throw new IllegalArgumentException(
- String.format("Missing %1$s element in %2$s package",
- SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,
- SdkRepoConstants.NODE_PLATFORM_TOOL));
- }
- }
- }
-
- /**
- * Manually create a new package with one archive and the given attributes or properties.
- * This is used to create packages from local directories in which case there must be
- * one archive which URL is the actual target location.
- * <p/>
- * By design, this creates a package with one and only one archive.
- */
- public static Package create(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- return new ToolPackage(source, props, revision, license, description,
- descUrl, archiveOs, archiveArch, archiveOsPath);
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected ToolPackage(
- SdkSource source,
- Properties props,
- int revision,
- String license,
- String description,
- String descUrl,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- super(source,
- props,
- revision,
- license,
- description,
- descUrl,
- archiveOs,
- archiveArch,
- archiveOsPath);
-
- mMinPlatformToolsRevision = Integer.parseInt(
- getProperty(
- props,
- PROP_MIN_PLATFORM_TOOLS_REV,
- Integer.toString(MIN_PLATFORM_TOOLS_REV_INVALID)));
- }
-
- /**
- * The minimal revision of the tools package required by this package if > 0,
- * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
- * <p/>
- * This attribute is mandatory and should not be normally missing.
- */
- @Override
- public int getMinPlatformToolsRevision() {
- return mMinPlatformToolsRevision;
- }
-
- /**
- * Returns a string identifier to install this package from the command line.
- * For tools, we use "tools" since this package is unique.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String installId() {
- return INSTALL_ID;
- }
-
- /**
- * Returns a description of this package that is suitable for a list display.
- * <p/>
- * {@inheritDoc}
- */
- @Override
- public String getListDescription() {
- return String.format("Android SDK Tools%1$s",
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /**
- * Returns a short description for an {@link IDescription}.
- */
- @Override
- public String getShortDescription() {
- return String.format("Android SDK Tools, revision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- /** Returns a long description for an {@link IDescription}. */
- @Override
- public String getLongDescription() {
- String s = getDescription();
- if (s == null || s.length() == 0) {
- s = getShortDescription();
- }
-
- if (s.indexOf("revision") == -1) {
- s += String.format("\nRevision %1$d%2$s",
- getRevision(),
- isObsolete() ? " (Obsolete)" : "");
- }
-
- return s;
- }
-
- /**
- * Computes a potential installation folder if an archive of this package were
- * to be installed right away in the given SDK root.
- * <p/>
- * A "tool" package should always be located in SDK/tools.
- *
- * @param osSdkRoot The OS path of the SDK root folder.
- * @param sdkManager An existing SDK manager to list current platforms and addons.
- * @return A new {@link File} corresponding to the directory to use to install this package.
- */
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return new File(osSdkRoot, SdkConstants.FD_TOOLS);
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- // only one tool package so any tool package is the same item.
- return pkg instanceof ToolPackage;
- }
-
- @Override
- public void saveProperties(Properties props) {
- super.saveProperties(props);
-
- if (getMinPlatformToolsRevision() != MIN_PLATFORM_TOOLS_REV_INVALID) {
- props.setProperty(PROP_MIN_PLATFORM_TOOLS_REV,
- Integer.toString(getMinPlatformToolsRevision()));
- }
- }
-
- /**
- * The tool package executes tools/lib/post_tools_install[.bat|.sh]
- * {@inheritDoc}
- */
- @Override
- public void postInstallHook(Archive archive, final ITaskMonitor monitor, File installFolder) {
- super.postInstallHook(archive, monitor, installFolder);
-
- if (installFolder == null) {
- return;
- }
-
- File libDir = new File(installFolder, SdkConstants.FD_LIB);
- if (!libDir.isDirectory()) {
- return;
- }
-
- String scriptName = "post_tools_install"; //$NON-NLS-1$
- String shell = ""; //$NON-NLS-1$
- if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
- shell = "cmd.exe /c "; //$NON-NLS-1$
- scriptName += ".bat"; //$NON-NLS-1$
- } else {
- scriptName += ".sh"; //$NON-NLS-1$
- }
-
- File scriptFile = new File(libDir, scriptName);
- if (!scriptFile.isFile()) {
- return;
- }
-
- int status = -1;
-
- try {
- Process proc = Runtime.getRuntime().exec(
- shell + scriptName, // command
- null, // environment
- libDir); // working dir
-
- final String tag = scriptName;
- status = GrabProcessOutput.grabProcessOutput(
- proc,
- Wait.WAIT_FOR_PROCESS,
- new IProcessOutput() {
- @Override
- public void out(String line) {
- if (line != null) {
- monitor.log("[%1$s] %2$s", tag, line);
- }
- }
-
- @Override
- public void err(String line) {
- if (line != null) {
- monitor.logError("[%1$s] Error: %2$s", tag, line);
- }
- }
- });
-
- } catch (Exception e) {
- monitor.logError("Exception: %s", e.toString());
- }
-
- if (status != 0) {
- monitor.logError("Failed to execute %s", scriptName);
- return;
- }
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + mMinPlatformToolsRevision;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!super.equals(obj)) {
- return false;
- }
- if (!(obj instanceof ToolPackage)) {
- return false;
- }
- ToolPackage other = (ToolPackage) obj;
- if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) {
- return false;
- }
- return true;
- }
-}
+/*
+ * 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.packages;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.util.GrabProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.Wait;
+
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a tool XML node in an SDK repository.
+ */
+public class ToolPackage extends FullRevisionPackage implements IMinPlatformToolsDependency {
+
+ /** The value returned by {@link ToolPackage#installId()}. */
+ public static final String INSTALL_ID = "tools"; //$NON-NLS-1$
+ /** The value returned by {@link ToolPackage#installId()}. */
+ private static final String INSTALL_ID_PREVIEW = "tools-preview"; //$NON-NLS-1$
+
+ /**
+ * The minimal revision of the platform-tools package required by this package
+ * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
+ */
+ private final FullRevision mMinPlatformToolsRevision;
+
+ /**
+ * Creates a new tool package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ public ToolPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mMinPlatformToolsRevision = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode,
+ SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV));
+
+ if (mMinPlatformToolsRevision.equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
+ // This revision number is mandatory starting with sdk-repository-3.xsd
+ // and did not exist before. Complain if the URI has level >= 3.
+
+ boolean needRevision = false;
+
+ Pattern nsPattern = Pattern.compile(SdkRepoConstants.NS_PATTERN);
+ Matcher m = nsPattern.matcher(nsUri);
+ if (m.matches()) {
+ String version = m.group(1);
+ try {
+ needRevision = Integer.parseInt(version) >= 3;
+ } catch (NumberFormatException e) {
+ // ignore. needRevision defaults to false
+ }
+ }
+
+ if (needRevision) {
+ throw new IllegalArgumentException(
+ String.format("Missing %1$s element in %2$s package",
+ SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,
+ SdkRepoConstants.NODE_PLATFORM_TOOL));
+ }
+ }
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes or properties.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public static Package create(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new ToolPackage(source, props, revision, license, description,
+ descUrl, archiveOs, archiveArch, archiveOsPath);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected ToolPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source,
+ props,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.MIN_PLATFORM_TOOLS_REV, null);
+
+ FullRevision rev = MIN_PLATFORM_TOOLS_REV_INVALID;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ mMinPlatformToolsRevision = rev;
+ }
+
+ /**
+ * The minimal revision of the tools package required by this package if > 0,
+ * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.
+ * <p/>
+ * This attribute is mandatory and should not be normally missing.
+ */
+ @Override
+ public FullRevision getMinPlatformToolsRevision() {
+ return mMinPlatformToolsRevision;
+ }
+
+ /**
+ * Returns a string identifier to install this package from the command line.
+ * For tools, we use "tools" or "tools-preview" since this package is unique.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String installId() {
+ if (getRevision().isPreview()) {
+ return INSTALL_ID_PREVIEW;
+ } else {
+ return INSTALL_ID;
+ }
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return String.format("Android SDK Tools%1$s",
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+ return String.format("Android SDK Tools, revision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /** Returns a long description for an {@link IDescription}. */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+
+ if (s.indexOf("revision") == -1) {
+ s += String.format("\nRevision %1$s%2$s",
+ getRevision().toShortString(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ return s;
+ }
+
+ /**
+ * Computes a potential installation folder if an archive of this package were
+ * to be installed right away in the given SDK root.
+ * <p/>
+ * A "tool" package should always be located in SDK/tools.
+ *
+ * @param osSdkRoot The OS path of the SDK root folder.
+ * @param sdkManager An existing SDK manager to list current platforms and addons.
+ * @return A new {@link File} corresponding to the directory to use to install this package.
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ return new File(osSdkRoot, SdkConstants.FD_TOOLS);
+ }
+
+ /**
+ * Check whether 2 tool packages are the same <em>and</em> have the
+ * same preview bit.
+ */
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ // Only one tool package so any tool package is the same item
+ return sameItemAs(pkg, false /*ignorePreviews*/);
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg, boolean ignorePreviews) {
+ // only one tool package so any tool package is the same item.
+ if (pkg instanceof ToolPackage) {
+ if (ignorePreviews) {
+ return true;
+ } else {
+ // however previews can only match previews by default, unless we ignore that check.
+ return ((ToolPackage) pkg).getRevision().isPreview() ==
+ getRevision().isPreview();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ if (!getMinPlatformToolsRevision().equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {
+ props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
+ getMinPlatformToolsRevision().toShortString());
+ }
+ }
+
+ /**
+ * The tool package executes tools/lib/post_tools_install[.bat|.sh]
+ * {@inheritDoc}
+ */
+ @Override
+ public void postInstallHook(Archive archive, final ITaskMonitor monitor, File installFolder) {
+ super.postInstallHook(archive, monitor, installFolder);
+
+ if (installFolder == null) {
+ return;
+ }
+
+ File libDir = new File(installFolder, SdkConstants.FD_LIB);
+ if (!libDir.isDirectory()) {
+ return;
+ }
+
+ String scriptName = "post_tools_install"; //$NON-NLS-1$
+ String shell = ""; //$NON-NLS-1$
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
+ shell = "cmd.exe /c "; //$NON-NLS-1$
+ scriptName += ".bat"; //$NON-NLS-1$
+ } else {
+ scriptName += ".sh"; //$NON-NLS-1$
+ }
+
+ File scriptFile = new File(libDir, scriptName);
+ if (!scriptFile.isFile()) {
+ return;
+ }
+
+ int status = -1;
+
+ try {
+ Process proc = Runtime.getRuntime().exec(
+ shell + scriptName, // command
+ null, // environment
+ libDir); // working dir
+
+ final String tag = scriptName;
+ status = GrabProcessOutput.grabProcessOutput(
+ proc,
+ Wait.WAIT_FOR_PROCESS,
+ new IProcessOutput() {
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ monitor.log("[%1$s] %2$s", tag, line);
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ monitor.logError("[%1$s] Error: %2$s", tag, line);
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ monitor.logError("Exception: %s", e.toString());
+ }
+
+ if (status != 0) {
+ monitor.logError("Failed to execute %s", scriptName);
+ return;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result
+ + ((mMinPlatformToolsRevision == null) ? 0 : mMinPlatformToolsRevision.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof ToolPackage)) {
+ return false;
+ }
+ ToolPackage other = (ToolPackage) obj;
+ if (mMinPlatformToolsRevision == null) {
+ if (other.mMinPlatformToolsRevision != null) {
+ return false;
+ }
+ } else if (!mMinPlatformToolsRevision.equals(other.mMinPlatformToolsRevision)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkAddonSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
index 39a134b..98bfc5a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
@@ -1,100 +1,109 @@
-/*
- * 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.sources;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.repository.SdkAddonConstants;
-
-import org.w3c.dom.Document;
-
-import java.io.InputStream;
-
-
-/**
- * An sdk-addon source, i.e. a download site for addons and extra packages.
- * A repository describes one or more {@link Package}s available for download.
- */
-public class SdkAddonSource extends SdkSource {
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * repository.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkAddonSource(String url, String uiName) {
- super(url, uiName);
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- @Override
- public boolean isAddonSource() {
- return true;
- }
-
- @Override
- protected String[] getDefaultXmlFileUrls() {
- return new String[] { SdkAddonConstants.URL_DEFAULT_FILENAME };
- }
-
- @Override
- protected int getNsLatestVersion() {
- return SdkAddonConstants.NS_LATEST_VERSION;
- }
-
- @Override
- protected String getNsUri() {
- return SdkAddonConstants.NS_URI;
- }
-
- @Override
- protected String getNsPattern() {
- return SdkAddonConstants.NS_PATTERN;
- }
-
- @Override
- protected String getSchemaUri(int version) {
- return SdkAddonConstants.getSchemaUri(version);
- }
-
- @Override
- protected String getRootElementName() {
- return SdkAddonConstants.NODE_SDK_ADDON;
- }
-
- @Override
- protected InputStream getXsdStream(int version) {
- return SdkAddonConstants.getXsdStream(version);
- }
-
- /**
- * There is no support forward evolution of the sdk-addon schema yet since we
- * currently have only one version.
- *
- * @param xml The input XML stream. Can be null.
- * @return Always null.
- * @null This implementation always return null.
- */
- @Override
- protected Document findAlternateToolsXml(@Nullable InputStream xml) {
- return null;
- }
-}
+/*
+ * 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.sources;
+
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.SdkAddonConstants;
+
+import org.w3c.dom.Document;
+
+import java.io.InputStream;
+
+
+/**
+ * An sdk-addon source, i.e. a download site for addons and extra packages.
+ * A repository describes one or more {@link Package}s available for download.
+ */
+public class SdkAddonSource extends SdkSource {
+
+ /**
+ * Constructs a new source for the given repository URL.
+ * @param url The source URL. Cannot be null. If the URL ends with a /, the default
+ * addon.xml filename will be appended automatically.
+ * @param uiName The UI-visible name of the source. Can be null.
+ */
+ public SdkAddonSource(String url, String uiName) {
+ super(url, uiName);
+ }
+
+ /**
+ * Returns true if this is an addon source.
+ * We only load addons and extras from these sources.
+ */
+ @Override
+ public boolean isAddonSource() {
+ return true;
+ }
+
+ /**
+ * Returns true if this is a system-image source.
+ * We only load system-images from these sources.
+ */
+ @Override
+ public boolean isSysImgSource() {
+ return false;
+ }
+
+
+ @Override
+ protected String[] getDefaultXmlFileUrls() {
+ return new String[] { SdkAddonConstants.URL_DEFAULT_FILENAME };
+ }
+
+ @Override
+ protected int getNsLatestVersion() {
+ return SdkAddonConstants.NS_LATEST_VERSION;
+ }
+
+ @Override
+ protected String getNsUri() {
+ return SdkAddonConstants.NS_URI;
+ }
+
+ @Override
+ protected String getNsPattern() {
+ return SdkAddonConstants.NS_PATTERN;
+ }
+
+ @Override
+ protected String getSchemaUri(int version) {
+ return SdkAddonConstants.getSchemaUri(version);
+ }
+
+ @Override
+ protected String getRootElementName() {
+ return SdkAddonConstants.NODE_SDK_ADDON;
+ }
+
+ @Override
+ protected InputStream getXsdStream(int version) {
+ return SdkAddonConstants.getXsdStream(version);
+ }
+
+ /**
+ * This kind of schema does not support forward-evolution of the &lt;tool&gt; element.
+ *
+ * @param xml The input XML stream. Can be null.
+ * @return Always null.
+ * @null This implementation always return null.
+ */
+ @Override
+ protected Document findAlternateToolsXml(@Nullable InputStream xml) {
+ return null;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
index 07c3a86..09913ed 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
@@ -1,514 +1,524 @@
-/*
- * 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.sources;
-
-import com.android.annotations.Nullable;
-import com.android.sdklib.internal.repository.archives.Archive.Arch;
-import com.android.sdklib.internal.repository.archives.Archive.Os;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.XmlParserUtils;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
-import org.xml.sax.ErrorHandler;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-
-/**
- * An sdk-repository source, i.e. a download site.
- * A repository describes one or more {@link Package}s available for download.
- */
-public class SdkRepoSource extends SdkSource {
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * repository.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkRepoSource(String url, String uiName) {
- super(url, uiName);
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- @Override
- public boolean isAddonSource() {
- return false;
- }
-
- private static String[] sDefaults = null; // lazily allocated in getDefaultXmlFileUrls
-
- @Override
- protected String[] getDefaultXmlFileUrls() {
- if (sDefaults == null) {
- sDefaults = new String[SdkRepoConstants.NS_LATEST_VERSION
- - SdkRepoConstants.NS_SERVER_MIN_VERSION
- + 2];
- int k = 0;
- for (int i = SdkRepoConstants.NS_LATEST_VERSION;
- i >= SdkRepoConstants.NS_SERVER_MIN_VERSION;
- i--) {
- sDefaults[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
- }
- sDefaults[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
- assert k == sDefaults.length;
- }
-
- return sDefaults;
- }
-
- @Override
- protected int getNsLatestVersion() {
- return SdkRepoConstants.NS_LATEST_VERSION;
- }
-
- @Override
- protected String getNsUri() {
- return SdkRepoConstants.NS_URI;
- }
-
- @Override
- protected String getNsPattern() {
- return SdkRepoConstants.NS_PATTERN;
- }
-
- @Override
- protected String getSchemaUri(int version) {
- return SdkRepoConstants.getSchemaUri(version);
- }
-
- @Override
- protected String getRootElementName() {
- return SdkRepoConstants.NODE_SDK_REPOSITORY;
- }
-
- @Override
- protected InputStream getXsdStream(int version) {
- return SdkRepoConstants.getXsdStream(version);
- }
-
- /**
- * The purpose of this method is to support forward evolution of our schema.
- * <p/>
- * At this point, we know that xml does not point to any schema that this version of
- * the tool knows how to process, so it's not one of the possible 1..N versions of our
- * XSD schema.
- * <p/>
- * We thus try to interpret the byte stream as a possible XML stream. It may not be
- * one at all in the first place. If it looks anything line an XML schema, we try to
- * find its &lt;tool&gt; and the &lt;platform-tools&gt; elements. If we find any,
- * we recreate a suitable document that conforms to what we expect from our XSD schema
- * with only those elements.
- * <p/>
- * To be valid, the &lt;tool&gt; and the &lt;platform-tools&gt; elements must have at
- * least one &lt;archive&gt; compatible with this platform.
- * <p/>
- * Starting the sdk-repository schema v3, &lt;tools&gt; has a &lt;min-platform-tools-rev&gt;
- * node, so technically the corresponding XML schema will be usable only if there's a
- * &lt;platform-tools&gt; with the request revision number. We don't enforce that here, as
- * this is done at install time.
- * <p/>
- * If we don't find anything suitable, we drop the whole thing.
- *
- * @param xml The input XML stream. Can be null.
- * @return Either a new XML document conforming to our schema with at least one &lt;tool&gt;
- * and &lt;platform-tools&gt; element or null.
- * @throws IOException if InputStream.reset() fails
- * @null Can return null on failure.
- */
- @Override
- protected Document findAlternateToolsXml(@Nullable InputStream xml) throws IOException {
- return findAlternateToolsXml(xml, null /*errorHandler*/);
- }
-
- /**
- * An alternate version of {@link #findAlternateToolsXml(InputStream)} that allows
- * the caller to specify the XML error handler. The default from the underlying Java
- * XML Xerces parser will dump to stdout/stderr, which is not convenient during unit tests.
- *
- * @param xml The input XML stream. Can be null.
- * @param errorHandler An optional XML error handler. If null, the default will be used.
- * @return Either a new XML document conforming to our schema with at least one &lt;tool&gt;
- * and &lt;platform-tools&gt; element or null.
- * @throws IOException if InputStream.reset() fails
- * @null Can return null on failure.
- * @see #findAlternateToolsXml(InputStream) findAlternateToolsXml() provides more details.
- */
- protected Document findAlternateToolsXml(
- @Nullable InputStream xml,
- @Nullable ErrorHandler errorHandler)
- throws IOException {
- if (xml == null) {
- return null;
- }
-
- // Reset the stream if it supports that operation.
- xml.reset();
-
- // Get an XML document
-
- Document oldDoc = null;
- Document newDoc = null;
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- if (errorHandler != null) {
- builder.setErrorHandler(errorHandler);
- }
-
- oldDoc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
- newDoc = builder.newDocument();
-
- } catch (Exception e) {
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (oldDoc == null || newDoc == null) {
- return null;
- }
-
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-repository
- // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(getNsPattern());
-
- Node oldRoot = null;
- String prefix = null;
- for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (SdkRepoConstants.NODE_SDK_REPOSITORY.equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null && nsPattern.matcher(uri).matches()) {
- oldRoot = child;
- break;
- }
- }
- }
- }
- }
-
- // we must have found the root node, and it must have an XML namespace prefix.
- if (oldRoot == null || prefix == null || prefix.length() == 0) {
- return null;
- }
-
- final String ns = getNsUri();
- Element newRoot = newDoc.createElementNS(ns, getRootElementName());
- newRoot.setPrefix(prefix);
- newDoc.appendChild(newRoot);
- int numTool = 0;
-
- // Find any inner <tool> or <platform-tool> nodes and extract their required parameters
-
- String[] elementNames = {
- SdkRepoConstants.NODE_TOOL,
- SdkRepoConstants.NODE_PLATFORM_TOOL,
- SdkRepoConstants.NODE_LICENSE
- };
-
- Element element = null;
- while ((element = findChild(oldRoot, element, prefix, elementNames)) != null) {
- boolean isElementValid = false;
-
- String name = element.getLocalName();
- if (name == null) {
- name = element.getNodeName();
-
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- name = name.substring(pos + 1);
- }
- }
-
- // To be valid, the tool or platform-tool element must have:
- // - a <revision> element with a number
- // - a <min-platform-tools-rev> element with a number for a <tool> element
- // - an <archives> element with one or more <archive> elements inside
- // - one of the <archive> elements must have an "os" and "arch" attributes
- // compatible with the current platform. Only keep the first such element found.
- // - the <archive> element must contain a <size>, a <checksum> and a <url>.
- // - none of the above for a license element
-
- if (SdkRepoConstants.NODE_LICENSE.equals(name)) {
- isElementValid = true;
-
- } else {
- try {
- Node revision = findChild(element, null, prefix, RepoConstants.NODE_REVISION);
- Node archives = findChild(element, null, prefix, RepoConstants.NODE_ARCHIVES);
-
- if (revision == null || archives == null) {
- continue;
- }
-
- // check revision contains a number
- try {
- String content = revision.getTextContent();
- content = content.trim();
- int rev = Integer.parseInt(content);
- if (rev < 1) {
- continue;
- }
- } catch (NumberFormatException ignore) {
- continue;
- }
-
- if (SdkRepoConstants.NODE_TOOL.equals(name)) {
- Node minPTRev = findChild(element, null, prefix,
- RepoConstants.NODE_MIN_PLATFORM_TOOLS_REV);
-
- if (minPTRev == null) {
- continue;
- }
-
- // check min-platform-tools-rev contains a number
- try {
- String content = minPTRev.getTextContent();
- content = content.trim();
- int rev = Integer.parseInt(content);
- if (rev < 1) {
- continue;
- }
- } catch (NumberFormatException ignore) {
- continue;
- }
- }
-
- Node archive = null;
- while ((archive = findChild(archives,
- archive,
- prefix,
- RepoConstants.NODE_ARCHIVE)) != null) {
- try {
- Os os = (Os) XmlParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_OS,
- Os.values(),
- null /*default*/);
- Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive,
- RepoConstants.ATTR_ARCH,
- Arch.values(),
- Arch.ANY);
- if (os == null || !os.isCompatible() ||
- arch == null || !arch.isCompatible()) {
- continue;
- }
-
- Node node = findChild(archive, null, prefix, RepoConstants.NODE_URL);
- String url = node == null ? null : node.getTextContent().trim();
- if (url == null || url.length() == 0) {
- continue;
- }
-
- node = findChild(archive, null, prefix, RepoConstants.NODE_SIZE);
- long size = 0;
- try {
- size = Long.parseLong(node.getTextContent());
- } catch (Exception e) {
- // pass
- }
- if (size < 1) {
- continue;
- }
-
- node = findChild(archive, null, prefix, RepoConstants.NODE_CHECKSUM);
- // double check that the checksum element contains a type=sha1 attribute
- if (node == null) {
- continue;
- }
- NamedNodeMap attrs = node.getAttributes();
- Node typeNode = attrs.getNamedItem(RepoConstants.ATTR_TYPE);
- if (typeNode == null ||
- !RepoConstants.ATTR_TYPE.equals(typeNode.getNodeName()) ||
- !RepoConstants.SHA1_TYPE.equals(typeNode.getNodeValue())) {
- continue;
- }
- String sha1 = node == null ? null : node.getTextContent().trim();
- if (sha1 == null ||
- sha1.length() != RepoConstants.SHA1_CHECKSUM_LEN) {
- continue;
- }
-
- isElementValid = true;
-
- } catch (Exception ignore1) {
- // pass
- }
- } // while <archive>
- } catch (Exception ignore2) {
- // For debugging it is useful to re-throw the exception.
- // For end-users, not so much. It would be nice to make it
- // happen automatically during unit tests.
- if (System.getenv("TESTING") != null) {
- throw new RuntimeException(ignore2);
- }
- }
- }
-
- if (isElementValid) {
- duplicateNode(newRoot, element, SdkRepoConstants.NS_URI, prefix);
- numTool++;
- }
- } // while <tool>
-
- return numTool > 0 ? newDoc : null;
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
- * element child in a root XML node.
- */
- private Element findChild(Node rootNode, Node after, String prefix, String[] nodeNames) {
- for (int i = 0; i < nodeNames.length; i++) {
- if (nodeNames[i].indexOf(':') < 0) {
- nodeNames[i] = prefix + ":" + nodeNames[i];
- }
- }
- Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling();
- for(; child != null; child = child.getNextSibling()) {
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- for (String nodeName : nodeNames) {
- if (nodeName.equals(child.getNodeName())) {
- return (Element) child;
- }
- }
- }
- return null;
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
- * element child in a root XML node.
- */
- private Node findChild(Node rootNode, Node after, String prefix, String nodeName) {
- return findChild(rootNode, after, prefix, new String[] { nodeName });
- }
-
- /**
- * Helper method used by {@link #findAlternateToolsXml(InputStream)} to duplicate a node
- * and attach it to the given root in the new document.
- */
- private Element duplicateNode(Element newRootNode, Element oldNode,
- String namespaceUri, String prefix) {
- // The implementation here is more or less equivalent to
- //
- // newRoot.appendChild(newDoc.importNode(oldNode, deep=true))
- //
- // except we can't just use importNode() since we need to deal with the fact
- // that the old document is not namespace-aware yet the new one is.
-
- Document newDoc = newRootNode.getOwnerDocument();
- Element newNode = null;
-
- String nodeName = oldNode.getNodeName();
- int pos = nodeName.indexOf(':');
- if (pos > 0 && pos < nodeName.length() - 1) {
- nodeName = nodeName.substring(pos + 1);
- newNode = newDoc.createElementNS(namespaceUri, nodeName);
- newNode.setPrefix(prefix);
- } else {
- newNode = newDoc.createElement(nodeName);
- }
-
- newRootNode.appendChild(newNode);
-
- // Merge in all the attributes
- NamedNodeMap attrs = oldNode.getAttributes();
- for (int i = 0; i < attrs.getLength(); i++) {
- Attr attr = (Attr) attrs.item(i);
- Attr newAttr = null;
-
- String attrName = attr.getNodeName();
- pos = attrName.indexOf(':');
- if (pos > 0 && pos < attrName.length() - 1) {
- attrName = attrName.substring(pos + 1);
- newAttr = newDoc.createAttributeNS(namespaceUri, attrName);
- newAttr.setPrefix(prefix);
- } else {
- newAttr = newDoc.createAttribute(attrName);
- }
-
- newAttr.setNodeValue(attr.getNodeValue());
-
- if (pos > 0) {
- newNode.getAttributes().setNamedItemNS(newAttr);
- } else {
- newNode.getAttributes().setNamedItem(newAttr);
- }
- }
-
- // Merge all child elements and texts
- for (Node child = oldNode.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- duplicateNode(newNode, (Element) child, namespaceUri, prefix);
-
- } else if (child.getNodeType() == Node.TEXT_NODE) {
- Text newText = newDoc.createTextNode(child.getNodeValue());
- newNode.appendChild(newText);
- }
- }
-
- return newNode;
- }
-}
+/*
+ * 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.sources;
+
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PackageParserUtils;
+import com.android.sdklib.repository.RepoConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+import org.xml.sax.ErrorHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+
+/**
+ * An sdk-repository source, i.e. a download site.
+ * A repository describes one or more {@link Package}s available for download.
+ */
+public class SdkRepoSource extends SdkSource {
+
+ /**
+ * Constructs a new source for the given repository URL.
+ * @param url The source URL. Cannot be null. If the URL ends with a /, the default
+ * repository.xml filename will be appended automatically.
+ * @param uiName The UI-visible name of the source. Can be null.
+ */
+ public SdkRepoSource(String url, String uiName) {
+ super(url, uiName);
+ }
+
+ /**
+ * Returns true if this is an addon source.
+ * We only load addons and extras from these sources.
+ */
+ @Override
+ public boolean isAddonSource() {
+ return false;
+ }
+
+ /**
+ * Returns true if this is a system-image source.
+ * We only load system-images from these sources.
+ */
+ @Override
+ public boolean isSysImgSource() {
+ return false;
+ }
+
+ private static String[] sDefaults = null; // lazily allocated in getDefaultXmlFileUrls
+
+ @Override
+ protected String[] getDefaultXmlFileUrls() {
+ if (sDefaults == null) {
+ sDefaults = new String[SdkRepoConstants.NS_LATEST_VERSION
+ - SdkRepoConstants.NS_SERVER_MIN_VERSION
+ + 2];
+ int k = 0;
+ for (int i = SdkRepoConstants.NS_LATEST_VERSION;
+ i >= SdkRepoConstants.NS_SERVER_MIN_VERSION;
+ i--) {
+ sDefaults[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
+ }
+ sDefaults[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
+ assert k == sDefaults.length;
+ }
+
+ return sDefaults;
+ }
+
+ @Override
+ protected int getNsLatestVersion() {
+ return SdkRepoConstants.NS_LATEST_VERSION;
+ }
+
+ @Override
+ protected String getNsUri() {
+ return SdkRepoConstants.NS_URI;
+ }
+
+ @Override
+ protected String getNsPattern() {
+ return SdkRepoConstants.NS_PATTERN;
+ }
+
+ @Override
+ protected String getSchemaUri(int version) {
+ return SdkRepoConstants.getSchemaUri(version);
+ }
+
+ @Override
+ protected String getRootElementName() {
+ return SdkRepoConstants.NODE_SDK_REPOSITORY;
+ }
+
+ @Override
+ protected InputStream getXsdStream(int version) {
+ return SdkRepoConstants.getXsdStream(version);
+ }
+
+ /**
+ * The purpose of this method is to support forward evolution of our schema.
+ * <p/>
+ * At this point, we know that xml does not point to any schema that this version of
+ * the tool knows how to process, so it's not one of the possible 1..N versions of our
+ * XSD schema.
+ * <p/>
+ * We thus try to interpret the byte stream as a possible XML stream. It may not be
+ * one at all in the first place. If it looks anything line an XML schema, we try to
+ * find its &lt;tool&gt; and the &lt;platform-tools&gt; elements. If we find any,
+ * we recreate a suitable document that conforms to what we expect from our XSD schema
+ * with only those elements.
+ * <p/>
+ * To be valid, the &lt;tool&gt; and the &lt;platform-tools&gt; elements must have at
+ * least one &lt;archive&gt; compatible with this platform.
+ * <p/>
+ * Starting the sdk-repository schema v3, &lt;tools&gt; has a &lt;min-platform-tools-rev&gt;
+ * node, so technically the corresponding XML schema will be usable only if there's a
+ * &lt;platform-tools&gt; with the request revision number. We don't enforce that here, as
+ * this is done at install time.
+ * <p/>
+ * If we don't find anything suitable, we drop the whole thing.
+ *
+ * @param xml The input XML stream. Can be null.
+ * @return Either a new XML document conforming to our schema with at least one &lt;tool&gt;
+ * and &lt;platform-tools&gt; element or null.
+ * @throws IOException if InputStream.reset() fails
+ * @null Can return null on failure.
+ */
+ @Override
+ protected Document findAlternateToolsXml(@Nullable InputStream xml) throws IOException {
+ return findAlternateToolsXml(xml, null /*errorHandler*/);
+ }
+
+ /**
+ * An alternate version of {@link #findAlternateToolsXml(InputStream)} that allows
+ * the caller to specify the XML error handler. The default from the underlying Java
+ * XML Xerces parser will dump to stdout/stderr, which is not convenient during unit tests.
+ *
+ * @param xml The input XML stream. Can be null.
+ * @param errorHandler An optional XML error handler. If null, the default will be used.
+ * @return Either a new XML document conforming to our schema with at least one &lt;tool&gt;
+ * and &lt;platform-tools&gt; element or null.
+ * @throws IOException if InputStream.reset() fails
+ * @null Can return null on failure.
+ * @see #findAlternateToolsXml(InputStream) findAlternateToolsXml() provides more details.
+ */
+ protected Document findAlternateToolsXml(
+ @Nullable InputStream xml,
+ @Nullable ErrorHandler errorHandler)
+ throws IOException {
+ if (xml == null) {
+ return null;
+ }
+
+ // Reset the stream if it supports that operation.
+ assert xml.markSupported();
+ xml.reset();
+
+ // Get an XML document
+
+ Document oldDoc = null;
+ Document newDoc = null;
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(false);
+ factory.setValidating(false);
+
+ // Parse the old document using a non namespace aware builder
+ factory.setNamespaceAware(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ if (errorHandler != null) {
+ builder.setErrorHandler(errorHandler);
+ }
+
+ oldDoc = builder.parse(xml);
+
+ // Prepare a new document using a namespace aware builder
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+ newDoc = builder.newDocument();
+
+ } catch (Exception e) {
+ // Failed to get builder factor
+ // Failed to create XML document builder
+ // Failed to parse XML document
+ // Failed to read XML document
+ }
+
+ if (oldDoc == null || newDoc == null) {
+ return null;
+ }
+
+
+ // Check the root element is an XML with at least the following properties:
+ // <sdk:sdk-repository
+ // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
+ //
+ // Note that we don't have namespace support enabled, we just do it manually.
+
+ Pattern nsPattern = Pattern.compile(getNsPattern());
+
+ Node oldRoot = null;
+ String prefix = null;
+ for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ prefix = null;
+ String name = child.getNodeName();
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ prefix = name.substring(0, pos);
+ name = name.substring(pos + 1);
+ }
+ if (SdkRepoConstants.NODE_SDK_REPOSITORY.equals(name)) {
+ NamedNodeMap attrs = child.getAttributes();
+ String xmlns = "xmlns"; //$NON-NLS-1$
+ if (prefix != null) {
+ xmlns += ":" + prefix; //$NON-NLS-1$
+ }
+ Node attr = attrs.getNamedItem(xmlns);
+ if (attr != null) {
+ String uri = attr.getNodeValue();
+ if (uri != null && nsPattern.matcher(uri).matches()) {
+ oldRoot = child;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // we must have found the root node, and it must have an XML namespace prefix.
+ if (oldRoot == null || prefix == null || prefix.length() == 0) {
+ return null;
+ }
+
+ final String ns = getNsUri();
+ Element newRoot = newDoc.createElementNS(ns, getRootElementName());
+ newRoot.setPrefix(prefix);
+ newDoc.appendChild(newRoot);
+ int numTool = 0;
+
+ // Find any inner <tool> or <platform-tool> nodes and extract their required parameters
+
+ String[] elementNames = {
+ SdkRepoConstants.NODE_TOOL,
+ SdkRepoConstants.NODE_PLATFORM_TOOL,
+ SdkRepoConstants.NODE_LICENSE
+ };
+
+ Element element = null;
+ while ((element = findChild(oldRoot, element, prefix, elementNames)) != null) {
+ boolean isElementValid = false;
+
+ String name = element.getLocalName();
+ if (name == null) {
+ name = element.getNodeName();
+
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ name = name.substring(pos + 1);
+ }
+ }
+
+ // To be valid, the tool or platform-tool element must have:
+ // - a <revision> element with a number
+ // - a <min-platform-tools-rev> element with a number for a <tool> element
+ // - an <archives> element with one or more <archive> elements inside
+ // - one of the <archive> elements must have an "os" and "arch" attributes
+ // compatible with the current platform. Only keep the first such element found.
+ // - the <archive> element must contain a <size>, a <checksum> and a <url>.
+ // - none of the above for a license element
+
+ if (SdkRepoConstants.NODE_LICENSE.equals(name)) {
+ isElementValid = true;
+
+ } else {
+ try {
+ Node revision = findChild(element, null, prefix, RepoConstants.NODE_REVISION);
+ Node archives = findChild(element, null, prefix, RepoConstants.NODE_ARCHIVES);
+
+ if (revision == null || archives == null) {
+ continue;
+ }
+
+ // check revision contains a number
+ try {
+ String content = revision.getTextContent();
+ content = content.trim();
+ int rev = Integer.parseInt(content);
+ if (rev < 1) {
+ continue;
+ }
+ } catch (NumberFormatException ignore) {
+ continue;
+ }
+
+ if (SdkRepoConstants.NODE_TOOL.equals(name)) {
+ Node minPTRev = findChild(element, null, prefix,
+ RepoConstants.NODE_MIN_PLATFORM_TOOLS_REV);
+
+ if (minPTRev == null) {
+ continue;
+ }
+
+ // check min-platform-tools-rev contains a number
+ try {
+ String content = minPTRev.getTextContent();
+ content = content.trim();
+ int rev = Integer.parseInt(content);
+ if (rev < 1) {
+ continue;
+ }
+ } catch (NumberFormatException ignore) {
+ continue;
+ }
+ }
+
+ Node archive = null;
+ while ((archive = findChild(archives,
+ archive,
+ prefix,
+ RepoConstants.NODE_ARCHIVE)) != null) {
+ try {
+ Os os = (Os) PackageParserUtils.getEnumAttribute(archive,
+ RepoConstants.ATTR_OS,
+ Os.values(),
+ null /*default*/);
+ Arch arch = (Arch) PackageParserUtils.getEnumAttribute(archive,
+ RepoConstants.ATTR_ARCH,
+ Arch.values(),
+ Arch.ANY);
+ if (os == null || !os.isCompatible() ||
+ arch == null || !arch.isCompatible()) {
+ continue;
+ }
+
+ Node node = findChild(archive, null, prefix, RepoConstants.NODE_URL);
+ String url = node == null ? null : node.getTextContent().trim();
+ if (url == null || url.length() == 0) {
+ continue;
+ }
+
+ node = findChild(archive, null, prefix, RepoConstants.NODE_SIZE);
+ long size = 0;
+ try {
+ size = Long.parseLong(node.getTextContent());
+ } catch (Exception e) {
+ // pass
+ }
+ if (size < 1) {
+ continue;
+ }
+
+ node = findChild(archive, null, prefix, RepoConstants.NODE_CHECKSUM);
+ // double check that the checksum element contains a type=sha1 attribute
+ if (node == null) {
+ continue;
+ }
+ NamedNodeMap attrs = node.getAttributes();
+ Node typeNode = attrs.getNamedItem(RepoConstants.ATTR_TYPE);
+ if (typeNode == null ||
+ !RepoConstants.ATTR_TYPE.equals(typeNode.getNodeName()) ||
+ !RepoConstants.SHA1_TYPE.equals(typeNode.getNodeValue())) {
+ continue;
+ }
+ String sha1 = node == null ? null : node.getTextContent().trim();
+ if (sha1 == null ||
+ sha1.length() != RepoConstants.SHA1_CHECKSUM_LEN) {
+ continue;
+ }
+
+ isElementValid = true;
+
+ } catch (Exception ignore1) {
+ // pass
+ }
+ } // while <archive>
+ } catch (Exception ignore2) {
+ // For debugging it is useful to re-throw the exception.
+ // For end-users, not so much. It would be nice to make it
+ // happen automatically during unit tests.
+ if (System.getenv("TESTING") != null) {
+ throw new RuntimeException(ignore2);
+ }
+ }
+ }
+
+ if (isElementValid) {
+ duplicateNode(newRoot, element, SdkRepoConstants.NS_URI, prefix);
+ numTool++;
+ }
+ } // while <tool>
+
+ return numTool > 0 ? newDoc : null;
+ }
+
+ /**
+ * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
+ * element child in a root XML node.
+ */
+ private Element findChild(Node rootNode, Node after, String prefix, String[] nodeNames) {
+ for (int i = 0; i < nodeNames.length; i++) {
+ if (nodeNames[i].indexOf(':') < 0) {
+ nodeNames[i] = prefix + ":" + nodeNames[i];
+ }
+ }
+ Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling();
+ for(; child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ for (String nodeName : nodeNames) {
+ if (nodeName.equals(child.getNodeName())) {
+ return (Element) child;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
+ * element child in a root XML node.
+ */
+ private Node findChild(Node rootNode, Node after, String prefix, String nodeName) {
+ return findChild(rootNode, after, prefix, new String[] { nodeName });
+ }
+
+ /**
+ * Helper method used by {@link #findAlternateToolsXml(InputStream)} to duplicate a node
+ * and attach it to the given root in the new document.
+ */
+ private Element duplicateNode(Element newRootNode, Element oldNode,
+ String namespaceUri, String prefix) {
+ // The implementation here is more or less equivalent to
+ //
+ // newRoot.appendChild(newDoc.importNode(oldNode, deep=true))
+ //
+ // except we can't just use importNode() since we need to deal with the fact
+ // that the old document is not namespace-aware yet the new one is.
+
+ Document newDoc = newRootNode.getOwnerDocument();
+ Element newNode = null;
+
+ String nodeName = oldNode.getNodeName();
+ int pos = nodeName.indexOf(':');
+ if (pos > 0 && pos < nodeName.length() - 1) {
+ nodeName = nodeName.substring(pos + 1);
+ newNode = newDoc.createElementNS(namespaceUri, nodeName);
+ newNode.setPrefix(prefix);
+ } else {
+ newNode = newDoc.createElement(nodeName);
+ }
+
+ newRootNode.appendChild(newNode);
+
+ // Merge in all the attributes
+ NamedNodeMap attrs = oldNode.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr = (Attr) attrs.item(i);
+ Attr newAttr = null;
+
+ String attrName = attr.getNodeName();
+ pos = attrName.indexOf(':');
+ if (pos > 0 && pos < attrName.length() - 1) {
+ attrName = attrName.substring(pos + 1);
+ newAttr = newDoc.createAttributeNS(namespaceUri, attrName);
+ newAttr.setPrefix(prefix);
+ } else {
+ newAttr = newDoc.createAttribute(attrName);
+ }
+
+ newAttr.setNodeValue(attr.getNodeValue());
+
+ if (pos > 0) {
+ newNode.getAttributes().setNamedItemNS(newAttr);
+ } else {
+ newNode.getAttributes().setNamedItem(newAttr);
+ }
+ }
+
+ // Merge all child elements and texts
+ for (Node child = oldNode.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ duplicateNode(newNode, (Element) child, namespaceUri, prefix);
+
+ } else if (child.getNodeType() == Node.TEXT_NODE) {
+ Text newText = newDoc.createTextNode(child.getNodeValue());
+ newNode.appendChild(newText);
+ }
+ }
+
+ return newNode;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
index 45646f0..2558e71 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
@@ -1,956 +1,991 @@
-/*
- * 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.sources;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.UrlOpener;
-import com.android.sdklib.internal.repository.UrlOpener.CanceledByUserException;
-import com.android.sdklib.internal.repository.packages.AddonPackage;
-import com.android.sdklib.internal.repository.packages.DocPackage;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformPackage;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.internal.repository.packages.SamplePackage;
-import com.android.sdklib.internal.repository.packages.SourcePackage;
-import com.android.sdklib.internal.repository.packages.SystemImagePackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
-import com.android.sdklib.repository.RepoConstants;
-import com.android.sdklib.repository.SdkAddonConstants;
-import com.android.sdklib.repository.SdkRepoConstants;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.net.ssl.SSLKeyException;
-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;
-
-/**
- * An sdk-addon or sdk-repository source, i.e. a download site.
- * It may be a full repository or an add-on only repository.
- * A repository describes one or {@link Package}s available for download.
- */
-public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
-
- private String mUrl;
-
- private Package[] mPackages;
- private String mDescription;
- private String mFetchError;
- private final String mUiName;
-
- private static final SdkSourceProperties sSourcesProps = new SdkSourceProperties();
-
- /**
- * Constructs a new source for the given repository URL.
- * @param url The source URL. Cannot be null. If the URL ends with a /, the default
- * repository.xml filename will be appended automatically.
- * @param uiName The UI-visible name of the source. Can be null.
- */
- public SdkSource(String url, String uiName) {
-
- // URLs should not be null and should not have whitespace.
- if (url == null) {
- url = "";
- }
- url = url.trim();
-
- // if the URL ends with a /, it must be "directory" resource,
- // in which case we automatically add the default file that will
- // looked for. This way it will be obvious to the user which
- // resource we are actually trying to fetch.
- if (url.endsWith("/")) { //$NON-NLS-1$
- String[] names = getDefaultXmlFileUrls();
- if (names.length > 0) {
- url += names[0];
- }
- }
-
- if (uiName == null) {
- uiName = sSourcesProps.getProperty(SdkSourceProperties.KEY_NAME, url, null);
- } else {
- sSourcesProps.setProperty(SdkSourceProperties.KEY_NAME, url, uiName);
- }
-
- mUrl = url;
- mUiName = uiName;
- setDefaultDescription();
- }
-
- /**
- * Returns true if this is an addon source.
- * We only load addons and extras from these sources.
- */
- public abstract boolean isAddonSource();
-
- /**
- * Returns the basename of the default URLs to try to download the
- * XML manifest.
- * E.g. this is typically SdkRepoConstants.URL_DEFAULT_XML_FILE
- * or SdkAddonConstants.URL_DEFAULT_XML_FILE
- */
- protected abstract String[] getDefaultXmlFileUrls();
-
- /** Returns SdkRepoConstants.NS_LATEST_VERSION or SdkAddonConstants.NS_LATEST_VERSION. */
- protected abstract int getNsLatestVersion();
-
- /** Returns SdkRepoConstants.NS_URI or SdkAddonConstants.NS_URI. */
- protected abstract String getNsUri();
-
- /** Returns SdkRepoConstants.NS_PATTERN or SdkAddonConstants.NS_PATTERN. */
- protected abstract String getNsPattern();
-
- /** Returns SdkRepoConstants.getSchemaUri() or SdkAddonConstants.getSchemaUri(). */
- protected abstract String getSchemaUri(int version);
-
- /* Returns SdkRepoConstants.NODE_SDK_REPOSITORY or SdkAddonConstants.NODE_SDK_ADDON. */
- protected abstract String getRootElementName();
-
- /** Returns SdkRepoConstants.getXsdStream() or SdkAddonConstants.getXsdStream(). */
- protected abstract InputStream getXsdStream(int version);
-
- /**
- * In case we fail to load an XML, examine the XML to see if it matches a <b>future</b>
- * schema that as at least a <code>tools</code> node that we could load to update the
- * SDK Manager.
- *
- * @param xml The input XML stream. Can be null.
- * @return Null on failure, otherwise returns an XML DOM with just the tools we
- * need to update this SDK Manager.
- * @null Can return null on failure.
- */
- protected abstract Document findAlternateToolsXml(@Nullable InputStream xml)
- throws IOException;
-
- /**
- * Two repo source are equal if they have the same URL.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SdkSource) {
- SdkSource rs = (SdkSource) obj;
- return rs.getUrl().equals(this.getUrl());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return mUrl.hashCode();
- }
-
- /**
- * Implementation of the {@link Comparable} interface.
- * Simply compares the URL using the string's default ordering.
- */
- @Override
- public int compareTo(SdkSource rhs) {
- return this.getUrl().compareTo(rhs.getUrl());
- }
-
- /**
- * Returns the UI-visible name of the source. Can be null.
- */
- public String getUiName() {
- return mUiName;
- }
-
- /** Returns the URL of the XML file for this source. */
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * Returns the list of known packages found by the last call to load().
- * This is null when the source hasn't been loaded yet -- caller should
- * then call {@link #load} to load the packages.
- */
- public Package[] getPackages() {
- return mPackages;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void setPackages(Package[] packages) {
- mPackages = packages;
-
- if (mPackages != null) {
- // Order the packages.
- Arrays.sort(mPackages, null);
- }
- }
-
- /**
- * Clear the internal packages list. After this call, {@link #getPackages()} will return
- * null till load() is called.
- */
- public void clearPackages() {
- setPackages(null);
- }
-
- /**
- * Indicates if the source is enabled.
- * <p/>
- * A 3rd-party add-on source can be disabled by the user to prevent from loading it.
- *
- * @return True if the source is enabled (default is true).
- */
- public boolean isEnabled() {
- // A URL is enabled if it's not in the disabled list.
- return sSourcesProps.getProperty(SdkSourceProperties.KEY_DISABLED, mUrl, null) == null;
- }
-
- /**
- * Changes whether the source is marked as enabled.
- * <p/>
- * When <em>changing</em> the enable state, the current package list is purged
- * and the next {@code load} will either return an empty list (if disabled) or
- * the actual package list (if enabled.)
- *
- * @param enabled True for the source to be enabled (can be loaded), false otherwise.
- */
- public void setEnabled(boolean enabled) {
- if (enabled != isEnabled()) {
- // First we clear the current package list, which will force the
- // next load() to actually set the package list as desired.
- clearPackages();
-
- sSourcesProps.setProperty(SdkSourceProperties.KEY_DISABLED, mUrl,
- enabled ? null /*remove*/ : "disabled"); //$NON-NLS-1$
- }
- }
-
- /**
- * Returns the short description of the source, if not null.
- * Otherwise returns the default Object toString result.
- * <p/>
- * This is mostly helpful for debugging.
- * For UI display, use the {@link IDescription} interface.
- */
- @Override
- public String toString() {
- String s = getShortDescription();
- if (s != null) {
- return s;
- }
- return super.toString();
- }
-
- @Override
- public String getShortDescription() {
-
- if (mUiName != null && mUiName.length() > 0) {
-
- String host = "malformed URL";
-
- try {
- URL u = new URL(mUrl);
- host = u.getHost();
- } catch (MalformedURLException e) {
- }
-
- return String.format("%1$s (%2$s)", mUiName, host);
-
- }
- return mUrl;
- }
-
- @Override
- public String getLongDescription() {
- // Note: in a normal workflow, mDescription is filled by setDefaultDescription().
- // However for packages made by unit tests or such, this can be null.
- return mDescription == null ? "" : mDescription; //$NON-NLS-1$
- }
-
- /**
- * Returns the last fetch error description.
- * If there was no error, returns null.
- */
- public String getFetchError() {
- return mFetchError;
- }
-
- /**
- * Tries to fetch the repository index for the given URL and updates the package list.
- * When a source is disabled, this create an empty non-null package list.
- * <p/>
- * Callers can get the package list using {@link #getPackages()} after this. It will be
- * null in case of error, in which case {@link #getFetchError()} can be used to an
- * error message.
- */
- public void load(DownloadCache cache, ITaskMonitor monitor, boolean forceHttp) {
-
- setDefaultDescription();
- monitor.setProgressMax(7);
-
- if (!isEnabled()) {
- setPackages(new Package[0]);
- mDescription += "\nSource is disabled.";
- monitor.incProgress(7);
- return;
- }
-
- String url = mUrl;
- if (forceHttp) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- monitor.setDescription("Fetching URL: %1$s", url);
- monitor.incProgress(1);
-
- mFetchError = null;
- Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
- String[] validationError = new String[] { null };
- Exception[] exception = new Exception[] { null };
- Document validatedDoc = null;
- boolean usingAlternateXml = false;
- boolean usingAlternateUrl = false;
- String validatedUri = null;
-
- String[] defaultNames = getDefaultXmlFileUrls();
- String firstDefaultName = defaultNames.length > 0 ? defaultNames[0] : "";
-
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- xml = null;
- }
- }
-
- // FIXME: this is a quick fix to support an alternate upgrade path.
- // The whole logic below needs to be updated.
- if (xml == null && defaultNames.length > 0) {
- ITaskMonitor subMonitor = monitor.createSubMonitor(1);
- subMonitor.setProgressMax(defaultNames.length);
-
- String baseUrl = url;
- if (!baseUrl.endsWith("/")) {
- int pos = baseUrl.lastIndexOf('/');
- if (pos > 0) {
- baseUrl = baseUrl.substring(0, pos + 1);
- }
- }
-
- for(String name : defaultNames) {
- String newUrl = baseUrl + name;
- if (newUrl.equals(url)) {
- continue;
- }
- xml = fetchUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
- if (xml != null) {
- int version = getXmlSchemaVersion(xml);
- if (version == 0) {
- xml = null;
- } else {
- url = newUrl;
- subMonitor.incProgress(
- subMonitor.getProgressMax() - subMonitor.getProgress());
- break;
- }
- }
- }
- } else {
- monitor.incProgress(1);
- }
-
- // If the original URL can't be fetched
- // and the URL doesn't explicitly end with our filename
- // and it wasn't an HTTP authentication operation canceled by the user
- // then make another tentative after changing the URL.
- if (xml == null
- && !url.endsWith(firstDefaultName)
- && !(exception[0] instanceof CanceledByUserException)) {
- if (!url.endsWith("/")) { //$NON-NLS-1$
- url += "/"; //$NON-NLS-1$
- }
- url += firstDefaultName;
-
- xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
- usingAlternateUrl = true;
- } else {
- monitor.incProgress(1);
- }
-
- // FIXME this needs to revisited.
- if (xml != null) {
- monitor.setDescription("Validate XML: %1$s", url);
-
- ITaskMonitor subMonitor = monitor.createSubMonitor(2);
- subMonitor.setProgressMax(2);
- for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
- // Explore the XML to find the potential XML schema version
- int version = getXmlSchemaVersion(xml);
-
- if (version >= 1 && version <= getNsLatestVersion()) {
- // This should be a version we can handle. Try to validate it
- // and report any error as invalid XML syntax,
-
- String uri = validateXml(xml, url, version, validationError, validatorFound);
- if (uri != null) {
- // Validation was successful
- validatedDoc = getDocument(xml, monitor);
- validatedUri = uri;
-
- if (usingAlternateUrl && validatedDoc != null) {
- // If the second tentative succeeded, indicate it in the console
- // with the URL that worked.
- monitor.log("Repository found at %1$s", url);
-
- // Keep the modified URL
- mUrl = url;
- }
- } else if (validatorFound[0].equals(Boolean.FALSE)) {
- // Validation failed because this JVM lacks a proper XML Validator
- mFetchError = validationError[0];
- } else {
- // We got a validator but validation failed. We know there's
- // what looks like a suitable root element with a suitable XMLNS
- // so it must be a genuine error of an XML not conforming to the schema.
- }
- } else if (version > getNsLatestVersion()) {
- // The schema used is more recent than what is supported by this tool.
- // Tell the user to upgrade, pointing him to the right version of the tool
- // package.
-
- try {
- validatedDoc = findAlternateToolsXml(xml);
- } catch (IOException e) {
- // Failed, will be handled below.
- }
- if (validatedDoc != null) {
- validationError[0] = null; // remove error from XML validation
- validatedUri = getNsUri();
- usingAlternateXml = true;
- }
-
- } else if (version < 1 && tryOtherUrl == 0 && !usingAlternateUrl) {
- // This is obviously not one of our documents.
- mFetchError = String.format(
- "Failed to validate the XML for the repository at URL '%1$s'",
- url);
-
- // If we haven't already tried the alternate URL, let's do it now.
- // We don't capture any fetch exception that happen during the second
- // fetch in order to avoid hidding any previous fetch errors.
- if (!url.endsWith(firstDefaultName)) {
- if (!url.endsWith("/")) { //$NON-NLS-1$
- url += "/"; //$NON-NLS-1$
- }
- url += firstDefaultName;
-
- xml = fetchUrl(url, cache, subMonitor.createSubMonitor(1),
- null /* outException */);
- subMonitor.incProgress(1);
- // Loop to try the alternative document
- if (xml != null) {
- usingAlternateUrl = true;
- continue;
- }
- }
- } else if (version < 1 && usingAlternateUrl && mFetchError == null) {
- // The alternate URL is obviously not a valid XML either.
- // We only report the error if we failed to produce one earlier.
- mFetchError = String.format(
- "Failed to validate the XML for the repository at URL '%1$s'",
- url);
- }
-
- // If we get here either we succeeded or we ran out of alternatives.
- break;
- }
- }
-
- // If any exception was handled during the URL fetch, display it now.
- if (exception[0] != null) {
- mFetchError = "Failed to fetch URL";
-
- String reason = null;
- if (exception[0] instanceof FileNotFoundException) {
- // FNF has no useful getMessage, so we need to special handle it.
- reason = "File not found";
- mFetchError += ": " + reason;
- } else if (exception[0] instanceof SSLKeyException) {
- // That's a common error and we have a pref for it.
- reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
- mFetchError += ": HTTPS SSL error";
- } else if (exception[0].getMessage() != null) {
- reason = exception[0].getMessage();
- } else {
- // We don't know what's wrong. Let's give the exception class at least.
- reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
- }
-
- monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
- }
-
- if (validationError[0] != null) {
- monitor.logError("%s", validationError[0]); //$NON-NLS-1$
- }
-
- // Stop here if we failed to validate the XML. We don't want to load it.
- if (validatedDoc == null) {
- return;
- }
-
- if (usingAlternateXml) {
- // We found something using the "alternate" XML schema (that is the one made up
- // to support schema upgrades). That means the user can only install the tools
- // and needs to upgrade them before it download more stuff.
-
- // Is the manager running from inside ADT?
- // We check that com.android.ide.eclipse.adt.AdtPlugin exists using reflection.
-
- boolean isADT = false;
- try {
- Class<?> adt = Class.forName("com.android.ide.eclipse.adt.AdtPlugin"); //$NON-NLS-1$
- isADT = (adt != null);
- } catch (ClassNotFoundException e) {
- // pass
- }
-
- String info;
- if (isADT) {
- info = "This repository requires a more recent version of ADT. Please update the Eclipse Android plugin.";
- mDescription = "This repository requires a more recent version of ADT, the Eclipse Android plugin.\nYou must update it before you can see other new packages.";
-
- } else {
- info = "This repository requires a more recent version of the Tools. Please update.";
- mDescription = "This repository requires a more recent version of the Tools.\nYou must update it before you can see other new packages.";
- }
-
- mFetchError = mFetchError == null ? info : mFetchError + ". " + info;
- }
-
- monitor.incProgress(1);
-
- if (xml != null) {
- monitor.setDescription("Parse XML: %1$s", url);
- monitor.incProgress(1);
- parsePackages(validatedDoc, validatedUri, monitor);
- if (mPackages == null || mPackages.length == 0) {
- mDescription += "\nNo packages found.";
- } else if (mPackages.length == 1) {
- mDescription += "\nOne package found.";
- } else {
- mDescription += String.format("\n%1$d packages found.", mPackages.length);
- }
- }
-
- // done
- monitor.incProgress(1);
- }
-
- private void setDefaultDescription() {
- if (isAddonSource()) {
- String desc = "";
-
- if (mUiName != null) {
- desc += "Add-on Provider: " + mUiName;
- desc += "\n";
- }
- desc += "Add-on URL: " + mUrl;
-
- mDescription = desc;
- } else {
- mDescription = String.format("SDK Source: %1$s", mUrl);
- }
- }
-
- /**
- * 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: <br/>
- * URL Connection:
- *
- * @param urlString The URL to load, as a string.
- * @param monitor {@link ITaskMonitor} related to this URL.
- * @param outException If non null, where to store any exception that
- * happens during the fetch.
- * @see UrlOpener UrlOpener, which handles all URL logic.
- */
- private InputStream fetchUrl(String urlString,
- DownloadCache cache,
- ITaskMonitor monitor,
- Exception[] outException) {
- try {
- return cache.openCachedUrl(urlString, monitor);
- } catch (Exception e) {
- if (outException != null) {
- outException[0] = e;
- }
- }
-
- return null;
- }
-
- /**
- * Validates this XML against one of the requested SDK Repository schemas.
- * If the XML was correctly validated, returns the schema that worked.
- * If it doesn't validate, returns null and stores the error in outError[0].
- * If we can't find a validator, returns null and set validatorFound[0] to false.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected String validateXml(InputStream xml, String url, int version,
- String[] outError, Boolean[] validatorFound) {
-
- if (xml == null) {
- return null;
- }
-
- try {
- Validator validator = getValidator(version);
-
- if (validator == null) {
- validatorFound[0] = Boolean.FALSE;
- outError[0] = String.format(
- "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
- url);
- return null;
- }
-
- validatorFound[0] = Boolean.TRUE;
-
- // Reset the stream if it supports that operation.
- xml.reset();
-
- // Validation throws a bunch of possible Exceptions on failure.
- validator.validate(new StreamSource(xml));
- return getSchemaUri(version);
-
- } catch (SAXParseException e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
- url,
- e.getLineNumber(),
- e.getColumnNumber(),
- e.toString());
-
- } catch (Exception e) {
- outError[0] = String.format(
- "XML verification failed for %1$s.\nError: %2$s",
- url,
- e.toString());
- }
- return null;
- }
-
- /**
- * Manually parses the root element of the XML to extract the schema version
- * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"
- * declaration.
- *
- * @return 1..{@link SdkRepoConstants#NS_LATEST_VERSION} for a valid schema version
- * or 0 if no schema could be found.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected int getXmlSchemaVersion(InputStream xml) {
- if (xml == null) {
- return 0;
- }
-
- // Get an XML document
- Document doc = null;
- try {
- xml.reset();
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(false);
- factory.setValidating(false);
-
- // Parse the old document using a non namespace aware builder
- factory.setNamespaceAware(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- doc = builder.parse(xml);
-
- // Prepare a new document using a namespace aware builder
- factory.setNamespaceAware(true);
- builder = factory.newDocumentBuilder();
-
- } catch (Exception e) {
- // Failed to reset XML stream
- // Failed to get builder factor
- // Failed to create XML document builder
- // Failed to parse XML document
- // Failed to read XML document
- }
-
- if (doc == null) {
- return 0;
- }
-
- // Check the root element is an XML with at least the following properties:
- // <sdk:sdk-repository
- // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
- //
- // Note that we don't have namespace support enabled, we just do it manually.
-
- Pattern nsPattern = Pattern.compile(getNsPattern());
-
- String prefix = null;
- for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- prefix = null;
- String name = child.getNodeName();
- int pos = name.indexOf(':');
- if (pos > 0 && pos < name.length() - 1) {
- prefix = name.substring(0, pos);
- name = name.substring(pos + 1);
- }
- if (getRootElementName().equals(name)) {
- NamedNodeMap attrs = child.getAttributes();
- String xmlns = "xmlns"; //$NON-NLS-1$
- if (prefix != null) {
- xmlns += ":" + prefix; //$NON-NLS-1$
- }
- Node attr = attrs.getNamedItem(xmlns);
- if (attr != null) {
- String uri = attr.getNodeValue();
- if (uri != null) {
- Matcher m = nsPattern.matcher(uri);
- if (m.matches()) {
- String version = m.group(1);
- try {
- return Integer.parseInt(version);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return 0;
- }
-
- /**
- * Helper method that returns a validator for our XSD, or null if the current Java
- * implementation can't process XSD schemas.
- *
- * @param version The version of the XML Schema.
- * See {@link SdkRepoConstants#getXsdStream(int)}
- */
- private Validator getValidator(int version) throws SAXException {
- InputStream xsdStream = getXsdStream(version);
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-
- if (factory == null) {
- return null;
- }
-
- // 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 == null ? null : schema.newValidator();
-
- // We don't want the default handler, which by default dumps errors to stderr.
- validator.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) throws SAXException {
- // pass
- }
- @Override
- public void fatalError(SAXParseException e) throws SAXException {
- throw e;
- }
- @Override
- public void error(SAXParseException e) throws SAXException {
- throw e;
- }
- });
-
- return validator;
- }
-
- /**
- * Parse all packages defined in the SDK Repository XML and creates
- * a new mPackages array with them.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
-
- Node root = getFirstChild(doc, nsUri, getRootElementName());
- if (root != null) {
-
- ArrayList<Package> packages = new ArrayList<Package>();
-
- // Parse license definitions
- HashMap<String, String> licenses = new HashMap<String, String>();
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI()) &&
- child.getLocalName().equals(RepoConstants.NODE_LICENSE)) {
- Node id = child.getAttributes().getNamedItem(RepoConstants.ATTR_ID);
- if (id != null) {
- licenses.put(id.getNodeValue(), child.getTextContent());
- }
- }
- }
-
- // Parse packages
- for (Node child = root.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.equals(child.getNamespaceURI())) {
- String name = child.getLocalName();
- Package p = null;
-
- try {
- // We can load addon and extra packages from all sources, either
- // internal or user sources.
- if (SdkAddonConstants.NODE_ADD_ON.equals(name)) {
- p = new AddonPackage(this, child, nsUri, licenses);
-
- } else if (SdkAddonConstants.NODE_EXTRA.equals(name)) {
- p = new ExtraPackage(this, child, nsUri, licenses);
-
- } else if (!isAddonSource()) {
- // We only load platform, doc and tool packages from internal
- // sources, never from user sources.
- if (SdkRepoConstants.NODE_PLATFORM.equals(name)) {
- p = new PlatformPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_DOC.equals(name)) {
- p = new DocPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_TOOL.equals(name)) {
- p = new ToolPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_PLATFORM_TOOL.equals(name)) {
- p = new PlatformToolPackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SAMPLE.equals(name)) {
- p = new SamplePackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SYSTEM_IMAGE.equals(name)) {
- p = new SystemImagePackage(this, child, nsUri, licenses);
- } else if (SdkRepoConstants.NODE_SOURCE.equals(name)) {
- p = new SourcePackage(this, child, nsUri, licenses);
- }
- }
-
- if (p != null) {
- packages.add(p);
- monitor.logVerbose("Found %1$s", p.getShortDescription());
- }
- } catch (Exception e) {
- // Ignore invalid packages
- monitor.logError("Ignoring invalid %1$s element: %2$s", name, e.toString());
- }
- }
- }
-
- setPackages(packages.toArray(new Package[packages.size()]));
-
- return true;
- }
-
- 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 nsUri, String xmlLocalName) {
-
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- nsUri.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.
- *
- * On error, returns null and prints a (hopefully) useful message on the monitor.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setIgnoringComments(true);
- factory.setNamespaceAware(true);
-
- DocumentBuilder builder = factory.newDocumentBuilder();
- xml.reset();
- Document doc = builder.parse(new InputSource(xml));
-
- return doc;
- } catch (ParserConfigurationException e) {
- monitor.logError("Failed to create XML document builder");
-
- } catch (SAXException e) {
- monitor.logError("Failed to parse XML document");
-
- } catch (IOException e) {
- monitor.logError("Failed to read XML document");
- }
-
- return null;
- }
-}
+/*
+ * 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.sources;
+
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.internal.repository.CanceledByUserException;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.packages.AddonPackage;
+import com.android.sdklib.internal.repository.packages.DocPackage;
+import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformPackage;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
+import com.android.sdklib.internal.repository.packages.SamplePackage;
+import com.android.sdklib.internal.repository.packages.SourcePackage;
+import com.android.sdklib.internal.repository.packages.SystemImagePackage;
+import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
+import com.android.sdklib.repository.RepoConstants;
+import com.android.sdklib.repository.SdkAddonConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.SSLKeyException;
+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;
+
+/**
+ * An sdk-addon or sdk-repository source, i.e. a download site.
+ * It may be a full repository or an add-on only repository.
+ * A repository describes one or {@link Package}s available for download.
+ */
+public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
+
+ private String mUrl;
+
+ private Package[] mPackages;
+ private String mDescription;
+ private String mFetchError;
+ private final String mUiName;
+
+ private static final SdkSourceProperties sSourcesProps = new SdkSourceProperties();
+
+ /**
+ * Constructs a new source for the given repository URL.
+ * @param url The source URL. Cannot be null. If the URL ends with a /, the default
+ * repository.xml filename will be appended automatically.
+ * @param uiName The UI-visible name of the source. Can be null.
+ */
+ public SdkSource(String url, String uiName) {
+
+ // URLs should not be null and should not have whitespace.
+ if (url == null) {
+ url = "";
+ }
+ url = url.trim();
+
+ // if the URL ends with a /, it must be "directory" resource,
+ // in which case we automatically add the default file that will
+ // looked for. This way it will be obvious to the user which
+ // resource we are actually trying to fetch.
+ if (url.endsWith("/")) { //$NON-NLS-1$
+ String[] names = getDefaultXmlFileUrls();
+ if (names.length > 0) {
+ url += names[0];
+ }
+ }
+
+ if (uiName == null) {
+ uiName = sSourcesProps.getProperty(SdkSourceProperties.KEY_NAME, url, null);
+ } else {
+ sSourcesProps.setProperty(SdkSourceProperties.KEY_NAME, url, uiName);
+ }
+
+ mUrl = url;
+ mUiName = uiName;
+ setDefaultDescription();
+ }
+
+ /**
+ * Returns true if this is an addon source.
+ * We only load addons and extras from these sources.
+ */
+ public abstract boolean isAddonSource();
+
+ /**
+ * Returns true if this is a system-image source.
+ * We only load system-images from these sources.
+ */
+ public abstract boolean isSysImgSource();
+
+
+ /**
+ * Returns the basename of the default URLs to try to download the
+ * XML manifest.
+ * E.g. this is typically SdkRepoConstants.URL_DEFAULT_XML_FILE
+ * or SdkAddonConstants.URL_DEFAULT_XML_FILE
+ */
+ protected abstract String[] getDefaultXmlFileUrls();
+
+ /** Returns SdkRepoConstants.NS_LATEST_VERSION or SdkAddonConstants.NS_LATEST_VERSION. */
+ protected abstract int getNsLatestVersion();
+
+ /** Returns SdkRepoConstants.NS_URI or SdkAddonConstants.NS_URI. */
+ protected abstract String getNsUri();
+
+ /** Returns SdkRepoConstants.NS_PATTERN or SdkAddonConstants.NS_PATTERN. */
+ protected abstract String getNsPattern();
+
+ /** Returns SdkRepoConstants.getSchemaUri() or SdkAddonConstants.getSchemaUri(). */
+ protected abstract String getSchemaUri(int version);
+
+ /* Returns SdkRepoConstants.NODE_SDK_REPOSITORY or SdkAddonConstants.NODE_SDK_ADDON. */
+ protected abstract String getRootElementName();
+
+ /** Returns SdkRepoConstants.getXsdStream() or SdkAddonConstants.getXsdStream(). */
+ protected abstract InputStream getXsdStream(int version);
+
+ /**
+ * In case we fail to load an XML, examine the XML to see if it matches a <b>future</b>
+ * schema that as at least a <code>tools</code> node that we could load to update the
+ * SDK Manager.
+ *
+ * @param xml The input XML stream. Can be null.
+ * @return Null on failure, otherwise returns an XML DOM with just the tools we
+ * need to update this SDK Manager.
+ * @null Can return null on failure.
+ */
+ protected abstract Document findAlternateToolsXml(@Nullable InputStream xml)
+ throws IOException;
+
+ /**
+ * Two repo source are equal if they have the same URL.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SdkSource) {
+ SdkSource rs = (SdkSource) obj;
+ return rs.getUrl().equals(this.getUrl());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mUrl.hashCode();
+ }
+
+ /**
+ * Implementation of the {@link Comparable} interface.
+ * Simply compares the URL using the string's default ordering.
+ */
+ @Override
+ public int compareTo(SdkSource rhs) {
+ return this.getUrl().compareTo(rhs.getUrl());
+ }
+
+ /**
+ * Returns the UI-visible name of the source. Can be null.
+ */
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /** Returns the URL of the XML file for this source. */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Returns the list of known packages found by the last call to load().
+ * This is null when the source hasn't been loaded yet -- caller should
+ * then call {@link #load} to load the packages.
+ */
+ public Package[] getPackages() {
+ return mPackages;
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void setPackages(Package[] packages) {
+ mPackages = packages;
+
+ if (mPackages != null) {
+ // Order the packages.
+ Arrays.sort(mPackages, null);
+ }
+ }
+
+ /**
+ * Clear the internal packages list. After this call, {@link #getPackages()} will return
+ * null till load() is called.
+ */
+ public void clearPackages() {
+ setPackages(null);
+ }
+
+ /**
+ * Indicates if the source is enabled.
+ * <p/>
+ * A 3rd-party add-on source can be disabled by the user to prevent from loading it.
+ *
+ * @return True if the source is enabled (default is true).
+ */
+ public boolean isEnabled() {
+ // A URL is enabled if it's not in the disabled list.
+ return sSourcesProps.getProperty(SdkSourceProperties.KEY_DISABLED, mUrl, null) == null;
+ }
+
+ /**
+ * Changes whether the source is marked as enabled.
+ * <p/>
+ * When <em>changing</em> the enable state, the current package list is purged
+ * and the next {@code load} will either return an empty list (if disabled) or
+ * the actual package list (if enabled.)
+ *
+ * @param enabled True for the source to be enabled (can be loaded), false otherwise.
+ */
+ public void setEnabled(boolean enabled) {
+ if (enabled != isEnabled()) {
+ // First we clear the current package list, which will force the
+ // next load() to actually set the package list as desired.
+ clearPackages();
+
+ sSourcesProps.setProperty(SdkSourceProperties.KEY_DISABLED, mUrl,
+ enabled ? null /*remove*/ : "disabled"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Returns the short description of the source, if not null.
+ * Otherwise returns the default Object toString result.
+ * <p/>
+ * This is mostly helpful for debugging.
+ * For UI display, use the {@link IDescription} interface.
+ */
+ @Override
+ public String toString() {
+ String s = getShortDescription();
+ if (s != null) {
+ return s;
+ }
+ return super.toString();
+ }
+
+ @Override
+ public String getShortDescription() {
+
+ if (mUiName != null && mUiName.length() > 0) {
+
+ String host = "malformed URL";
+
+ try {
+ URL u = new URL(mUrl);
+ host = u.getHost();
+ } catch (MalformedURLException e) {
+ }
+
+ return String.format("%1$s (%2$s)", mUiName, host);
+
+ }
+ return mUrl;
+ }
+
+ @Override
+ public String getLongDescription() {
+ // Note: in a normal workflow, mDescription is filled by setDefaultDescription().
+ // However for packages made by unit tests or such, this can be null.
+ return mDescription == null ? "" : mDescription; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the last fetch error description.
+ * If there was no error, returns null.
+ */
+ public String getFetchError() {
+ return mFetchError;
+ }
+
+ /**
+ * Tries to fetch the repository index for the given URL and updates the package list.
+ * When a source is disabled, this create an empty non-null package list.
+ * <p/>
+ * Callers can get the package list using {@link #getPackages()} after this. It will be
+ * null in case of error, in which case {@link #getFetchError()} can be used to an
+ * error message.
+ */
+ public void load(DownloadCache cache, ITaskMonitor monitor, boolean forceHttp) {
+
+ setDefaultDescription();
+ monitor.setProgressMax(7);
+
+ if (!isEnabled()) {
+ setPackages(new Package[0]);
+ mDescription += "\nSource is disabled.";
+ monitor.incProgress(7);
+ return;
+ }
+
+ String url = mUrl;
+ if (forceHttp) {
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ monitor.setDescription("Fetching URL: %1$s", url);
+ monitor.incProgress(1);
+
+ mFetchError = null;
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ Exception[] exception = new Exception[] { null };
+ Document validatedDoc = null;
+ boolean usingAlternateXml = false;
+ boolean usingAlternateUrl = false;
+ String validatedUri = null;
+
+ String[] defaultNames = getDefaultXmlFileUrls();
+ String firstDefaultName = defaultNames.length > 0 ? defaultNames[0] : "";
+
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
+ if (xml != null) {
+ int version = getXmlSchemaVersion(xml);
+ if (version == 0) {
+ closeStream(xml);
+ xml = null;
+ }
+ }
+
+ // FIXME: this is a quick fix to support an alternate upgrade path.
+ // The whole logic below needs to be updated.
+ if (xml == null && defaultNames.length > 0) {
+ ITaskMonitor subMonitor = monitor.createSubMonitor(1);
+ subMonitor.setProgressMax(defaultNames.length);
+
+ String baseUrl = url;
+ if (!baseUrl.endsWith("/")) {
+ int pos = baseUrl.lastIndexOf('/');
+ if (pos > 0) {
+ baseUrl = baseUrl.substring(0, pos + 1);
+ }
+ }
+
+ for (String name : defaultNames) {
+ String newUrl = baseUrl + name;
+ if (newUrl.equals(url)) {
+ continue;
+ }
+ xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
+ if (xml != null) {
+ int version = getXmlSchemaVersion(xml);
+ if (version == 0) {
+ closeStream(xml);
+ xml = null;
+ } else {
+ url = newUrl;
+ subMonitor.incProgress(
+ subMonitor.getProgressMax() - subMonitor.getProgress());
+ break;
+ }
+ }
+ }
+ } else {
+ monitor.incProgress(1);
+ }
+
+ // If the original URL can't be fetched
+ // and the URL doesn't explicitly end with our filename
+ // and it wasn't an HTTP authentication operation canceled by the user
+ // then make another tentative after changing the URL.
+ if (xml == null
+ && !url.endsWith(firstDefaultName)
+ && !(exception[0] instanceof CanceledByUserException)) {
+ if (!url.endsWith("/")) { //$NON-NLS-1$
+ url += "/"; //$NON-NLS-1$
+ }
+ url += firstDefaultName;
+
+ xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
+ usingAlternateUrl = true;
+ } else {
+ monitor.incProgress(1);
+ }
+
+ // FIXME this needs to revisited.
+ if (xml != null) {
+ monitor.setDescription("Validate XML: %1$s", url);
+
+ ITaskMonitor subMonitor = monitor.createSubMonitor(2);
+ subMonitor.setProgressMax(2);
+ for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
+ // Explore the XML to find the potential XML schema version
+ int version = getXmlSchemaVersion(xml);
+
+ if (version >= 1 && version <= getNsLatestVersion()) {
+ // This should be a version we can handle. Try to validate it
+ // and report any error as invalid XML syntax,
+
+ String uri = validateXml(xml, url, version, validationError, validatorFound);
+ if (uri != null) {
+ // Validation was successful
+ validatedDoc = getDocument(xml, monitor);
+ validatedUri = uri;
+
+ if (usingAlternateUrl && validatedDoc != null) {
+ // If the second tentative succeeded, indicate it in the console
+ // with the URL that worked.
+ monitor.log("Repository found at %1$s", url);
+
+ // Keep the modified URL
+ mUrl = url;
+ }
+ } else if (validatorFound[0].equals(Boolean.FALSE)) {
+ // Validation failed because this JVM lacks a proper XML Validator
+ mFetchError = validationError[0];
+ } else {
+ // We got a validator but validation failed. We know there's
+ // what looks like a suitable root element with a suitable XMLNS
+ // so it must be a genuine error of an XML not conforming to the schema.
+ }
+ } else if (version > getNsLatestVersion()) {
+ // The schema used is more recent than what is supported by this tool.
+ // Tell the user to upgrade, pointing him to the right version of the tool
+ // package.
+
+ try {
+ validatedDoc = findAlternateToolsXml(xml);
+ } catch (IOException e) {
+ // Failed, will be handled below.
+ }
+ if (validatedDoc != null) {
+ validationError[0] = null; // remove error from XML validation
+ validatedUri = getNsUri();
+ usingAlternateXml = true;
+ }
+
+ } else if (version < 1 && tryOtherUrl == 0 && !usingAlternateUrl) {
+ // This is obviously not one of our documents.
+ mFetchError = String.format(
+ "Failed to validate the XML for the repository at URL '%1$s'",
+ url);
+
+ // If we haven't already tried the alternate URL, let's do it now.
+ // We don't capture any fetch exception that happen during the second
+ // fetch in order to avoid hidding any previous fetch errors.
+ if (!url.endsWith(firstDefaultName)) {
+ if (!url.endsWith("/")) { //$NON-NLS-1$
+ url += "/"; //$NON-NLS-1$
+ }
+ url += firstDefaultName;
+
+ closeStream(xml);
+ xml = fetchXmlUrl(url, cache, subMonitor.createSubMonitor(1),
+ null /* outException */);
+ subMonitor.incProgress(1);
+ // Loop to try the alternative document
+ if (xml != null) {
+ usingAlternateUrl = true;
+ continue;
+ }
+ }
+ } else if (version < 1 && usingAlternateUrl && mFetchError == null) {
+ // The alternate URL is obviously not a valid XML either.
+ // We only report the error if we failed to produce one earlier.
+ mFetchError = String.format(
+ "Failed to validate the XML for the repository at URL '%1$s'",
+ url);
+ }
+
+ // If we get here either we succeeded or we ran out of alternatives.
+ break;
+ }
+ }
+
+ // If any exception was handled during the URL fetch, display it now.
+ if (exception[0] != null) {
+ mFetchError = "Failed to fetch URL";
+
+ String reason = null;
+ if (exception[0] instanceof FileNotFoundException) {
+ // FNF has no useful getMessage, so we need to special handle it.
+ reason = "File not found";
+ mFetchError += ": " + reason;
+ } else if (exception[0] instanceof SSLKeyException) {
+ // That's a common error and we have a pref for it.
+ reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
+ mFetchError += ": HTTPS SSL error";
+ } else if (exception[0].getMessage() != null) {
+ reason =
+ exception[0].getClass().getSimpleName().replace("Exception", "") //$NON-NLS-1$ //$NON-NLS-2$
+ + ' '
+ + exception[0].getMessage();
+ } else {
+ reason = exception[0].toString();
+ }
+
+ monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
+ }
+
+ if (validationError[0] != null) {
+ monitor.logError("%s", validationError[0]); //$NON-NLS-1$
+ }
+
+ // Stop here if we failed to validate the XML. We don't want to load it.
+ if (validatedDoc == null) {
+ return;
+ }
+
+ if (usingAlternateXml) {
+ // We found something using the "alternate" XML schema (that is the one made up
+ // to support schema upgrades). That means the user can only install the tools
+ // and needs to upgrade them before it download more stuff.
+
+ // Is the manager running from inside ADT?
+ // We check that com.android.ide.eclipse.adt.AdtPlugin exists using reflection.
+
+ boolean isADT = false;
+ try {
+ Class<?> adt = Class.forName("com.android.ide.eclipse.adt.AdtPlugin"); //$NON-NLS-1$
+ isADT = (adt != null);
+ } catch (ClassNotFoundException e) {
+ // pass
+ }
+
+ String info;
+ if (isADT) {
+ info = "This repository requires a more recent version of ADT. Please update the Eclipse Android plugin.";
+ mDescription = "This repository requires a more recent version of ADT, the Eclipse Android plugin.\nYou must update it before you can see other new packages.";
+
+ } else {
+ info = "This repository requires a more recent version of the Tools. Please update.";
+ mDescription = "This repository requires a more recent version of the Tools.\nYou must update it before you can see other new packages.";
+ }
+
+ mFetchError = mFetchError == null ? info : mFetchError + ". " + info;
+ }
+
+ monitor.incProgress(1);
+
+ if (xml != null) {
+ monitor.setDescription("Parse XML: %1$s", url);
+ monitor.incProgress(1);
+ parsePackages(validatedDoc, validatedUri, monitor);
+ if (mPackages == null || mPackages.length == 0) {
+ mDescription += "\nNo packages found.";
+ } else if (mPackages.length == 1) {
+ mDescription += "\nOne package found.";
+ } else {
+ mDescription += String.format("\n%1$d packages found.", mPackages.length);
+ }
+ }
+
+ // done
+ monitor.incProgress(1);
+ closeStream(xml);
+ }
+
+ private void setDefaultDescription() {
+ if (isAddonSource()) {
+ String desc = "";
+
+ if (mUiName != null) {
+ desc += "Add-on Provider: " + mUiName;
+ desc += "\n";
+ }
+ desc += "Add-on URL: " + mUrl;
+
+ mDescription = desc;
+ } else {
+ mDescription = String.format("SDK Source: %1$s", mUrl);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param urlString The URL to load, as a string.
+ * @param monitor {@link ITaskMonitor} related to this URL.
+ * @param outException If non null, where to store any exception that
+ * happens during the fetch.
+ */
+ private InputStream fetchXmlUrl(String urlString,
+ DownloadCache cache,
+ ITaskMonitor monitor,
+ Exception[] outException) {
+ try {
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml);
+ ((NonClosingInputStream) xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
+ } catch (Exception e) {
+ if (outException != null) {
+ outException[0] = e;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
+ * Validates this XML against one of the requested SDK Repository schemas.
+ * If the XML was correctly validated, returns the schema that worked.
+ * If it doesn't validate, returns null and stores the error in outError[0].
+ * If we can't find a validator, returns null and set validatorFound[0] to false.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected String validateXml(InputStream xml, String url, int version,
+ String[] outError, Boolean[] validatorFound) {
+
+ if (xml == null) {
+ return null;
+ }
+
+ try {
+ Validator validator = getValidator(version);
+
+ if (validator == null) {
+ validatorFound[0] = Boolean.FALSE;
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
+ url);
+ return null;
+ }
+
+ validatorFound[0] = Boolean.TRUE;
+
+ // Reset the stream if it supports that operation.
+ assert xml.markSupported();
+ xml.reset();
+
+ // Validation throws a bunch of possible Exceptions on failure.
+ validator.validate(new StreamSource(xml));
+ return getSchemaUri(version);
+
+ } catch (SAXParseException e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
+ url,
+ e.getLineNumber(),
+ e.getColumnNumber(),
+ e.toString());
+
+ } catch (Exception e) {
+ outError[0] = String.format(
+ "XML verification failed for %1$s.\nError: %2$s",
+ url,
+ e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Manually parses the root element of the XML to extract the schema version
+ * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"
+ * declaration.
+ *
+ * @return 1..{@link SdkRepoConstants#NS_LATEST_VERSION} for a valid schema version
+ * or 0 if no schema could be found.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected int getXmlSchemaVersion(InputStream xml) {
+ if (xml == null) {
+ return 0;
+ }
+
+ // Get an XML document
+ Document doc = null;
+ try {
+ assert xml.markSupported();
+ xml.reset();
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(false);
+ factory.setValidating(false);
+
+ // Parse the old document using a non namespace aware builder
+ factory.setNamespaceAware(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // We don't want the default handler which prints errors to stderr.
+ builder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+ // pass
+ }
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+
+ doc = builder.parse(xml);
+
+ // Prepare a new document using a namespace aware builder
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+
+ } catch (Exception e) {
+ // Failed to reset XML stream
+ // Failed to get builder factor
+ // Failed to create XML document builder
+ // Failed to parse XML document
+ // Failed to read XML document
+ }
+
+ if (doc == null) {
+ return 0;
+ }
+
+ // Check the root element is an XML with at least the following properties:
+ // <sdk:sdk-repository
+ // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
+ //
+ // Note that we don't have namespace support enabled, we just do it manually.
+
+ Pattern nsPattern = Pattern.compile(getNsPattern());
+
+ String prefix = null;
+ for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ prefix = null;
+ String name = child.getNodeName();
+ int pos = name.indexOf(':');
+ if (pos > 0 && pos < name.length() - 1) {
+ prefix = name.substring(0, pos);
+ name = name.substring(pos + 1);
+ }
+ if (getRootElementName().equals(name)) {
+ NamedNodeMap attrs = child.getAttributes();
+ String xmlns = "xmlns"; //$NON-NLS-1$
+ if (prefix != null) {
+ xmlns += ":" + prefix; //$NON-NLS-1$
+ }
+ Node attr = attrs.getNamedItem(xmlns);
+ if (attr != null) {
+ String uri = attr.getNodeValue();
+ if (uri != null) {
+ Matcher m = nsPattern.matcher(uri);
+ if (m.matches()) {
+ String version = m.group(1);
+ try {
+ return Integer.parseInt(version);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Helper method that returns a validator for our XSD, or null if the current Java
+ * implementation can't process XSD schemas.
+ *
+ * @param version The version of the XML Schema.
+ * See {@link SdkRepoConstants#getXsdStream(int)}
+ */
+ private Validator getValidator(int version) throws SAXException {
+ InputStream xsdStream = getXsdStream(version);
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+ if (factory == null) {
+ return null;
+ }
+
+ // 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 == null ? null : schema.newValidator();
+
+ // We don't want the default handler, which by default dumps errors to stderr.
+ validator.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+ // pass
+ }
+ @Override
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+
+ return validator;
+ }
+
+ /**
+ * Parse all packages defined in the SDK Repository XML and creates
+ * a new mPackages array with them.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
+
+ Node root = getFirstChild(doc, nsUri, getRootElementName());
+ if (root != null) {
+
+ ArrayList<Package> packages = new ArrayList<Package>();
+
+ // Parse license definitions
+ HashMap<String, String> licenses = new HashMap<String, String>();
+ for (Node child = root.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI()) &&
+ child.getLocalName().equals(RepoConstants.NODE_LICENSE)) {
+ Node id = child.getAttributes().getNamedItem(RepoConstants.ATTR_ID);
+ if (id != null) {
+ licenses.put(id.getNodeValue(), child.getTextContent());
+ }
+ }
+ }
+
+ // Parse packages
+ for (Node child = root.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.equals(child.getNamespaceURI())) {
+ String name = child.getLocalName();
+ Package p = null;
+
+ try {
+ // We can load addon and extra packages from all sources, either
+ // internal or user sources.
+ if (SdkAddonConstants.NODE_ADD_ON.equals(name)) {
+ p = new AddonPackage(this, child, nsUri, licenses);
+
+ } else if (SdkAddonConstants.NODE_EXTRA.equals(name)) {
+ p = new ExtraPackage(this, child, nsUri, licenses);
+
+ } else if (!isAddonSource()) {
+ // We only load platform, doc and tool packages from internal
+ // sources, never from user sources.
+ if (SdkRepoConstants.NODE_PLATFORM.equals(name)) {
+ p = new PlatformPackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_DOC.equals(name)) {
+ p = new DocPackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_TOOL.equals(name)) {
+ p = new ToolPackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_PLATFORM_TOOL.equals(name)) {
+ p = new PlatformToolPackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_SAMPLE.equals(name)) {
+ p = new SamplePackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_SYSTEM_IMAGE.equals(name)) {
+ p = new SystemImagePackage(this, child, nsUri, licenses);
+ } else if (SdkRepoConstants.NODE_SOURCE.equals(name)) {
+ p = new SourcePackage(this, child, nsUri, licenses);
+ }
+ }
+
+ if (p != null) {
+ packages.add(p);
+ monitor.logVerbose("Found %1$s", p.getShortDescription());
+ }
+ } catch (Exception e) {
+ // Ignore invalid packages
+ monitor.logError("Ignoring invalid %1$s element: %2$s", name, e.toString());
+ }
+ }
+ }
+
+ setPackages(packages.toArray(new Package[packages.size()]));
+
+ return true;
+ }
+
+ 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 nsUri, String xmlLocalName) {
+
+ for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ nsUri.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.
+ *
+ * On error, returns null and prints a (hopefully) useful message on the monitor.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringComments(true);
+ factory.setNamespaceAware(true);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ assert xml.markSupported();
+ xml.reset();
+ Document doc = builder.parse(new InputSource(xml));
+
+ return doc;
+ } catch (ParserConfigurationException e) {
+ monitor.logError("Failed to create XML document builder");
+
+ } catch (SAXException e) {
+ monitor.logError("Failed to parse XML document");
+
+ } catch (IOException e) {
+ monitor.logError("Failed to read XML document");
+ }
+
+ return null;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java
index fac2c8b..5272cd5 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceCategory.java
@@ -1,89 +1,89 @@
-/*
- * Copyright (C) 2010 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.sources;
-
-import com.android.sdklib.internal.repository.IDescription;
-
-
-/**
- * The category of a given {@link SdkSource} (which represents a download site).
- */
-public enum SdkSourceCategory implements IDescription {
-
- /**
- * The default canonical and official Android repository.
- */
- ANDROID_REPO("Android Repository", true),
-
- /**
- * Repositories contributed by the SDK_UPDATER_URLS env var,
- * only used for local debugging.
- */
- GETENV_REPOS("Custom Repositories", false),
-
- /**
- * All third-party add-ons fetched from the Android repository.
- */
- ADDONS_3RD_PARTY("Third party Add-ons", true),
-
- /**
- * All add-ons contributed locally by the user via the "Add Add-on Site" button.
- */
- USER_ADDONS("User Add-ons", false),
-
- /**
- * Add-ons contributed by the SDK_UPDATER_USER_URLS env var,
- * only used for local debugging.
- */
- GETENV_ADDONS("Custom Add-ons", false);
-
-
- private final String mUiName;
- private final boolean mAlwaysDisplay;
-
- private SdkSourceCategory(String uiName, boolean alwaysDisplay) {
- mUiName = uiName;
- mAlwaysDisplay = alwaysDisplay;
- }
-
- /**
- * Returns the UI-visible name of the cateogry. Displayed in the available package tree.
- * Cannot be null nor empty.
- */
- public String getUiName() {
- return mUiName;
- }
-
- /**
- * True if this category must always be displayed by the available package tree, even
- * if empty.
- * When false, the category must not be displayed when empty.
- */
- public boolean getAlwaysDisplay() {
- return mAlwaysDisplay;
- }
-
- @Override
- public String getLongDescription() {
- return getUiName();
- }
-
- @Override
- public String getShortDescription() {
- return getUiName();
- }
-}
+/*
+ * Copyright (C) 2010 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.sources;
+
+import com.android.sdklib.internal.repository.IDescription;
+
+
+/**
+ * The category of a given {@link SdkSource} (which represents a download site).
+ */
+public enum SdkSourceCategory implements IDescription {
+
+ /**
+ * The default canonical and official Android repository.
+ */
+ ANDROID_REPO("Android Repository", true),
+
+ /**
+ * Repositories contributed by the SDK_UPDATER_URLS env var,
+ * only used for local debugging.
+ */
+ GETENV_REPOS("Custom Repositories", false),
+
+ /**
+ * All third-party add-ons fetched from the Android repository.
+ */
+ ADDONS_3RD_PARTY("Third party Add-ons", true),
+
+ /**
+ * All add-ons contributed locally by the user via the "Add Add-on Site" button.
+ */
+ USER_ADDONS("User Add-ons", false),
+
+ /**
+ * Add-ons contributed by the SDK_UPDATER_USER_URLS env var,
+ * only used for local debugging.
+ */
+ GETENV_ADDONS("Custom Add-ons", false);
+
+
+ private final String mUiName;
+ private final boolean mAlwaysDisplay;
+
+ private SdkSourceCategory(String uiName, boolean alwaysDisplay) {
+ mUiName = uiName;
+ mAlwaysDisplay = alwaysDisplay;
+ }
+
+ /**
+ * Returns the UI-visible name of the cateogry. Displayed in the available package tree.
+ * Cannot be null nor empty.
+ */
+ public String getUiName() {
+ return mUiName;
+ }
+
+ /**
+ * True if this category must always be displayed by the available package tree, even
+ * if empty.
+ * When false, the category must not be displayed when empty.
+ */
+ public boolean getAlwaysDisplay() {
+ return mAlwaysDisplay;
+ }
+
+ @Override
+ public String getLongDescription() {
+ return getUiName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return getUiName();
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java
index 915dc36..cdd428f 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSourceProperties.java
@@ -1,249 +1,249 @@
-/*
- * Copyright (C) 2012 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.sources;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Properties;
-
-/**
- * Properties for individual sources which are persisted by a local settings file.
- * <p/>
- * All instances of {@link SdkSourceProperties} share the same singleton storage.
- * The persisted setting file is loaded as necessary, however callers must persist
- * it at some point by calling {@link #save()}.
- */
-public class SdkSourceProperties {
-
- /**
- * An internal file version number, in case we want to change the format later.
- */
- private static final String KEY_VERSION = "@version@"; //$NON-NLS-1$
- /**
- * The last known UI name of the source.
- */
- public static final String KEY_NAME = "@name@"; //$NON-NLS-1$
- /**
- * A non-null string if the source is disabled. Null if the source is enabled.
- */
- public static final String KEY_DISABLED = "@disabled@"; //$NON-NLS-1$
-
- private static final Properties sSourcesProperties = new Properties();
- private static final String SRC_FILENAME = "sites-settings.cfg"; //$NON-NLS-1$
-
- private static boolean sModified = false;
-
- public SdkSourceProperties() {
- }
-
- public void save() {
- synchronized (sSourcesProperties) {
- if (sModified && !sSourcesProperties.isEmpty()) {
- saveLocked();
- sModified = false;
- }
- }
- }
-
- /**
- * Retrieves a property for the given source URL and the given key type.
- * <p/>
- * Implementation detail: this loads the persistent settings file as needed.
- *
- * @param key The kind of property to retrieve for that source URL.
- * @param sourceUrl The source URL.
- * @param defaultValue The default value to return, if the property isn't found. Can be null.
- * @return The non-null string property for the key/sourceUrl or the default value.
- */
- @Nullable
- public String getProperty(@NonNull String key,
- @NonNull String sourceUrl,
- @Nullable String defaultValue) {
- String value = defaultValue;
-
- synchronized (sSourcesProperties) {
- if (sSourcesProperties.isEmpty()) {
- loadLocked();
- }
-
- value = sSourcesProperties.getProperty(key + sourceUrl, defaultValue);
- }
-
- return value;
- }
-
- /**
- * Sets or remove a property for the given source URL and the given key type.
- * <p/>
- * Implementation detail: this does <em>not</em> save the persistent settings file.
- * Somehow the caller will need to call the {@link #save()} method later.
- *
- * @param key The kind of property to retrieve for that source URL.
- * @param sourceUrl The source URL.
- * @param value The new value to set (if non null) or null to remove an existing property.
- */
- public void setProperty(String key, String sourceUrl, String value) {
- synchronized (sSourcesProperties) {
- if (sSourcesProperties.isEmpty()) {
- loadLocked();
- }
-
- key += sourceUrl;
-
- String old = sSourcesProperties.getProperty(key);
- if (value == null) {
- if (old != null) {
- sSourcesProperties.remove(key);
- sModified = true;
- }
- } else if (old == null || !old.equals(value)) {
- sSourcesProperties.setProperty(key, value);
- sModified = true;
- }
- }
- }
-
- /**
- * Returns an internal string representation of the underlying Properties map,
- * sorted by ascending keys. Useful for debugging and testing purposes only.
- */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("<SdkSourceProperties"); //$NON-NLS-1$
- synchronized (sSourcesProperties) {
- List<Object> keys = Collections.list(sSourcesProperties.keys());
- Collections.sort(keys, new Comparator<Object>() {
- @Override
- public int compare(Object o1, Object o2) {
- return o1.toString().compareTo(o2.toString());
- }});
-
- for (Object key : keys) {
- sb.append('\n').append(key)
- .append(" = ").append(sSourcesProperties.get(key)); //$NON-NLS-1$
- }
- }
- sb.append('>');
- return sb.toString();
- }
-
- /** Load state from persistent file. Expects sSourcesProperties to be synchronized. */
- private void loadLocked() {
- // Load state from persistent file
- if (loadProperties()) {
- // If it lacks our magic version key, don't use it
- if (sSourcesProperties.getProperty(KEY_VERSION) == null) {
- sSourcesProperties.clear();
- }
-
- sModified = false;
- }
-
- if (sSourcesProperties.isEmpty()) {
- // Nothing was loaded. Initialize the storage with a version
- // identified. This isn't currently checked back, but we might
- // want it later if we decide to change the way this works.
- // The version key is choosen on purpose to not match any valid URL.
- sSourcesProperties.setProperty(KEY_VERSION, "1"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- /**
- * Load properties from default file. Extracted so that it can be mocked in tests.
- *
- * @return True if actually loaded the file. False if there was an IO error or no
- * file and nothing was loaded.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected boolean loadProperties() {
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
- if (f.exists()) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(f);
- sSourcesProperties.load(fis);
- } catch (IOException ignore) {
- // nop
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ignore) {}
- }
- }
-
- return true;
- }
- } catch (AndroidLocationException ignore) {
- // nop
- }
- return false;
- }
-
- /**
- * Save file to disk. Expects sSourcesProperties to be synchronized.
- * Made accessible for testing purposes.
- * For public usage, please use {@link #save()} instead.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void saveLocked() {
- // Persist it to the file
- FileOutputStream fos = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
-
- fos = new FileOutputStream(f);
-
- sSourcesProperties.store(fos,"## Sites Settings for Android SDK Manager");//$NON-NLS-1$
-
- } catch (AndroidLocationException ignore) {
- // nop
- } catch (IOException ignore) {
- // nop
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
- /** Empty current property list. Made accessible for testing purposes. */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void clear() {
- synchronized (sSourcesProperties) {
- sSourcesProperties.clear();
- sModified = false;
- }
- }
-}
+/*
+ * Copyright (C) 2012 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.sources;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Properties for individual sources which are persisted by a local settings file.
+ * <p/>
+ * All instances of {@link SdkSourceProperties} share the same singleton storage.
+ * The persisted setting file is loaded as necessary, however callers must persist
+ * it at some point by calling {@link #save()}.
+ */
+public class SdkSourceProperties {
+
+ /**
+ * An internal file version number, in case we want to change the format later.
+ */
+ private static final String KEY_VERSION = "@version@"; //$NON-NLS-1$
+ /**
+ * The last known UI name of the source.
+ */
+ public static final String KEY_NAME = "@name@"; //$NON-NLS-1$
+ /**
+ * A non-null string if the source is disabled. Null if the source is enabled.
+ */
+ public static final String KEY_DISABLED = "@disabled@"; //$NON-NLS-1$
+
+ private static final Properties sSourcesProperties = new Properties();
+ private static final String SRC_FILENAME = "sites-settings.cfg"; //$NON-NLS-1$
+
+ private static boolean sModified = false;
+
+ public SdkSourceProperties() {
+ }
+
+ public void save() {
+ synchronized (sSourcesProperties) {
+ if (sModified && !sSourcesProperties.isEmpty()) {
+ saveLocked();
+ sModified = false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves a property for the given source URL and the given key type.
+ * <p/>
+ * Implementation detail: this loads the persistent settings file as needed.
+ *
+ * @param key The kind of property to retrieve for that source URL.
+ * @param sourceUrl The source URL.
+ * @param defaultValue The default value to return, if the property isn't found. Can be null.
+ * @return The non-null string property for the key/sourceUrl or the default value.
+ */
+ @Nullable
+ public String getProperty(@NonNull String key,
+ @NonNull String sourceUrl,
+ @Nullable String defaultValue) {
+ String value = defaultValue;
+
+ synchronized (sSourcesProperties) {
+ if (sSourcesProperties.isEmpty()) {
+ loadLocked();
+ }
+
+ value = sSourcesProperties.getProperty(key + sourceUrl, defaultValue);
+ }
+
+ return value;
+ }
+
+ /**
+ * Sets or remove a property for the given source URL and the given key type.
+ * <p/>
+ * Implementation detail: this does <em>not</em> save the persistent settings file.
+ * Somehow the caller will need to call the {@link #save()} method later.
+ *
+ * @param key The kind of property to retrieve for that source URL.
+ * @param sourceUrl The source URL.
+ * @param value The new value to set (if non null) or null to remove an existing property.
+ */
+ public void setProperty(String key, String sourceUrl, String value) {
+ synchronized (sSourcesProperties) {
+ if (sSourcesProperties.isEmpty()) {
+ loadLocked();
+ }
+
+ key += sourceUrl;
+
+ String old = sSourcesProperties.getProperty(key);
+ if (value == null) {
+ if (old != null) {
+ sSourcesProperties.remove(key);
+ sModified = true;
+ }
+ } else if (old == null || !old.equals(value)) {
+ sSourcesProperties.setProperty(key, value);
+ sModified = true;
+ }
+ }
+ }
+
+ /**
+ * Returns an internal string representation of the underlying Properties map,
+ * sorted by ascending keys. Useful for debugging and testing purposes only.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("<SdkSourceProperties"); //$NON-NLS-1$
+ synchronized (sSourcesProperties) {
+ List<Object> keys = Collections.list(sSourcesProperties.keys());
+ Collections.sort(keys, new Comparator<Object>() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ return o1.toString().compareTo(o2.toString());
+ }});
+
+ for (Object key : keys) {
+ sb.append('\n').append(key)
+ .append(" = ").append(sSourcesProperties.get(key)); //$NON-NLS-1$
+ }
+ }
+ sb.append('>');
+ return sb.toString();
+ }
+
+ /** Load state from persistent file. Expects sSourcesProperties to be synchronized. */
+ private void loadLocked() {
+ // Load state from persistent file
+ if (loadProperties()) {
+ // If it lacks our magic version key, don't use it
+ if (sSourcesProperties.getProperty(KEY_VERSION) == null) {
+ sSourcesProperties.clear();
+ }
+
+ sModified = false;
+ }
+
+ if (sSourcesProperties.isEmpty()) {
+ // Nothing was loaded. Initialize the storage with a version
+ // identified. This isn't currently checked back, but we might
+ // want it later if we decide to change the way this works.
+ // The version key is choosen on purpose to not match any valid URL.
+ sSourcesProperties.setProperty(KEY_VERSION, "1"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /**
+ * Load properties from default file. Extracted so that it can be mocked in tests.
+ *
+ * @return True if actually loaded the file. False if there was an IO error or no
+ * file and nothing was loaded.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected boolean loadProperties() {
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SRC_FILENAME);
+ if (f.exists()) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(f);
+ sSourcesProperties.load(fis);
+ } catch (IOException ignore) {
+ // nop
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ return true;
+ }
+ } catch (AndroidLocationException ignore) {
+ // nop
+ }
+ return false;
+ }
+
+ /**
+ * Save file to disk. Expects sSourcesProperties to be synchronized.
+ * Made accessible for testing purposes.
+ * For public usage, please use {@link #save()} instead.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void saveLocked() {
+ // Persist it to the file
+ FileOutputStream fos = null;
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SRC_FILENAME);
+
+ fos = new FileOutputStream(f);
+
+ sSourcesProperties.store(fos,"## Sites Settings for Android SDK Manager");//$NON-NLS-1$
+
+ } catch (AndroidLocationException ignore) {
+ // nop
+ } catch (IOException ignore) {
+ // nop
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+ /** Empty current property list. Made accessible for testing purposes. */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void clear() {
+ synchronized (sSourcesProperties) {
+ sSourcesProperties.clear();
+ sModified = false;
+ }
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
index b1354c3..c89df5e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
@@ -1,413 +1,429 @@
-/*
- * 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.sources;
-
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.ISdkLog;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.Iterator;
-import java.util.Properties;
-import java.util.Map.Entry;
-
-/**
- * A list of sdk-repository and sdk-addon sources, sorted by {@link SdkSourceCategory}.
- */
-public class SdkSources {
-
- private static final String KEY_COUNT = "count";
-
- private static final String KEY_SRC = "src";
-
- private static final String SRC_FILENAME = "repositories.cfg"; //$NON-NLS-1$
-
- private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mSources =
- new EnumMap<SdkSourceCategory, ArrayList<SdkSource>>(SdkSourceCategory.class);
-
- private ArrayList<Runnable> mChangeListeners; // lazily initialized
-
-
- public SdkSources() {
- }
-
- /**
- * Adds a new source to the Sources list.
- * <p/>
- * Implementation detail: {@link SdkSources} doesn't invoke {@link #notifyChangeListeners()}
- * directly. Callers who use {@code add()} are responsible for notifying the listeners once
- * they are done modifying the sources list. The intent is to notify the listeners only once
- * at the end, not for every single addition.
- */
- public void add(SdkSourceCategory category, SdkSource source) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list == null) {
- list = new ArrayList<SdkSource>();
- mSources.put(category, list);
- }
-
- list.add(source);
- }
- }
-
- /**
- * Removes a source from the Sources list.
- * <p/>
- * Callers who remove entries are responsible for notifying the listeners using
- * {@link #notifyChangeListeners()} once they are done modifying the sources list.
- */
- public void remove(SdkSource source) {
- synchronized (mSources) {
- Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it =
- mSources.entrySet().iterator();
- while (it.hasNext()) {
- Entry<SdkSourceCategory, ArrayList<SdkSource>> entry = it.next();
- ArrayList<SdkSource> list = entry.getValue();
-
- if (list.remove(source)) {
- if (list.isEmpty()) {
- // remove the entry since the source list became empty
- it.remove();
- }
- }
- }
- }
- }
-
- /**
- * Removes all the sources in the given category.
- * <p/>
- * Callers who remove entries are responsible for notifying the listeners using
- * {@link #notifyChangeListeners()} once they are done modifying the sources list.
- */
- public void removeAll(SdkSourceCategory category) {
- synchronized (mSources) {
- mSources.remove(category);
- }
- }
-
- /**
- * Returns a set of all categories that must be displayed. This includes all
- * categories that are to be always displayed as well as all categories which
- * have at least one source.
- * Might return a empty array, but never returns null.
- */
- public SdkSourceCategory[] getCategories() {
- ArrayList<SdkSourceCategory> cats = new ArrayList<SdkSourceCategory>();
-
- for (SdkSourceCategory cat : SdkSourceCategory.values()) {
- if (cat.getAlwaysDisplay()) {
- cats.add(cat);
- } else {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(cat);
- if (list != null && !list.isEmpty()) {
- cats.add(cat);
- }
- }
- }
- }
-
- return cats.toArray(new SdkSourceCategory[cats.size()]);
- }
-
- /**
- * Returns a new array of sources attached to the given category.
- * Might return an empty array, but never returns null.
- */
- public SdkSource[] getSources(SdkSourceCategory category) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list == null) {
- return new SdkSource[0];
- } else {
- return list.toArray(new SdkSource[list.size()]);
- }
- }
- }
-
- /**
- * Returns an array of the sources across all categories. This is never null.
- */
- public SdkSource[] getAllSources() {
- synchronized (mSources) {
- int n = 0;
-
- for (ArrayList<SdkSource> list : mSources.values()) {
- n += list.size();
- }
-
- SdkSource[] sources = new SdkSource[n];
-
- int i = 0;
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource source : list) {
- sources[i++] = source;
- }
- }
-
- return sources;
- }
- }
-
- /**
- * Each source keeps a local cache of whatever it loaded recently.
- * This calls {@link SdkSource#clearPackages()} on all the available sources,
- * and the next call to {@link SdkSource#getPackages()} will actually reload
- * the remote package list.
- */
- public void clearAllPackages() {
- synchronized (mSources) {
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource source : list) {
- source.clearPackages();
- }
- }
- }
- }
-
- /**
- * Returns the category of a given source, or null if the source is unknown.
- * <p/>
- * Note that this method uses object identity to find a given source, and does
- * not identify sources by their URL like {@link #hasSourceUrl(SdkSource)} does.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public SdkSourceCategory getCategory(SdkSource source) {
- if (source != null) {
- synchronized (mSources) {
- for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mSources.entrySet()) {
- if (entry.getValue().contains(source)) {
- return entry.getKey();
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns true if there's already a similar source in the sources list
- * under any category.
- * <p/>
- * Important: The match is NOT done on object identity.
- * Instead, this searches for a <em>similar</em> source, based on
- * {@link SdkSource#equals(Object)} which compares the source URLs.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public boolean hasSourceUrl(SdkSource source) {
- synchronized (mSources) {
- for (ArrayList<SdkSource> list : mSources.values()) {
- for (SdkSource s : list) {
- if (s.equals(source)) {
- return true;
- }
- }
- }
- return false;
- }
- }
-
- /**
- * Returns true if there's already a similar source in the sources list
- * under the specified category.
- * <p/>
- * Important: The match is NOT done on object identity.
- * Instead, this searches for a <em>similar</em> source, based on
- * {@link SdkSource#equals(Object)} which compares the source URLs.
- * <p/>
- * The search is O(N), which should be acceptable on the expectedly small source list.
- */
- public boolean hasSourceUrl(SdkSourceCategory category, SdkSource source) {
- synchronized (mSources) {
- ArrayList<SdkSource> list = mSources.get(category);
- if (list != null) {
- for (SdkSource s : list) {
- if (s.equals(source)) {
- return true;
- }
- }
- }
- return false;
- }
- }
-
- /**
- * Loads all user sources. This <em>replaces</em> all existing user sources
- * by the ones from the property file.
- * <p/>
- * This calls {@link #notifyChangeListeners()} at the end of the operation.
- */
- public void loadUserAddons(ISdkLog log) {
- // Implementation detail: synchronize on the sources list to make sure that
- // a- the source list doesn't change while we load/save it, and most important
- // b- to make sure it's not being saved while loaded or the reverse.
- // In most cases we do these operation from the UI thread so it's not really
- // that necessary. This is more a protection in case of someone calls this
- // from a worker thread by mistake.
- synchronized (mSources) {
- // Remove all existing user sources
- removeAll(SdkSourceCategory.USER_ADDONS);
-
- // Load new user sources from property file
- FileInputStream fis = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
- if (f.exists()) {
- fis = new FileInputStream(f);
-
- Properties props = new Properties();
- props.load(fis);
-
- int count = Integer.parseInt(props.getProperty(KEY_COUNT, "0"));
-
- for (int i = 0; i < count; i++) {
- String url = props.getProperty(String.format("%s%02d", KEY_SRC, i)); //$NON-NLS-1$
- if (url != null) {
- SdkSource s = new SdkAddonSource(url, null/*uiName*/);
- if (!hasSourceUrl(s)) {
- add(SdkSourceCategory.USER_ADDONS, s);
- }
- }
- }
- }
-
- } catch (NumberFormatException e) {
- log.error(e, null);
-
- } catch (AndroidLocationException e) {
- log.error(e, null);
-
- } catch (IOException e) {
- log.error(e, null);
-
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- }
- notifyChangeListeners();
- }
-
- /**
- * Saves all the user sources.
- * @param log Logger. Cannot be null.
- */
- public void saveUserAddons(ISdkLog log) {
- // See the implementation detail note in loadUserAddons() about the synchronization.
- synchronized (mSources) {
- FileOutputStream fos = null;
- try {
- String folder = AndroidLocation.getFolder();
- File f = new File(folder, SRC_FILENAME);
-
- fos = new FileOutputStream(f);
-
- Properties props = new Properties();
-
- int count = 0;
- for (SdkSource s : getSources(SdkSourceCategory.USER_ADDONS)) {
- props.setProperty(String.format("%s%02d", KEY_SRC, count), //$NON-NLS-1$
- s.getUrl());
- count++;
- }
- props.setProperty(KEY_COUNT, Integer.toString(count));
-
- props.store( fos, "## User Sources for Android SDK Manager"); //$NON-NLS-1$
-
- } catch (AndroidLocationException e) {
- log.error(e, null);
-
- } catch (IOException e) {
- log.error(e, null);
-
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
- }
- }
- }
-
- /**
- * Adds a listener that will be notified when the sources list has changed.
- *
- * @param changeListener A non-null listener to add. Ignored if already present.
- * @see SdkSources#notifyChangeListeners()
- */
- public void addChangeListener(Runnable changeListener) {
- assert changeListener != null;
- if (mChangeListeners == null) {
- mChangeListeners = new ArrayList<Runnable>();
- }
- synchronized (mChangeListeners) {
- if (changeListener != null && !mChangeListeners.contains(changeListener)) {
- mChangeListeners.add(changeListener);
- }
- }
- }
-
- /**
- * Removes a listener from the list of listeners to notify when the sources change.
- *
- * @param changeListener A listener to remove. Ignored if not previously added.
- */
- public void removeChangeListener(Runnable changeListener) {
- if (mChangeListeners != null && changeListener != null) {
- synchronized (mChangeListeners) {
- mChangeListeners.remove(changeListener);
- }
- }
- }
-
- /**
- * Invoke all the registered change listeners, if any.
- * <p/>
- * This <em>may</em> be called from a worker thread, in which case the runnable
- * should take care of only updating UI from a main thread.
- */
- public void notifyChangeListeners() {
- if (mChangeListeners == null) {
- return;
- }
- synchronized (mChangeListeners) {
- for (Runnable runnable : mChangeListeners) {
- try {
- runnable.run();
- } catch (Throwable ignore) {
- assert ignore == null : "A SdkSource.ChangeListener failed with an exception.";
- }
- }
- }
- }
-}
+/*
+ * 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.sources;
+
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.repository.SdkSysImgConstants;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Map.Entry;
+
+/**
+ * A list of sdk-repository and sdk-addon sources, sorted by {@link SdkSourceCategory}.
+ */
+public class SdkSources {
+
+ private static final String KEY_COUNT = "count";
+
+ private static final String KEY_SRC = "src";
+
+ private static final String SRC_FILENAME = "repositories.cfg"; //$NON-NLS-1$
+
+ private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mSources =
+ new EnumMap<SdkSourceCategory, ArrayList<SdkSource>>(SdkSourceCategory.class);
+
+ private ArrayList<Runnable> mChangeListeners; // lazily initialized
+
+
+ public SdkSources() {
+ }
+
+ /**
+ * Adds a new source to the Sources list.
+ * <p/>
+ * Implementation detail: {@link SdkSources} doesn't invoke {@link #notifyChangeListeners()}
+ * directly. Callers who use {@code add()} are responsible for notifying the listeners once
+ * they are done modifying the sources list. The intent is to notify the listeners only once
+ * at the end, not for every single addition.
+ */
+ public void add(SdkSourceCategory category, SdkSource source) {
+ synchronized (mSources) {
+ ArrayList<SdkSource> list = mSources.get(category);
+ if (list == null) {
+ list = new ArrayList<SdkSource>();
+ mSources.put(category, list);
+ }
+
+ list.add(source);
+ }
+ }
+
+ /**
+ * Removes a source from the Sources list.
+ * <p/>
+ * Callers who remove entries are responsible for notifying the listeners using
+ * {@link #notifyChangeListeners()} once they are done modifying the sources list.
+ */
+ public void remove(SdkSource source) {
+ synchronized (mSources) {
+ Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it =
+ mSources.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<SdkSourceCategory, ArrayList<SdkSource>> entry = it.next();
+ ArrayList<SdkSource> list = entry.getValue();
+
+ if (list.remove(source)) {
+ if (list.isEmpty()) {
+ // remove the entry since the source list became empty
+ it.remove();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes all the sources in the given category.
+ * <p/>
+ * Callers who remove entries are responsible for notifying the listeners using
+ * {@link #notifyChangeListeners()} once they are done modifying the sources list.
+ */
+ public void removeAll(SdkSourceCategory category) {
+ synchronized (mSources) {
+ mSources.remove(category);
+ }
+ }
+
+ /**
+ * Returns a set of all categories that must be displayed. This includes all
+ * categories that are to be always displayed as well as all categories which
+ * have at least one source.
+ * Might return a empty array, but never returns null.
+ */
+ public SdkSourceCategory[] getCategories() {
+ ArrayList<SdkSourceCategory> cats = new ArrayList<SdkSourceCategory>();
+
+ for (SdkSourceCategory cat : SdkSourceCategory.values()) {
+ if (cat.getAlwaysDisplay()) {
+ cats.add(cat);
+ } else {
+ synchronized (mSources) {
+ ArrayList<SdkSource> list = mSources.get(cat);
+ if (list != null && !list.isEmpty()) {
+ cats.add(cat);
+ }
+ }
+ }
+ }
+
+ return cats.toArray(new SdkSourceCategory[cats.size()]);
+ }
+
+ /**
+ * Returns a new array of sources attached to the given category.
+ * Might return an empty array, but never returns null.
+ */
+ public SdkSource[] getSources(SdkSourceCategory category) {
+ synchronized (mSources) {
+ ArrayList<SdkSource> list = mSources.get(category);
+ if (list == null) {
+ return new SdkSource[0];
+ } else {
+ return list.toArray(new SdkSource[list.size()]);
+ }
+ }
+ }
+
+ /**
+ * Returns an array of the sources across all categories. This is never null.
+ */
+ public SdkSource[] getAllSources() {
+ synchronized (mSources) {
+ int n = 0;
+
+ for (ArrayList<SdkSource> list : mSources.values()) {
+ n += list.size();
+ }
+
+ SdkSource[] sources = new SdkSource[n];
+
+ int i = 0;
+ for (ArrayList<SdkSource> list : mSources.values()) {
+ for (SdkSource source : list) {
+ sources[i++] = source;
+ }
+ }
+
+ return sources;
+ }
+ }
+
+ /**
+ * Each source keeps a local cache of whatever it loaded recently.
+ * This calls {@link SdkSource#clearPackages()} on all the available sources,
+ * and the next call to {@link SdkSource#getPackages()} will actually reload
+ * the remote package list.
+ */
+ public void clearAllPackages() {
+ synchronized (mSources) {
+ for (ArrayList<SdkSource> list : mSources.values()) {
+ for (SdkSource source : list) {
+ source.clearPackages();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the category of a given source, or null if the source is unknown.
+ * <p/>
+ * Note that this method uses object identity to find a given source, and does
+ * not identify sources by their URL like {@link #hasSourceUrl(SdkSource)} does.
+ * <p/>
+ * The search is O(N), which should be acceptable on the expectedly small source list.
+ */
+ public SdkSourceCategory getCategory(SdkSource source) {
+ if (source != null) {
+ synchronized (mSources) {
+ for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mSources.entrySet()) {
+ if (entry.getValue().contains(source)) {
+ return entry.getKey();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if there's already a similar source in the sources list
+ * under any category.
+ * <p/>
+ * Important: The match is NOT done on object identity.
+ * Instead, this searches for a <em>similar</em> source, based on
+ * {@link SdkSource#equals(Object)} which compares the source URLs.
+ * <p/>
+ * The search is O(N), which should be acceptable on the expectedly small source list.
+ */
+ public boolean hasSourceUrl(SdkSource source) {
+ synchronized (mSources) {
+ for (ArrayList<SdkSource> list : mSources.values()) {
+ for (SdkSource s : list) {
+ if (s.equals(source)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if there's already a similar source in the sources list
+ * under the specified category.
+ * <p/>
+ * Important: The match is NOT done on object identity.
+ * Instead, this searches for a <em>similar</em> source, based on
+ * {@link SdkSource#equals(Object)} which compares the source URLs.
+ * <p/>
+ * The search is O(N), which should be acceptable on the expectedly small source list.
+ */
+ public boolean hasSourceUrl(SdkSourceCategory category, SdkSource source) {
+ synchronized (mSources) {
+ ArrayList<SdkSource> list = mSources.get(category);
+ if (list != null) {
+ for (SdkSource s : list) {
+ if (s.equals(source)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Loads all user sources. This <em>replaces</em> all existing user sources
+ * by the ones from the property file.
+ * <p/>
+ * This calls {@link #notifyChangeListeners()} at the end of the operation.
+ */
+ public void loadUserAddons(ILogger log) {
+ // Implementation detail: synchronize on the sources list to make sure that
+ // a- the source list doesn't change while we load/save it, and most important
+ // b- to make sure it's not being saved while loaded or the reverse.
+ // In most cases we do these operation from the UI thread so it's not really
+ // that necessary. This is more a protection in case of someone calls this
+ // from a worker thread by mistake.
+ synchronized (mSources) {
+ // Remove all existing user sources
+ removeAll(SdkSourceCategory.USER_ADDONS);
+
+ // Load new user sources from property file
+ FileInputStream fis = null;
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SRC_FILENAME);
+ if (f.exists()) {
+ fis = new FileInputStream(f);
+
+ Properties props = new Properties();
+ props.load(fis);
+
+ int count = Integer.parseInt(props.getProperty(KEY_COUNT, "0"));
+
+ for (int i = 0; i < count; i++) {
+ String url = props.getProperty(String.format("%s%02d", KEY_SRC, i)); //$NON-NLS-1$
+ if (url != null) {
+ // FIXME: this code originally only dealt with add-on XML sources.
+ // Now we'd like it to deal with system-image sources too, but we
+ // don't know which kind of object it is (at least not without
+ // trying to fetch it.) As a temporary workaround, just take a
+ // guess based on the leaf URI name. However ideally what we can
+ // simply do is add a checkbox "is system-image XML" in the user
+ // dialog and pass this info down here. Another alternative is to
+ // make a "dynamic" source object that tries to guess its type once
+ // the URI has been fetched.
+ SdkSource s;
+ if (url.endsWith(SdkSysImgConstants.URL_DEFAULT_FILENAME)) {
+ s = new SdkSysImgSource(url, null/*uiName*/);
+ } else {
+ s = new SdkAddonSource(url, null/*uiName*/);
+ }
+ if (!hasSourceUrl(s)) {
+ add(SdkSourceCategory.USER_ADDONS, s);
+ }
+ }
+ }
+ }
+
+ } catch (NumberFormatException e) {
+ log.error(e, null);
+
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+
+ } catch (IOException e) {
+ log.error(e, null);
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ notifyChangeListeners();
+ }
+
+ /**
+ * Saves all the user sources.
+ * @param log Logger. Cannot be null.
+ */
+ public void saveUserAddons(ILogger log) {
+ // See the implementation detail note in loadUserAddons() about the synchronization.
+ synchronized (mSources) {
+ FileOutputStream fos = null;
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SRC_FILENAME);
+
+ fos = new FileOutputStream(f);
+
+ Properties props = new Properties();
+
+ int count = 0;
+ for (SdkSource s : getSources(SdkSourceCategory.USER_ADDONS)) {
+ props.setProperty(String.format("%s%02d", KEY_SRC, count), //$NON-NLS-1$
+ s.getUrl());
+ count++;
+ }
+ props.setProperty(KEY_COUNT, Integer.toString(count));
+
+ props.store( fos, "## User Sources for Android SDK Manager"); //$NON-NLS-1$
+
+ } catch (AndroidLocationException e) {
+ log.error(e, null);
+
+ } catch (IOException e) {
+ log.error(e, null);
+
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a listener that will be notified when the sources list has changed.
+ *
+ * @param changeListener A non-null listener to add. Ignored if already present.
+ * @see SdkSources#notifyChangeListeners()
+ */
+ public void addChangeListener(Runnable changeListener) {
+ assert changeListener != null;
+ if (mChangeListeners == null) {
+ mChangeListeners = new ArrayList<Runnable>();
+ }
+ synchronized (mChangeListeners) {
+ if (changeListener != null && !mChangeListeners.contains(changeListener)) {
+ mChangeListeners.add(changeListener);
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from the list of listeners to notify when the sources change.
+ *
+ * @param changeListener A listener to remove. Ignored if not previously added.
+ */
+ public void removeChangeListener(Runnable changeListener) {
+ if (mChangeListeners != null && changeListener != null) {
+ synchronized (mChangeListeners) {
+ mChangeListeners.remove(changeListener);
+ }
+ }
+ }
+
+ /**
+ * Invoke all the registered change listeners, if any.
+ * <p/>
+ * This <em>may</em> be called from a worker thread, in which case the runnable
+ * should take care of only updating UI from a main thread.
+ */
+ public void notifyChangeListeners() {
+ if (mChangeListeners == null) {
+ return;
+ }
+ synchronized (mChangeListeners) {
+ for (Runnable runnable : mChangeListeners) {
+ try {
+ runnable.run();
+ } catch (Throwable ignore) {
+ assert ignore == null :
+ "A SdkSource.ChangeListener failed with an exception: " + ignore.toString();
+ }
+ }
+ }
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java
new file mode 100755
index 0000000..7909bff
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSysImgSource.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 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.sources;
+
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.SdkSysImgConstants;
+
+import org.w3c.dom.Document;
+
+import java.io.InputStream;
+
+
+/**
+ * An sdk-sys-img source, i.e. a download site for system-image packages.
+ * A repository describes one or more {@link Package}s available for download.
+ */
+public class SdkSysImgSource extends SdkSource {
+
+ /**
+ * Constructs a new source for the given repository URL.
+ * @param url The source URL. Cannot be null. If the URL ends with a /, the default
+ * sys-img.xml filename will be appended automatically.
+ * @param uiName The UI-visible name of the source. Can be null.
+ */
+ public SdkSysImgSource(String url, String uiName) {
+ super(url, uiName);
+ }
+
+ /**
+ * Returns true if this is an addon source.
+ * We only load addons and extras from these sources.
+ */
+ @Override
+ public boolean isAddonSource() {
+ return false;
+ }
+
+ /**
+ * Returns true if this is a system-image source.
+ * We only load system-images from these sources.
+ */
+ @Override
+ public boolean isSysImgSource() {
+ return true;
+ }
+
+
+ @Override
+ protected String[] getDefaultXmlFileUrls() {
+ return new String[] { SdkSysImgConstants.URL_DEFAULT_FILENAME };
+ }
+
+ @Override
+ protected int getNsLatestVersion() {
+ return SdkSysImgConstants.NS_LATEST_VERSION;
+ }
+
+ @Override
+ protected String getNsUri() {
+ return SdkSysImgConstants.NS_URI;
+ }
+
+ @Override
+ protected String getNsPattern() {
+ return SdkSysImgConstants.NS_PATTERN;
+ }
+
+ @Override
+ protected String getSchemaUri(int version) {
+ return SdkSysImgConstants.getSchemaUri(version);
+ }
+
+ @Override
+ protected String getRootElementName() {
+ return SdkSysImgConstants.NODE_SDK_SYS_IMG;
+ }
+
+ @Override
+ protected InputStream getXsdStream(int version) {
+ return SdkSysImgConstants.getXsdStream(version);
+ }
+
+ /**
+ * This kind of schema does not support forward-evolution of the &lt;tool&gt; element.
+ *
+ * @param xml The input XML stream. Can be null.
+ * @return Always null.
+ * @null This implementation always return null.
+ */
+ @Override
+ protected Document findAlternateToolsXml(@Nullable InputStream xml) {
+ return null;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java
index a0fc8e3..7bbe54f 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java
@@ -1,340 +1,388 @@
-/*
- * Copyright (C) 2010 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.io;
-
-import com.android.sdklib.SdkConstants;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-
-/**
- * Wraps some common {@link File} operations on files and folders.
- * <p/>
- * This makes it possible to override/mock/stub some file operations in unit tests.
- */
-public class FileOp implements IFileOp {
-
- /**
- * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
- */
- private static Method sFileSetExecutable = null;
-
- /**
- * Parameters to call File.setExecutable through reflection.
- */
- private final static Object[] sFileSetExecutableParams = new Object[] {
- Boolean.TRUE, Boolean.FALSE };
-
- // static initialization of sFileSetExecutable.
- static {
- try {
- sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
- boolean.class, boolean.class);
-
- } catch (SecurityException e) {
- // do nothing we'll use chdmod instead
- } catch (NoSuchMethodException e) {
- // do nothing we'll use chdmod instead
- }
- }
-
- /**
- * Appends the given {@code segments} to the {@code base} file.
- *
- * @param base A base file, non-null.
- * @param segments Individual folder or filename segments to append to the base file.
- * @return A new file representing the concatenation of the base path with all the segments.
- */
- public static File append(File base, String...segments) {
- for (String segment : segments) {
- base = new File(base, segment);
- }
- return base;
- }
-
- /**
- * Appends the given {@code segments} to the {@code base} file.
- *
- * @param base A base file path, non-empty and non-null.
- * @param segments Individual folder or filename segments to append to the base path.
- * @return A new file representing the concatenation of the base path with all the segments.
- */
- public static File append(String base, String...segments) {
- return append(new File(base), segments);
- }
-
- /**
- * Helper to delete a file or a directory.
- * For a directory, recursively deletes all of its content.
- * Files that cannot be deleted right away are marked for deletion on exit.
- * The argument can be null.
- */
- @Override
- public void deleteFileOrFolder(File fileOrFolder) {
- if (fileOrFolder != null) {
- if (isDirectory(fileOrFolder)) {
- // Must delete content recursively first
- File[] files = fileOrFolder.listFiles();
- if (files != null) {
- for (File item : files) {
- deleteFileOrFolder(item);
- }
- }
- }
-
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- // Trying to delete a resource on windows might fail if there's a file
- // indexer locking the resource. Generally retrying will be enough to
- // make it work.
- //
- // Try for half a second before giving up.
-
- for (int i = 0; i < 5; i++) {
- if (fileOrFolder.delete()) {
- return;
- }
-
- try {
- Thread.sleep(100 /*ms*/);
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
-
- fileOrFolder.deleteOnExit();
-
- } else {
- // On Linux or Mac, just straight deleting it should just work.
-
- if (!fileOrFolder.delete()) {
- fileOrFolder.deleteOnExit();
- }
- }
- }
- }
-
- /**
- * Sets the executable Unix permission (+x) on a file or folder.
- * <p/>
- * This attempts to use File#setExecutable through reflection if
- * it's available.
- * If this is not available, this invokes a chmod exec instead,
- * so there is no guarantee of it being fast.
- * <p/>
- * Caller must make sure to not invoke this under Windows.
- *
- * @param file The file to set permissions on.
- * @throws IOException If an I/O error occurs
- */
- @Override
- public void setExecutablePermission(File file) throws IOException {
-
- if (sFileSetExecutable != null) {
- try {
- sFileSetExecutable.invoke(file, sFileSetExecutableParams);
- return;
- } catch (IllegalArgumentException e) {
- // we'll run chmod below
- } catch (IllegalAccessException e) {
- // we'll run chmod below
- } catch (InvocationTargetException e) {
- // we'll run chmod below
- }
- }
-
- Runtime.getRuntime().exec(new String[] {
- "chmod", "+x", file.getAbsolutePath() //$NON-NLS-1$ //$NON-NLS-2$
- });
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setReadOnly(File file) {
- file.setReadOnly();
- }
-
- /**
- * Copies a binary file.
- *
- * @param source the source file to copy.
- * @param dest the destination file to write.
- * @throws FileNotFoundException if the source file doesn't exist.
- * @throws IOException if there's a problem reading or writing the file.
- */
- @Override
- public void copyFile(File source, File dest) throws IOException {
- byte[] buffer = new byte[8192];
-
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try {
- fis = new FileInputStream(source);
- fos = new FileOutputStream(dest);
-
- int read;
- while ((read = fis.read(buffer)) != -1) {
- fos.write(buffer, 0, read);
- }
-
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- }
- }
-
- /**
- * Checks whether 2 binary files are the same.
- *
- * @param source the source file to copy
- * @param destination the destination file to write
- * @throws FileNotFoundException if the source files don't exist.
- * @throws IOException if there's a problem reading the files.
- */
- @Override
- public boolean isSameFile(File source, File destination) throws IOException {
-
- if (source.length() != destination.length()) {
- return false;
- }
-
- FileInputStream fis1 = null;
- FileInputStream fis2 = null;
-
- try {
- fis1 = new FileInputStream(source);
- fis2 = new FileInputStream(destination);
-
- byte[] buffer1 = new byte[8192];
- byte[] buffer2 = new byte[8192];
-
- int read1;
- while ((read1 = fis1.read(buffer1)) != -1) {
- int read2 = 0;
- while (read2 < read1) {
- int n = fis2.read(buffer2, read2, read1 - read2);
- if (n == -1) {
- break;
- }
- }
-
- if (read2 != read1) {
- return false;
- }
-
- if (!Arrays.equals(buffer1, buffer2)) {
- return false;
- }
- }
- } finally {
- if (fis2 != null) {
- try {
- fis2.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (fis1 != null) {
- try {
- fis1.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return true;
- }
-
- /** Invokes {@link File#isFile()} on the given {@code file}. */
- @Override
- public boolean isFile(File file) {
- return file.isFile();
- }
-
- /** Invokes {@link File#isDirectory()} on the given {@code file}. */
- @Override
- public boolean isDirectory(File file) {
- return file.isDirectory();
- }
-
- /** Invokes {@link File#exists()} on the given {@code file}. */
- @Override
- public boolean exists(File file) {
- return file.exists();
- }
-
- /** Invokes {@link File#length()} on the given {@code file}. */
- @Override
- public long length(File file) {
- return file.length();
- }
-
- /**
- * Invokes {@link File#delete()} on the given {@code file}.
- * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
- */
- @Override
- public boolean delete(File file) {
- return file.delete();
- }
-
- /** Invokes {@link File#mkdirs()} on the given {@code file}. */
- @Override
- public boolean mkdirs(File file) {
- return file.mkdirs();
- }
-
- /** Invokes {@link File#listFiles()} on the given {@code file}. */
- @Override
- public File[] listFiles(File file) {
- return file.listFiles();
- }
-
- /** Invokes {@link File#renameTo(File)} on the given files. */
- @Override
- public boolean renameTo(File oldFile, File newFile) {
- return oldFile.renameTo(newFile);
- }
-
- /** Creates a new {@link FileOutputStream} for the given {@code file}. */
- @Override
- public OutputStream newFileOutputStream(File file) throws FileNotFoundException {
- return new FileOutputStream(file);
- }
-}
+/*
+ * Copyright (C) 2010 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.io;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Properties;
+
+
+/**
+ * Wraps some common {@link File} operations on files and folders.
+ * <p/>
+ * This makes it possible to override/mock/stub some file operations in unit tests.
+ */
+public class FileOp implements IFileOp {
+
+ /**
+ * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
+ */
+ private static Method sFileSetExecutable = null;
+
+ /**
+ * Parameters to call File.setExecutable through reflection.
+ */
+ private final static Object[] sFileSetExecutableParams = new Object[] {
+ Boolean.TRUE, Boolean.FALSE };
+
+ // static initialization of sFileSetExecutable.
+ static {
+ try {
+ sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
+ boolean.class, boolean.class);
+
+ } catch (SecurityException e) {
+ // do nothing we'll use chdmod instead
+ } catch (NoSuchMethodException e) {
+ // do nothing we'll use chdmod instead
+ }
+ }
+
+ /**
+ * Appends the given {@code segments} to the {@code base} file.
+ *
+ * @param base A base file, non-null.
+ * @param segments Individual folder or filename segments to append to the base file.
+ * @return A new file representing the concatenation of the base path with all the segments.
+ */
+ public static File append(File base, String...segments) {
+ for (String segment : segments) {
+ base = new File(base, segment);
+ }
+ return base;
+ }
+
+ /**
+ * Appends the given {@code segments} to the {@code base} file.
+ *
+ * @param base A base file path, non-empty and non-null.
+ * @param segments Individual folder or filename segments to append to the base path.
+ * @return A new file representing the concatenation of the base path with all the segments.
+ */
+ public static File append(String base, String...segments) {
+ return append(new File(base), segments);
+ }
+
+ /**
+ * Helper to delete a file or a directory.
+ * For a directory, recursively deletes all of its content.
+ * Files that cannot be deleted right away are marked for deletion on exit.
+ * It's ok for the file or folder to not exist at all.
+ * The argument can be null.
+ */
+ @Override
+ public void deleteFileOrFolder(File fileOrFolder) {
+ if (fileOrFolder != null) {
+ if (isDirectory(fileOrFolder)) {
+ // Must delete content recursively first
+ File[] files = fileOrFolder.listFiles();
+ if (files != null) {
+ for (File item : files) {
+ deleteFileOrFolder(item);
+ }
+ }
+ }
+
+ // Don't try to delete it if it doesn't exist.
+ if (!exists(fileOrFolder)) {
+ return;
+ }
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ // Trying to delete a resource on windows might fail if there's a file
+ // indexer locking the resource. Generally retrying will be enough to
+ // make it work.
+ //
+ // Try for half a second before giving up.
+
+ for (int i = 0; i < 5; i++) {
+ if (fileOrFolder.delete()) {
+ return;
+ }
+
+ try {
+ Thread.sleep(100 /*ms*/);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+
+ fileOrFolder.deleteOnExit();
+
+ } else {
+ // On Linux or Mac, just straight deleting it should just work.
+
+ if (!fileOrFolder.delete()) {
+ fileOrFolder.deleteOnExit();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the executable Unix permission (+x) on a file or folder.
+ * <p/>
+ * This attempts to use File#setExecutable through reflection if
+ * it's available.
+ * If this is not available, this invokes a chmod exec instead,
+ * so there is no guarantee of it being fast.
+ * <p/>
+ * Caller must make sure to not invoke this under Windows.
+ *
+ * @param file The file to set permissions on.
+ * @throws IOException If an I/O error occurs
+ */
+ @Override
+ public void setExecutablePermission(File file) throws IOException {
+
+ if (sFileSetExecutable != null) {
+ try {
+ sFileSetExecutable.invoke(file, sFileSetExecutableParams);
+ return;
+ } catch (IllegalArgumentException e) {
+ // we'll run chmod below
+ } catch (IllegalAccessException e) {
+ // we'll run chmod below
+ } catch (InvocationTargetException e) {
+ // we'll run chmod below
+ }
+ }
+
+ Runtime.getRuntime().exec(new String[] {
+ "chmod", "+x", file.getAbsolutePath() //$NON-NLS-1$ //$NON-NLS-2$
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setReadOnly(File file) {
+ file.setReadOnly();
+ }
+
+ /**
+ * Copies a binary file.
+ *
+ * @param source the source file to copy.
+ * @param dest the destination file to write.
+ * @throws FileNotFoundException if the source file doesn't exist.
+ * @throws IOException if there's a problem reading or writing the file.
+ */
+ @Override
+ public void copyFile(File source, File dest) throws IOException {
+ byte[] buffer = new byte[8192];
+
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ try {
+ fis = new FileInputStream(source);
+ fos = new FileOutputStream(dest);
+
+ int read;
+ while ((read = fis.read(buffer)) != -1) {
+ fos.write(buffer, 0, read);
+ }
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether 2 binary files are the same.
+ *
+ * @param source the source file to copy
+ * @param destination the destination file to write
+ * @throws FileNotFoundException if the source files don't exist.
+ * @throws IOException if there's a problem reading the files.
+ */
+ @Override
+ public boolean isSameFile(File source, File destination) throws IOException {
+
+ if (source.length() != destination.length()) {
+ return false;
+ }
+
+ FileInputStream fis1 = null;
+ FileInputStream fis2 = null;
+
+ try {
+ fis1 = new FileInputStream(source);
+ fis2 = new FileInputStream(destination);
+
+ byte[] buffer1 = new byte[8192];
+ byte[] buffer2 = new byte[8192];
+
+ int read1;
+ while ((read1 = fis1.read(buffer1)) != -1) {
+ int read2 = 0;
+ while (read2 < read1) {
+ int n = fis2.read(buffer2, read2, read1 - read2);
+ if (n == -1) {
+ break;
+ }
+ }
+
+ if (read2 != read1) {
+ return false;
+ }
+
+ if (!Arrays.equals(buffer1, buffer2)) {
+ return false;
+ }
+ }
+ } finally {
+ if (fis2 != null) {
+ try {
+ fis2.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (fis1 != null) {
+ try {
+ fis1.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /** Invokes {@link File#isFile()} on the given {@code file}. */
+ @Override
+ public boolean isFile(File file) {
+ return file.isFile();
+ }
+
+ /** Invokes {@link File#isDirectory()} on the given {@code file}. */
+ @Override
+ public boolean isDirectory(File file) {
+ return file.isDirectory();
+ }
+
+ /** Invokes {@link File#exists()} on the given {@code file}. */
+ @Override
+ public boolean exists(File file) {
+ return file.exists();
+ }
+
+ /** Invokes {@link File#length()} on the given {@code file}. */
+ @Override
+ public long length(File file) {
+ return file.length();
+ }
+
+ /**
+ * Invokes {@link File#delete()} on the given {@code file}.
+ * Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
+ */
+ @Override
+ public boolean delete(File file) {
+ return file.delete();
+ }
+
+ /** Invokes {@link File#mkdirs()} on the given {@code file}. */
+ @Override
+ public boolean mkdirs(File file) {
+ return file.mkdirs();
+ }
+
+ /** Invokes {@link File#listFiles()} on the given {@code file}. */
+ @Override
+ public File[] listFiles(File file) {
+ return file.listFiles();
+ }
+
+ /** Invokes {@link File#renameTo(File)} on the given files. */
+ @Override
+ public boolean renameTo(File oldFile, File newFile) {
+ return oldFile.renameTo(newFile);
+ }
+
+ /** Creates a new {@link FileOutputStream} for the given {@code file}. */
+ @Override
+ public OutputStream newFileOutputStream(File file) throws FileNotFoundException {
+ return new FileOutputStream(file);
+ }
+
+ @Override
+ public @NonNull Properties loadProperties(@NonNull File file) {
+ Properties props = new Properties();
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ props.load(fis);
+ } catch (IOException ignore) {
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (Exception ignore) {}
+ }
+ }
+ return props;
+ }
+
+ @Override
+ public boolean saveProperties(@NonNull File file, @NonNull Properties props,
+ @NonNull String comments) {
+ OutputStream fos = null;
+ try {
+ fos = newFileOutputStream(file);
+
+ props.store(fos, comments);
+ return true;
+ } catch (IOException ignore) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IFileOp.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IFileOp.java
index b0d442b..5b131d5 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IFileOp.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IFileOp.java
@@ -16,11 +16,14 @@
package com.android.sdklib.io;
+import com.android.annotations.NonNull;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Properties;
/**
@@ -34,6 +37,7 @@ public interface IFileOp {
* Helper to delete a file or a directory.
* For a directory, recursively deletes all of its content.
* Files that cannot be deleted right away are marked for deletion on exit.
+ * It's ok for the file or folder to not exist at all.
* The argument can be null.
*/
public abstract void deleteFileOrFolder(File fileOrFolder);
@@ -111,4 +115,25 @@ public interface IFileOp {
/** Creates a new {@link FileOutputStream} for the given {@code file}. */
public abstract OutputStream newFileOutputStream(File file) throws FileNotFoundException;
+ /**
+ * Load {@link Properties} from a file. Returns an empty property set on error.
+ *
+ * @param file A non-null file to load from. File may not exist.
+ * @return A new {@link Properties} with the properties loaded from the file,
+ * or an empty property set in case of error.
+ */
+ public @NonNull Properties loadProperties(@NonNull File file);
+
+ /**
+ * Saves (write, store) the given {@link Properties} into the given {@link File}.
+ *
+ * @param file A non-null file to write to.
+ * @param props The properties to write.
+ * @param comments A non-null description of the properly list, written in the file.
+ * @return True if the properties could be saved, false otherwise.
+ */
+ public boolean saveProperties(
+ @NonNull File file,
+ @NonNull Properties props,
+ @NonNull String comments);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java
new file mode 100755
index 0000000..470b706
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 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.io;
+
+import com.android.annotations.NonNull;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Wraps an {@link InputStream} to change its closing behavior:
+ * this makes it possible to ignore close operations or have them perform a
+ * {@link InputStream#reset()} instead (if supported by the underlying stream)
+ * or plain ignored.
+ */
+public class NonClosingInputStream extends FilterInputStream {
+
+ private final InputStream mInputStream;
+ private CloseBehavior mCloseBehavior = CloseBehavior.CLOSE;
+
+ public enum CloseBehavior {
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to close the
+ * underlying input stream. This is the default.
+ */
+ CLOSE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to ignore the
+ * close request and do nothing.
+ */
+ IGNORE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to call
+ * {@link InputStream#reset()} on the underlying stream. This will
+ * only succeed if the underlying stream supports it, e.g. it must
+ * have {@link InputStream#markSupported()} return true <em>and</em>
+ * the caller should have called {@link InputStream#mark(int)} at some
+ * point before.
+ */
+ RESET
+ }
+
+ /**
+ * Wraps an existing stream into this filtering stream.
+ * @param in A non-null input stream.
+ */
+ public NonClosingInputStream(@NonNull InputStream in) {
+ super(in);
+ mInputStream = in;
+ }
+
+ /**
+ * Returns the current {@link CloseBehavior}.
+ * @return the current {@link CloseBehavior}. Never null.
+ */
+ public @NonNull CloseBehavior getCloseBehavior() {
+ return mCloseBehavior;
+ }
+
+ /**
+ * Changes the current {@link CloseBehavior}.
+ *
+ * @param closeBehavior A new non-null {@link CloseBehavior}.
+ * @return Self for chaining.
+ */
+ public NonClosingInputStream setCloseBehavior(@NonNull CloseBehavior closeBehavior) {
+ mCloseBehavior = closeBehavior;
+ return this;
+ }
+
+ /**
+ * Performs the requested {@code close()} operation, depending on the current
+ * {@link CloseBehavior}.
+ */
+ @Override
+ public void close() throws IOException {
+ switch (mCloseBehavior) {
+ case IGNORE:
+ break;
+ case RESET:
+ mInputStream.reset();
+ break;
+ case CLOSE:
+ mInputStream.close();
+ break;
+ }
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
index 579656a..68d7119 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
@@ -1,93 +1,99 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-
-
-/**
- * Public constants used by the repository when saving {@code source.properties}
- * files in local packages.
- * <p/>
- * These constants are public and part of the SDK Manager public API.
- * Once published we can't change them arbitrarily since various parts
- * of our build process depend on them.
- */
-public class PkgProps {
-
- // Base Package
-
- public static final String PKG_REVISION = "Pkg.Revision"; //$NON-NLS-1$
- public static final String PKG_LICENSE = "Pkg.License"; //$NON-NLS-1$
- public static final String PKG_DESC = "Pkg.Desc"; //$NON-NLS-1$
- public static final String PKG_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$
- public static final String PKG_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$
- public static final String PKG_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$
- public static final String PKG_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
- public static final String PKG_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$
-
- // AndroidVersion
-
- public static final String VERSION_API_LEVEL = "AndroidVersion.ApiLevel";//$NON-NLS-1$
- public static final String VERSION_CODENAME = "AndroidVersion.CodeName";//$NON-NLS-1$
-
- // AddonPackage
-
- public static final String ADDON_NAME = "Addon.Name"; //$NON-NLS-1$
- public static final String ADDON_NAME_ID = "Addon.NameId"; //$NON-NLS-1$
- public static final String ADDON_NAME_DISPLAY = "Addon.NameDisplay"; //$NON-NLS-1$
-
- public static final String ADDON_VENDOR = "Addon.Vendor"; //$NON-NLS-1$
- public static final String ADDON_VENDOR_ID = "Addon.VendorId"; //$NON-NLS-1$
- public static final String ADDON_VENDOR_DISPLAY = "Addon.VendorDisplay"; //$NON-NLS-1$
-
- // DocPackage
-
- // ExtraPackage
-
- public static final String EXTRA_PATH = "Extra.Path"; //$NON-NLS-1$
- public static final String EXTRA_OLD_PATHS = "Extra.OldPaths"; //$NON-NLS-1$
- public static final String EXTRA_MIN_API_LEVEL = "Extra.MinApiLevel"; //$NON-NLS-1$
- public static final String EXTRA_PROJECT_FILES = "Extra.ProjectFiles"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR = "Extra.Vendor"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR_ID = "Extra.VendorId"; //$NON-NLS-1$
- public static final String EXTRA_VENDOR_DISPLAY = "Extra.VendorDisplay"; //$NON-NLS-1$
- public static final String EXTRA_NAME_DISPLAY = "Extra.NameDisplay"; //$NON-NLS-1$
-
- // ILayoutlibVersion
-
- public static final String LAYOUTLIB_API = "Layoutlib.Api"; //$NON-NLS-1$
- public static final String LAYOUTLIB_REV = "Layoutlib.Revision"; //$NON-NLS-1$
-
- // MinToolsPackage
-
- public static final String MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
-
- // PlatformPackage
-
- public static final String PLATFORM_VERSION = "Platform.Version"; //$NON-NLS-1$
- public static final String PLATFORM_INCLUDED_ABI = "Platform.Included.Abi"; //$NON-NLS-1$
-
- // PlatformToolPackage
-
- // SamplePackage
-
- public static final String SAMPLE_MIN_API_LEVEL = "Sample.MinApiLevel"; //$NON-NLS-1$
-
- // SystemImagePackage
-
- public static final String SYS_IMG_ABI = "SystemImage.Abi"; //$NON-NLS-1$
-}
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+
+/**
+ * Public constants used by the repository when saving {@code source.properties}
+ * files in local packages.
+ * <p/>
+ * These constants are public and part of the SDK Manager public API.
+ * Once published we can't change them arbitrarily since various parts
+ * of our build process depend on them.
+ */
+public class PkgProps {
+
+ // Base Package
+ public static final String PKG_REVISION = "Pkg.Revision"; //$NON-NLS-1$
+ public static final String PKG_LICENSE = "Pkg.License"; //$NON-NLS-1$
+ public static final String PKG_DESC = "Pkg.Desc"; //$NON-NLS-1$
+ public static final String PKG_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$
+ public static final String PKG_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$
+ public static final String PKG_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$
+ public static final String PKG_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
+ public static final String PKG_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$
+
+ // AndroidVersion
+
+ public static final String VERSION_API_LEVEL = "AndroidVersion.ApiLevel";//$NON-NLS-1$
+ /** Code name of the platform if the platform is not final */
+ public static final String VERSION_CODENAME = "AndroidVersion.CodeName";//$NON-NLS-1$
+
+
+ // AddonPackage
+
+ public static final String ADDON_NAME = "Addon.Name"; //$NON-NLS-1$
+ public static final String ADDON_NAME_ID = "Addon.NameId"; //$NON-NLS-1$
+ public static final String ADDON_NAME_DISPLAY = "Addon.NameDisplay"; //$NON-NLS-1$
+
+ public static final String ADDON_VENDOR = "Addon.Vendor"; //$NON-NLS-1$
+ public static final String ADDON_VENDOR_ID = "Addon.VendorId"; //$NON-NLS-1$
+ public static final String ADDON_VENDOR_DISPLAY = "Addon.VendorDisplay"; //$NON-NLS-1$
+
+ // DocPackage
+
+ // ExtraPackage
+
+ public static final String EXTRA_PATH = "Extra.Path"; //$NON-NLS-1$
+ public static final String EXTRA_OLD_PATHS = "Extra.OldPaths"; //$NON-NLS-1$
+ public static final String EXTRA_MIN_API_LEVEL = "Extra.MinApiLevel"; //$NON-NLS-1$
+ public static final String EXTRA_PROJECT_FILES = "Extra.ProjectFiles"; //$NON-NLS-1$
+ public static final String EXTRA_VENDOR = "Extra.Vendor"; //$NON-NLS-1$
+ public static final String EXTRA_VENDOR_ID = "Extra.VendorId"; //$NON-NLS-1$
+ public static final String EXTRA_VENDOR_DISPLAY = "Extra.VendorDisplay"; //$NON-NLS-1$
+ public static final String EXTRA_NAME_DISPLAY = "Extra.NameDisplay"; //$NON-NLS-1$
+
+ // ILayoutlibVersion
+
+ public static final String LAYOUTLIB_API = "Layoutlib.Api"; //$NON-NLS-1$
+ public static final String LAYOUTLIB_REV = "Layoutlib.Revision"; //$NON-NLS-1$
+
+ // MinToolsPackage
+
+ public static final String MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
+
+ // PlatformPackage
+
+ public static final String PLATFORM_VERSION = "Platform.Version"; //$NON-NLS-1$
+ /** Code name of the platform. This has no bearing on the package being a preview or not. */
+ public static final String PLATFORM_CODENAME = "Platform.CodeName"; //$NON-NLS-1$
+ public static final String PLATFORM_INCLUDED_ABI = "Platform.Included.Abi"; //$NON-NLS-1$
+
+ // ToolPackage
+
+ public static final String MIN_PLATFORM_TOOLS_REV = "Platform.MinPlatformToolsRev";//$NON-NLS-1$
+
+
+ // SamplePackage
+
+ public static final String SAMPLE_MIN_API_LEVEL = "Sample.MinApiLevel"; //$NON-NLS-1$
+
+ // SystemImagePackage
+
+ public static final String SYS_IMG_ABI = "SystemImage.Abi"; //$NON-NLS-1$
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/RepoConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/RepoConstants.java
index 53db79f..3fdfc91 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/RepoConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/RepoConstants.java
@@ -1,168 +1,176 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-import java.io.InputStream;
-
-
-
-/**
- * Public constants common to the sdk-repository and sdk-addon XML Schemas.
- */
-public class RepoConstants {
-
- /** The license definition. */
- public static final String NODE_LICENSE = "license"; //$NON-NLS-1$
- /** The optional uses-license for all packages or for a lib. */
- public static final String NODE_USES_LICENSE = "uses-license"; //$NON-NLS-1$
- /** The revision, an int > 0, for all packages. */
- public static final String NODE_REVISION = "revision"; //$NON-NLS-1$
- /** The optional description for all packages or for a lib. */
- public static final String NODE_DESCRIPTION = "description"; //$NON-NLS-1$
- /** The optional description URL for all packages. */
- public static final String NODE_DESC_URL = "desc-url"; //$NON-NLS-1$
- /** The optional release note for all packages. */
- public static final String NODE_RELEASE_NOTE = "release-note"; //$NON-NLS-1$
- /** The optional release note URL for all packages. */
- public static final String NODE_RELEASE_URL = "release-url"; //$NON-NLS-1$
- /** The optional obsolete qualifier for all packages. */
- public static final String NODE_OBSOLETE = "obsolete"; //$NON-NLS-1$
- /** The optional project-files provided by extra packages. */
- public static final String NODE_PROJECT_FILES = "project-files"; //$NON-NLS-1$
-
- /** The optional minimal tools revision required by platform & extra packages. */
- public static final String NODE_MIN_TOOLS_REV = "min-tools-rev"; //$NON-NLS-1$
- /** The optional minimal platform-tools revision required by tool packages. */
- public static final String NODE_MIN_PLATFORM_TOOLS_REV = "min-platform-tools-rev"; //$NON-NLS-1$
- /** The optional minimal API level required by extra packages. */
- public static final String NODE_MIN_API_LEVEL = "min-api-level"; //$NON-NLS-1$
-
- /** The version, a string, for platform packages. */
- public static final String NODE_VERSION = "version"; //$NON-NLS-1$
- /** The api-level, an int > 0, for platform, add-on and doc packages. */
- public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
- /** The codename, a string, for platform packages. */
- public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
- /** The *old* vendor, a string, for add-on and extra packages.
- * Replaced by {@link #NODE_VENDOR_DISPLAY} and {@link #NODE_VENDOR_ID} in addon-v4.xsd. */
- public static final String NODE_VENDOR = "vendor"; //$NON-NLS-1$
- /** The vendor display string, for add-on and extra packages. */
- public static final String NODE_VENDOR_DISPLAY = "vendor-display"; //$NON-NLS-1$
- /** The unique vendor id string, for add-on and extra packages. */
- public static final String NODE_VENDOR_ID = "vendor-id"; //$NON-NLS-1$
- /** The name, a string, for add-on packages or for libraries.
- * Replaced by {@link #NODE_NAME_DISPLAY} and {@link #NODE_NAME_ID} in addon-v4.xsd. */
- public static final String NODE_NAME = "name"; //$NON-NLS-1$
- /** The name display string, for add-on packages or for libraries. */
- public static final String NODE_NAME_DISPLAY = "name-display"; //$NON-NLS-1$
- /** The unique name id string, for add-on packages or for libraries. */
- public static final String NODE_NAME_ID = "name-id"; //$NON-NLS-1$
-
-
- /** A layoutlib package. */
- public static final String NODE_LAYOUT_LIB = "layoutlib"; //$NON-NLS-1$
- /** The API integer for a layoutlib element. */
- public static final String NODE_API = "api"; //$NON-NLS-1$
-
- /** The libs container, optional for an add-on. */
- public static final String NODE_LIBS = "libs"; //$NON-NLS-1$
- /** A lib element in a libs container. */
- public static final String NODE_LIB = "lib"; //$NON-NLS-1$
-
- /** The path segment, a string, for extra packages. */
- public static final String NODE_PATH = "path"; //$NON-NLS-1$
-
- /** The old_path segments, a string, for extra packages. */
- public static final String NODE_OLD_PATHS = "old-paths"; //$NON-NLS-1$
-
- /** The archives container, for all packages. */
- public static final String NODE_ARCHIVES = "archives"; //$NON-NLS-1$
- /** An archive element, for the archives container. */
- public static final String NODE_ARCHIVE = "archive"; //$NON-NLS-1$
-
- /** An archive size, an int > 0. */
- public static final String NODE_SIZE = "size"; //$NON-NLS-1$
- /** A sha1 archive checksum, as a 40-char hex. */
- public static final String NODE_CHECKSUM = "checksum"; //$NON-NLS-1$
- /** A download archive URL, either absolute or relative to the repository xml. */
- public static final String NODE_URL = "url"; //$NON-NLS-1$
-
- /** An archive checksum type, mandatory. */
- public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
- /** An archive OS attribute, mandatory. */
- public static final String ATTR_OS = "os"; //$NON-NLS-1$
- /** An optional archive Architecture attribute. */
- public static final String ATTR_ARCH = "arch"; //$NON-NLS-1$
-
- /** A license definition ID. */
- public static final String ATTR_ID = "id"; //$NON-NLS-1$
- /** A license reference. */
- public static final String ATTR_REF = "ref"; //$NON-NLS-1$
-
- /** Type of a sha1 checksum. */
- public static final String SHA1_TYPE = "sha1"; //$NON-NLS-1$
-
- /** Length of a string representing a SHA1 checksum; always 40 characters long. */
- public static final int SHA1_CHECKSUM_LEN = 40;
-
- /**
- * Temporary folder used to hold downloads and extract archives during installation.
- * This folder will be located in the SDK.
- */
- public static final String FD_TEMP = "temp"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested XML Schema.
- * This is an internal helper. Users of the library should call
- * {@link SdkRepoConstants#getXsdStream(String, int)} or
- * {@link SdkAddonConstants#getXsdStream(String, int)}.
- *
- * @param rootElement The root of the filename of the XML schema.
- * This is by convention the same as the root element declared by the schema.
- * @param version The XML schema revision number, an integer >= 1.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- * @see SdkRepoConstants#getXsdStream(int)
- * @see SdkAddonConstants#getXsdStream(int)
- */
- protected static InputStream getXsdStream(String rootElement, int version) {
- String filename = String.format("%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
-
- InputStream stream = null;
- try {
- stream = RepoConstants.class.getResourceAsStream(filename);
- } catch (Exception e) {
- // Some implementations seem to return null on failure,
- // others throw an exception. We want to return null.
- }
- if (stream == null) {
- // Try the alternate schemas that are not published yet.
- // This allows us to internally test with new schemas before the
- // public repository uses it.
- filename = String.format("-%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
- try {
- stream = RepoConstants.class.getResourceAsStream(filename);
- } catch (Exception e) {
- // Some implementations seem to return null on failure,
- // others throw an exception. We want to return null.
- }
- }
-
- return stream;
- }
-
-}
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+import java.io.InputStream;
+
+
+
+/**
+ * Public constants common to the sdk-repository and sdk-addon XML Schemas.
+ */
+public class RepoConstants {
+
+ /** The license definition. */
+ public static final String NODE_LICENSE = "license"; //$NON-NLS-1$
+ /** The optional uses-license for all packages or for a lib. */
+ public static final String NODE_USES_LICENSE = "uses-license"; //$NON-NLS-1$
+ /** The revision, an int > 0, for all packages. */
+ public static final String NODE_REVISION = "revision"; //$NON-NLS-1$
+ /** The optional description for all packages or for a lib. */
+ public static final String NODE_DESCRIPTION = "description"; //$NON-NLS-1$
+ /** The optional description URL for all packages. */
+ public static final String NODE_DESC_URL = "desc-url"; //$NON-NLS-1$
+ /** The optional release note for all packages. */
+ public static final String NODE_RELEASE_NOTE = "release-note"; //$NON-NLS-1$
+ /** The optional release note URL for all packages. */
+ public static final String NODE_RELEASE_URL = "release-url"; //$NON-NLS-1$
+ /** The optional obsolete qualifier for all packages. */
+ public static final String NODE_OBSOLETE = "obsolete"; //$NON-NLS-1$
+ /** The optional project-files provided by extra packages. */
+ public static final String NODE_PROJECT_FILES = "project-files"; //$NON-NLS-1$
+
+ /** A system-image package. */
+ public static final String NODE_SYSTEM_IMAGE = "system-image"; //$NON-NLS-1$
+
+ /* An included-ABI element for a system-image package. */
+ public static final String NODE_ABI_INCLUDED = "included-abi"; //$NON-NLS-1$
+ /* An ABI element for a system-image package. */
+ public static final String NODE_ABI = "abi"; //$NON-NLS-1$
+
+ /** The optional minimal tools revision required by platform & extra packages. */
+ public static final String NODE_MIN_TOOLS_REV = "min-tools-rev"; //$NON-NLS-1$
+ /** The optional minimal platform-tools revision required by tool packages. */
+ public static final String NODE_MIN_PLATFORM_TOOLS_REV = "min-platform-tools-rev"; //$NON-NLS-1$
+ /** The optional minimal API level required by extra packages. */
+ public static final String NODE_MIN_API_LEVEL = "min-api-level"; //$NON-NLS-1$
+
+ /** The version, a string, for platform packages. */
+ public static final String NODE_VERSION = "version"; //$NON-NLS-1$
+ /** The api-level, an int > 0, for platform, add-on and doc packages. */
+ public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
+ /** The codename, a string, for platform packages. */
+ public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
+ /** The *old* vendor, a string, for add-on and extra packages.
+ * Replaced by {@link #NODE_VENDOR_DISPLAY} and {@link #NODE_VENDOR_ID} in addon-v4.xsd. */
+ public static final String NODE_VENDOR = "vendor"; //$NON-NLS-1$
+ /** The vendor display string, for add-on and extra packages. */
+ public static final String NODE_VENDOR_DISPLAY = "vendor-display"; //$NON-NLS-1$
+ /** The unique vendor id string, for add-on and extra packages. */
+ public static final String NODE_VENDOR_ID = "vendor-id"; //$NON-NLS-1$
+ /** The name, a string, for add-on packages or for libraries.
+ * Replaced by {@link #NODE_NAME_DISPLAY} and {@link #NODE_NAME_ID} in addon-v4.xsd. */
+ public static final String NODE_NAME = "name"; //$NON-NLS-1$
+ /** The name display string, for add-on packages or for libraries. */
+ public static final String NODE_NAME_DISPLAY = "name-display"; //$NON-NLS-1$
+ /** The unique name id string, for add-on packages or for libraries. */
+ public static final String NODE_NAME_ID = "name-id"; //$NON-NLS-1$
+
+
+ /** A layoutlib package. */
+ public static final String NODE_LAYOUT_LIB = "layoutlib"; //$NON-NLS-1$
+ /** The API integer for a layoutlib element. */
+ public static final String NODE_API = "api"; //$NON-NLS-1$
+
+ /** The libs container, optional for an add-on. */
+ public static final String NODE_LIBS = "libs"; //$NON-NLS-1$
+ /** A lib element in a libs container. */
+ public static final String NODE_LIB = "lib"; //$NON-NLS-1$
+
+ /** The path segment, a string, for extra packages. */
+ public static final String NODE_PATH = "path"; //$NON-NLS-1$
+
+ /** The old_path segments, a string, for extra packages. */
+ public static final String NODE_OLD_PATHS = "old-paths"; //$NON-NLS-1$
+
+ /** The archives container, for all packages. */
+ public static final String NODE_ARCHIVES = "archives"; //$NON-NLS-1$
+ /** An archive element, for the archives container. */
+ public static final String NODE_ARCHIVE = "archive"; //$NON-NLS-1$
+
+ /** An archive size, an int > 0. */
+ public static final String NODE_SIZE = "size"; //$NON-NLS-1$
+ /** A sha1 archive checksum, as a 40-char hex. */
+ public static final String NODE_CHECKSUM = "checksum"; //$NON-NLS-1$
+ /** A download archive URL, either absolute or relative to the repository xml. */
+ public static final String NODE_URL = "url"; //$NON-NLS-1$
+
+ /** An archive checksum type, mandatory. */
+ public static final String ATTR_TYPE = "type"; //$NON-NLS-1$
+ /** An archive OS attribute, mandatory. */
+ public static final String ATTR_OS = "os"; //$NON-NLS-1$
+ /** An optional archive Architecture attribute. */
+ public static final String ATTR_ARCH = "arch"; //$NON-NLS-1$
+
+ /** A license definition ID. */
+ public static final String ATTR_ID = "id"; //$NON-NLS-1$
+ /** A license reference. */
+ public static final String ATTR_REF = "ref"; //$NON-NLS-1$
+
+ /** Type of a sha1 checksum. */
+ public static final String SHA1_TYPE = "sha1"; //$NON-NLS-1$
+
+ /** Length of a string representing a SHA1 checksum; always 40 characters long. */
+ public static final int SHA1_CHECKSUM_LEN = 40;
+
+ /**
+ * Temporary folder used to hold downloads and extract archives during installation.
+ * This folder will be located in the SDK.
+ */
+ public static final String FD_TEMP = "temp"; //$NON-NLS-1$
+
+ /**
+ * Returns a stream to the requested XML Schema.
+ * This is an internal helper. Users of the library should call
+ * {@link SdkRepoConstants#getXsdStream(String, int)} or
+ * {@link SdkAddonConstants#getXsdStream(String, int)}.
+ *
+ * @param rootElement The root of the filename of the XML schema.
+ * This is by convention the same as the root element declared by the schema.
+ * @param version The XML schema revision number, an integer >= 1.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ * @see SdkRepoConstants#getXsdStream(int)
+ * @see SdkAddonConstants#getXsdStream(int)
+ */
+ protected static InputStream getXsdStream(String rootElement, int version) {
+ String filename = String.format("%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
+
+ InputStream stream = null;
+ try {
+ stream = RepoConstants.class.getResourceAsStream(filename);
+ } catch (Exception e) {
+ // Some implementations seem to return null on failure,
+ // others throw an exception. We want to return null.
+ }
+ if (stream == null) {
+ // Try the alternate schemas that are not published yet.
+ // This allows us to internally test with new schemas before the
+ // public repository uses it.
+ filename = String.format("-%1$s-%2$d.xsd", rootElement, version); //$NON-NLS-1$
+ try {
+ stream = RepoConstants.class.getResourceAsStream(filename);
+ } catch (Exception e) {
+ // Some implementations seem to return null on failure,
+ // others throw an exception. We want to return null.
+ }
+ }
+
+ return stream;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
index 52d3a14..4af2276 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
@@ -1,90 +1,90 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-addon XML Schema.
- */
-public class SdkAddonConstants extends RepoConstants {
-
- /**
- * The default name looked for by {@link SdkSource} when trying to load an
- * sdk-addon XML if the URL doesn't match an existing resource.
- */
- public static final String URL_DEFAULT_FILENAME = "addon.xml"; //$NON-NLS-1$
-
- /** The base of our sdk-addon XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/addon/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-addon XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /**
- * The latest version of the sdk-addon XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 4;
-
- /** The XML namespace of the latest sdk-addon XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-addon element */
- public static final String NODE_SDK_ADDON = "sdk-addon"; //$NON-NLS-1$
-
- /** An add-on package. */
- public static final String NODE_ADD_ON = "add-on"; //$NON-NLS-1$
-
- /** An extra package. */
- public static final String NODE_EXTRA = "extra"; //$NON-NLS-1$
-
- /**
- * List of possible nodes in a repository XML. Used to populate options automatically
- * in the no-GUI mode.
- */
- public static final String[] NODES = {
- NODE_ADD_ON,
- NODE_EXTRA
- };
-
- /**
- * Returns a stream to the requested {@code sdk-addon} XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- return getXsdStream(NODE_SDK_ADDON, version);
- }
-
- /**
- * Returns the URI of the sdk-addon schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+import com.android.sdklib.internal.repository.sources.SdkSource;
+
+import java.io.InputStream;
+
+/**
+ * Public constants for the sdk-addon XML Schema.
+ */
+public class SdkAddonConstants extends RepoConstants {
+
+ /**
+ * The default name looked for by {@link SdkSource} when trying to load an
+ * sdk-addon XML if the URL doesn't match an existing resource.
+ */
+ public static final String URL_DEFAULT_FILENAME = "addon.xml"; //$NON-NLS-1$
+
+ /** The base of our sdk-addon XML namespace. */
+ private static final String NS_BASE =
+ "http://schemas.android.com/sdk/android/addon/"; //$NON-NLS-1$
+
+ /**
+ * The pattern of our sdk-addon XML namespace.
+ * Matcher's group(1) is the schema version (integer).
+ */
+ public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+
+ /**
+ * The latest version of the sdk-addon XML Schema.
+ * Valid version numbers are between 1 and this number, included.
+ */
+ public static final int NS_LATEST_VERSION = 5;
+
+ /** The XML namespace of the latest sdk-addon XML. */
+ public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
+
+ /** The root sdk-addon element */
+ public static final String NODE_SDK_ADDON = "sdk-addon"; //$NON-NLS-1$
+
+ /** An add-on package. */
+ public static final String NODE_ADD_ON = "add-on"; //$NON-NLS-1$
+
+ /** An extra package. */
+ public static final String NODE_EXTRA = "extra"; //$NON-NLS-1$
+
+ /**
+ * List of possible nodes in a repository XML. Used to populate options automatically
+ * in the no-GUI mode.
+ */
+ public static final String[] NODES = {
+ NODE_ADD_ON,
+ NODE_EXTRA
+ };
+
+ /**
+ * Returns a stream to the requested {@code sdk-addon} XML Schema.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ return getXsdStream(NODE_SDK_ADDON, version);
+ }
+
+ /**
+ * Returns the URI of the sdk-addon schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonsListConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonsListConstants.java
index b5b0249..4f6b897 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonsListConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonsListConstants.java
@@ -1,98 +1,108 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-addons-list XML Schema.
- */
-public class SdkAddonsListConstants {
-
- /** The canonical URL filename for addons-list XML files. */
- public static final String URL_DEFAULT_FILENAME = "addons_list-1.xml"; //$NON-NLS-1$
-
- /** The URL where to find the official addons list fle. */
- public static final String URL_ADDON_LIST =
- SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
-
- /** The base of our sdk-addons-list XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/addons-list/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-addons-list XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /** The latest version of the sdk-addons-list XML Schema.
- * Valid version numbers are between 1 and this number, included. */
- public static final int NS_LATEST_VERSION = 1;
-
- /** The XML namespace of the latest sdk-addons-list XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-addons-list element */
- public static final String NODE_SDK_ADDONS_LIST = "sdk-addons-list"; //$NON-NLS-1$
-
- /** An add-on site. */
- public static final String NODE_ADDON_SITE = "addon-site"; //$NON-NLS-1$
-
- /** The UI-visible name of the add-on site. */
- public static final String NODE_NAME = "name"; //$NON-NLS-1$
-
- /**
- * The URL of the site.
- * <p/>
- * This can be either the exact URL of the an XML resource conforming
- * to the latest sdk-addon-N.xsd schema, or it can be the URL of a
- * 'directory', in which case the manager will look for a resource
- * named 'addon.xml' at this location.
- * <p/>
- * Examples:
- * <pre>
- * http://www.example.com/android/my_addons.xml
- * or
- * http://www.example.com/android/
- * </pre>
- * In the second example, the manager will actually look for
- * http://www.example.com/android/addon.xml
- */
- public static final String NODE_URL = "url"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested sdk-addon XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- String filename = String.format("sdk-addons-list-%d.xsd", version); //$NON-NLS-1$
- return SdkAddonsListConstants.class.getResourceAsStream(filename);
- }
-
- /**
- * Returns the URI of the sdk-addon schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+import java.io.InputStream;
+
+/**
+ * Public constants for the sdk-addons-list XML Schema.
+ */
+public class SdkAddonsListConstants {
+
+ /** The base of our sdk-addons-list XML namespace. */
+ private static final String NS_BASE =
+ "http://schemas.android.com/sdk/android/addons-list/"; //$NON-NLS-1$
+
+ /**
+ * The pattern of our sdk-addons-list XML namespace.
+ * Matcher's group(1) is the schema version (integer).
+ */
+ public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+
+ /** The latest version of the sdk-addons-list XML Schema.
+ * Valid version numbers are between 1 and this number, included. */
+ public static final int NS_LATEST_VERSION = 2;
+
+ /** The XML namespace of the latest sdk-addons-list XML. */
+ public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
+
+
+ /** The canonical URL filename for addons-list XML files. */
+ public static final String URL_DEFAULT_FILENAME = getDefaultName(NS_LATEST_VERSION);
+
+ /** The URL where to find the official addons list fle. */
+ public static final String URL_ADDON_LIST =
+ SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
+
+
+
+ /** The root sdk-addons-list element */
+ public static final String NODE_SDK_ADDONS_LIST = "sdk-addons-list"; //$NON-NLS-1$
+
+ /** An add-on site. */
+ public static final String NODE_ADDON_SITE = "addon-site"; //$NON-NLS-1$
+
+ /** A system image site. */
+ public static final String NODE_SYS_IMG_SITE = "sys-img-site"; //$NON-NLS-1$
+
+ /** The UI-visible name of the add-on site. */
+ public static final String NODE_NAME = "name"; //$NON-NLS-1$
+
+ /**
+ * The URL of the site.
+ * <p/>
+ * This can be either the exact URL of the an XML resource conforming
+ * to the latest sdk-addon-N.xsd schema, or it can be the URL of a
+ * 'directory', in which case the manager will look for a resource
+ * named 'addon.xml' at this location.
+ * <p/>
+ * Examples:
+ * <pre>
+ * http://www.example.com/android/my_addons.xml
+ * or
+ * http://www.example.com/android/
+ * </pre>
+ * In the second example, the manager will actually look for
+ * http://www.example.com/android/addon.xml
+ */
+ public static final String NODE_URL = "url"; //$NON-NLS-1$
+
+ /**
+ * Returns a stream to the requested sdk-addon XML Schema.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ String filename = String.format("sdk-addons-list-%d.xsd", version); //$NON-NLS-1$
+ return SdkAddonsListConstants.class.getResourceAsStream(filename);
+ }
+
+ /**
+ * Returns the URI of the sdk-addon schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
+ }
+
+ public static String getDefaultName(int version) {
+ return String.format("addons_list-%1$d.xml", version); //$NON-NLS-1$
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
index 258ea26..48c9b25 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
@@ -1,136 +1,146 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-
-import com.android.sdklib.internal.repository.sources.SdkSource;
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-repository XML Schema.
- */
-public class SdkRepoConstants extends RepoConstants {
-
- /**
- * The latest version of the sdk-repository XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 6;
-
- /**
- * The min version of the sdk-repository XML Schema we'll try to load.
- * When looking for a repository-N.xml on the server, we'll check from
- * {@link #NS_LATEST_VERSION} down to this revision.
- * We only introduced the "repository-N.xml" pattern start with revision
- * 5, so we know that <em>our</em> server will never contain a repository
- * XML with a schema version lower than this one.
- */
- public static final int NS_SERVER_MIN_VERSION = 5;
-
- /**
- * The URL of the official Google sdk-repository site.
- * The URL ends with a /, allowing easy concatenation.
- * */
- public static final String URL_GOOGLE_SDK_SITE =
- "https://dl-ssl.google.com/android/repository/"; //$NON-NLS-1$
-
- /**
- * The default name looked for by {@link SdkSource} when trying to load an
- * sdk-repository XML if the URL doesn't match an existing resource.
- */
- public static final String URL_DEFAULT_FILENAME = "repository.xml"; //$NON-NLS-1$
-
- /**
- * The pattern name looked by {@link SdkSource} when trying to load
- * an sdk-repository XML that is specific to a given XSD revision.
- * <p/>
- * This must be used with {@link String#format(String, Object...)} with
- * one integer parameter between 1 and {@link #NS_LATEST_VERSION}.
- */
- public static final String URL_FILENAME_PATTERN = "repository-%1$d.xml"; //$NON-NLS-1$
-
- /** The base of our sdk-repository XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/repository/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-repository XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /** The XML namespace of the latest sdk-repository XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-repository element */
- public static final String NODE_SDK_REPOSITORY = "sdk-repository"; //$NON-NLS-1$
-
- /** A platform package. */
- public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
- /** A tool package. */
- public static final String NODE_TOOL = "tool"; //$NON-NLS-1$
- /** A platform-tool package. */
- public static final String NODE_PLATFORM_TOOL = "platform-tool"; //$NON-NLS-1$
- /** A doc package. */
- public static final String NODE_DOC = "doc"; //$NON-NLS-1$
- /** A sample package. */
- public static final String NODE_SAMPLE = "sample"; //$NON-NLS-1$
- /** A system-image package. */
- public static final String NODE_SYSTEM_IMAGE = "system-image"; //$NON-NLS-1$
- /** A source package. */
- public static final String NODE_SOURCE = "source"; //$NON-NLS-1$
-
- /* An included-ABI element for a system-image package. */
- public static final String NODE_ABI_INCLUDED = "included-abi"; //$NON-NLS-1$
- /* An ABI element for a system-image package. */
- public static final String NODE_ABI = "abi"; //$NON-NLS-1$
-
-
- /**
- * List of possible nodes in a repository XML. Used to populate options automatically
- * in the no-GUI mode.
- */
- public static final String[] NODES = {
- NODE_PLATFORM,
- NODE_SYSTEM_IMAGE,
- NODE_TOOL,
- NODE_PLATFORM_TOOL,
- NODE_DOC,
- NODE_SAMPLE,
- NODE_SOURCE,
- };
-
- /**
- * Returns a stream to the requested {@code sdk-repository} XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- return getXsdStream(NODE_SDK_REPOSITORY, version);
- }
-
- /**
- * Returns the URI of the SDK Repository schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+import com.android.sdklib.internal.repository.sources.SdkSource;
+
+import java.io.InputStream;
+
+/**
+ * Public constants for the sdk-repository XML Schema.
+ */
+public class SdkRepoConstants extends RepoConstants {
+
+ /**
+ * The latest version of the sdk-repository XML Schema.
+ * Valid version numbers are between 1 and this number, included.
+ */
+ public static final int NS_LATEST_VERSION = 7;
+
+ /**
+ * The min version of the sdk-repository XML Schema we'll try to load.
+ * When looking for a repository-N.xml on the server, we'll check from
+ * {@link #NS_LATEST_VERSION} down to this revision.
+ * We only introduced the "repository-N.xml" pattern start with revision
+ * 5, so we know that <em>our</em> server will never contain a repository
+ * XML with a schema version lower than this one.
+ */
+ public static final int NS_SERVER_MIN_VERSION = 5;
+
+ /**
+ * The URL of the official Google sdk-repository site.
+ * The URL ends with a /, allowing easy concatenation.
+ * */
+ public static final String URL_GOOGLE_SDK_SITE =
+ "https://dl-ssl.google.com/android/repository/"; //$NON-NLS-1$
+
+ /**
+ * The default name looked for by {@link SdkSource} when trying to load an
+ * sdk-repository XML if the URL doesn't match an existing resource.
+ */
+ public static final String URL_DEFAULT_FILENAME = "repository.xml"; //$NON-NLS-1$
+
+ /**
+ * The pattern name looked by {@link SdkSource} when trying to load
+ * an sdk-repository XML that is specific to a given XSD revision.
+ * <p/>
+ * This must be used with {@link String#format(String, Object...)} with
+ * one integer parameter between 1 and {@link #NS_LATEST_VERSION}.
+ */
+ public static final String URL_FILENAME_PATTERN = "repository-%1$d.xml"; //$NON-NLS-1$
+
+ /** The base of our sdk-repository XML namespace. */
+ private static final String NS_BASE =
+ "http://schemas.android.com/sdk/android/repository/"; //$NON-NLS-1$
+
+ /**
+ * The pattern of our sdk-repository XML namespace.
+ * Matcher's group(1) is the schema version (integer).
+ */
+ public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+
+ /** The XML namespace of the latest sdk-repository XML. */
+ public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
+
+ /** The root sdk-repository element */
+ public static final String NODE_SDK_REPOSITORY = "sdk-repository"; //$NON-NLS-1$
+
+ /* The major revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Mandatory int > 0. 0 when missing, which should not happen in
+ * a valid document. */
+ public static final String NODE_MAJOR_REV = "major"; //$NON-NLS-1$
+ /* The minor revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Optional int >= 0. Implied to be 0 when missing. */
+ public static final String NODE_MINOR_REV = "minor"; //$NON-NLS-1$
+ /* The micro revision for tool and platform-tool package
+ * (the full revision number is revision.minor.micro + preview#.)
+ * Optional int >= 0. Implied to be 0 when missing. */
+ public static final String NODE_MICRO_REV = "micro"; //$NON-NLS-1$
+ /* The preview revision for tool and platform-tool package.
+ * Int > 0, only present for "preview / release candidate" packages. */
+ public static final String NODE_PREVIEW = "preview"; //$NON-NLS-1$
+
+ /** A platform package. */
+ public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
+ /** A tool package. */
+ public static final String NODE_TOOL = "tool"; //$NON-NLS-1$
+ /** A platform-tool package. */
+ public static final String NODE_PLATFORM_TOOL = "platform-tool"; //$NON-NLS-1$
+ /** A doc package. */
+ public static final String NODE_DOC = "doc"; //$NON-NLS-1$
+ /** A sample package. */
+ public static final String NODE_SAMPLE = "sample"; //$NON-NLS-1$
+ /** A source package. */
+ public static final String NODE_SOURCE = "source"; //$NON-NLS-1$
+
+
+ /**
+ * List of possible nodes in a repository XML. Used to populate options automatically
+ * in the no-GUI mode.
+ */
+ public static final String[] NODES = {
+ NODE_PLATFORM,
+ NODE_SYSTEM_IMAGE,
+ NODE_TOOL,
+ NODE_PLATFORM_TOOL,
+ NODE_DOC,
+ NODE_SAMPLE,
+ NODE_SOURCE,
+ };
+
+ /**
+ * Returns a stream to the requested {@code sdk-repository} XML Schema.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ return getXsdStream(NODE_SDK_REPOSITORY, version);
+ }
+
+ /**
+ * Returns the URI of the SDK Repository schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkStatsConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkStatsConstants.java
index 06f8d39..22f8aa2 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkStatsConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkStatsConstants.java
@@ -1,91 +1,91 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.repository;
-
-
-import java.io.InputStream;
-
-/**
- * Public constants for the sdk-stats XML Schema.
- */
-public class SdkStatsConstants {
-
- /** The canonical URL filename for addons-list XML files. */
- public static final String URL_DEFAULT_FILENAME = "stats-1.xml"; //$NON-NLS-1$
-
- /** The URL where to find the official addons list fle. */
- public static final String URL_STATS =
- SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
-
- /** The base of our sdk-addons-list XML namespace. */
- private static final String NS_BASE =
- "http://schemas.android.com/sdk/android/stats/"; //$NON-NLS-1$
-
- /**
- * The pattern of our sdk-stats XML namespace.
- * Matcher's group(1) is the schema version (integer).
- */
- public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
-
- /** The latest version of the sdk-stats XML Schema.
- * Valid version numbers are between 1 and this number, included. */
- public static final int NS_LATEST_VERSION = 1;
-
- /** The XML namespace of the latest sdk-stats XML. */
- public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
-
- /** The root sdk-stats element */
- public static final String NODE_SDK_STATS = "sdk-stats"; //$NON-NLS-1$
-
- /** A platform stat. */
- public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
-
- /** The Android API Level for the platform. An int > 0. */
- public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
-
- /** The official codename for this platform, for example "Cupcake". */
- public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
-
- /** The official version name of this platform, for example "Android 1.5". */
- public static final String NODE_VERSION = "version"; //$NON-NLS-1$
-
- /**
- * The <em>approximate</em> share percentage of that platform.
- * See the caveat in sdk-stats-1.xsd about value freshness and accuracy.
- */
- public static final String NODE_SHARE = "share"; //$NON-NLS-1$
-
- /**
- * Returns a stream to the requested sdk-stats XML Schema.
- *
- * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
- * @return An {@link InputStream} object for the local XSD file or
- * null if there is no schema for the requested version.
- */
- public static InputStream getXsdStream(int version) {
- String filename = String.format("sdk-stats-%d.xsd", version); //$NON-NLS-1$
- return SdkStatsConstants.class.getResourceAsStream(filename);
- }
-
- /**
- * Returns the URI of the sdk-stats schema for the given version number.
- * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
- */
- public static String getSchemaUri(int version) {
- return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
- }
-}
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+import java.io.InputStream;
+
+/**
+ * Public constants for the sdk-stats XML Schema.
+ */
+public class SdkStatsConstants {
+
+ /** The canonical URL filename for addons-list XML files. */
+ public static final String URL_DEFAULT_FILENAME = "stats-1.xml"; //$NON-NLS-1$
+
+ /** The URL where to find the official addons list fle. */
+ public static final String URL_STATS =
+ SdkRepoConstants.URL_GOOGLE_SDK_SITE + URL_DEFAULT_FILENAME;
+
+ /** The base of our sdk-addons-list XML namespace. */
+ private static final String NS_BASE =
+ "http://schemas.android.com/sdk/android/stats/"; //$NON-NLS-1$
+
+ /**
+ * The pattern of our sdk-stats XML namespace.
+ * Matcher's group(1) is the schema version (integer).
+ */
+ public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+
+ /** The latest version of the sdk-stats XML Schema.
+ * Valid version numbers are between 1 and this number, included. */
+ public static final int NS_LATEST_VERSION = 1;
+
+ /** The XML namespace of the latest sdk-stats XML. */
+ public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
+
+ /** The root sdk-stats element */
+ public static final String NODE_SDK_STATS = "sdk-stats"; //$NON-NLS-1$
+
+ /** A platform stat. */
+ public static final String NODE_PLATFORM = "platform"; //$NON-NLS-1$
+
+ /** The Android API Level for the platform. An int > 0. */
+ public static final String NODE_API_LEVEL = "api-level"; //$NON-NLS-1$
+
+ /** The official codename for this platform, for example "Cupcake". */
+ public static final String NODE_CODENAME = "codename"; //$NON-NLS-1$
+
+ /** The official version name of this platform, for example "Android 1.5". */
+ public static final String NODE_VERSION = "version"; //$NON-NLS-1$
+
+ /**
+ * The <em>approximate</em> share percentage of that platform.
+ * See the caveat in sdk-stats-1.xsd about value freshness and accuracy.
+ */
+ public static final String NODE_SHARE = "share"; //$NON-NLS-1$
+
+ /**
+ * Returns a stream to the requested sdk-stats XML Schema.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ String filename = String.format("sdk-stats-%d.xsd", version); //$NON-NLS-1$
+ return SdkStatsConstants.class.getResourceAsStream(filename);
+ }
+
+ /**
+ * Returns the URI of the sdk-stats schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkSysImgConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkSysImgConstants.java
new file mode 100755
index 0000000..ba3017f
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkSysImgConstants.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.repository;
+
+
+import com.android.sdklib.internal.repository.sources.SdkSource;
+
+import java.io.InputStream;
+
+/**
+ * Public constants for the sdk-sys-img XML Schema.
+ */
+public class SdkSysImgConstants extends RepoConstants {
+
+ /**
+ * The default name looked for by {@link SdkSource} when trying to load an
+ * sdk-sys-img XML if the URL doesn't match an existing resource.
+ */
+ public static final String URL_DEFAULT_FILENAME = "sys-img.xml"; //$NON-NLS-1$
+
+ /** The base of our sdk-sys-img XML namespace. */
+ private static final String NS_BASE =
+ "http://schemas.android.com/sdk/android/sys-img/"; //$NON-NLS-1$
+
+ /**
+ * The pattern of our sdk-sys-img XML namespace.
+ * Matcher's group(1) is the schema version (integer).
+ */
+ public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
+
+ /**
+ * The latest version of the sdk-sys-img XML Schema.
+ * Valid version numbers are between 1 and this number, included.
+ */
+ public static final int NS_LATEST_VERSION = 1;
+
+ /** The XML namespace of the latest sdk-sys-img XML. */
+ public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
+
+ /** The root sdk-sys-img element */
+ public static final String NODE_SDK_SYS_IMG = "sdk-sys-img"; //$NON-NLS-1$
+
+ /**
+ * List of possible nodes in a repository XML. Used to populate options automatically
+ * in the no-GUI mode.
+ */
+ public static final String[] NODES = {
+ NODE_SYSTEM_IMAGE,
+ };
+
+ /**
+ * Returns a stream to the requested {@code sdk-sys-img} XML Schema.
+ *
+ * @param version Between 1 and {@link #NS_LATEST_VERSION}, included.
+ * @return An {@link InputStream} object for the local XSD file or
+ * null if there is no schema for the requested version.
+ */
+ public static InputStream getXsdStream(int version) {
+ return getXsdStream(NODE_SDK_SYS_IMG, version);
+ }
+
+ /**
+ * Returns the URI of the sdk-sys-img schema for the given version number.
+ * @param version Between 1 and {@link #NS_LATEST_VERSION} included.
+ */
+ public static String getSchemaUri(int version) {
+ return String.format(NS_BASE + "%d", version); //$NON-NLS-1$
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
index 564aaeb..c31efbf 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
@@ -137,7 +137,8 @@
<!-- An optional element indicating the package is a beta/preview.
When present, it indicates the release-candidate number.
- When the element is absent, it indicates this is a released package. -->
+ When the element is absent, it indicates this is a released package.
+ DEPRECATED. TODO remove in sdk-addon-5. -->
<xsd:element name="beta-rc" type="xsd:positiveInteger" minOccurs="0" />
<!-- Optional information on the layoutlib packaged in this platform. -->
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd
new file mode 100755
index 0000000..546b00d
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd
@@ -0,0 +1,442 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2011 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addon/5"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/5"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android Addon repository is a web site that contains an "addon.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r8. It is split out of the
+ main SDK Repository XML Schema and can only contain <addon> and
+ <extra> packages.
+
+ - v2 is used by the SDK Updater in Tools r12.
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <addon> element now has an optional <layoutlib> that indicates the API
+ and revision of the layout library for this particular add-on, if any.
+
+ - v3 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+
+ - v4 is used by the SDK Manager in Tools r18:
+ - <extra> and <addon> are not in the Repository XSD v6 anymore.
+ - <extra> get a new field <name-display>, which is used by the SDK Manager to
+ customize the name of the extra in the list display. The single <vendor>
+ field becomes <vendor-id> and <vendor-display>, the id being used internally
+ and the display in the UI.
+ - <addon> does the same, where <name> is replaced by <name-id> and <name-display>
+ and <vendor> is replaced by <vendor-id> and <vendor-display>.
+
+ - v5 is used by the SDK Manager in Tools r20:
+ - The <beta-rc> element is no longer supported. It was never implemented anyway.
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - <min-tools-rev> for <extra> becomes a full revision element.
+ -->
+
+ <xsd:element name="sdk-addon" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="add-on" type="sdk:addonType" />
+ <xsd:element name="extra" type="sdk:extraType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK Add-on package. -->
+
+ <xsd:complexType name="addonType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The internal name id of the add-on. Must be unique per vendor. -->
+ <xsd:element name="name-id" type="sdk:idType" />
+ <!-- The displayed name of the add-on. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the add-on. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the add-on. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The Android API Level for the add-on. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- Note: Add-ons do not support 'codenames' (a.k.a. API previews). -->
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- An add-on can declare 0 or more libraries.
+ This element is mandatory but it can be empty.
+ -->
+
+ <xsd:element name="libs">
+ <xsd:complexType>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="lib">
+ <xsd:complexType>
+ <xsd:all>
+ <!-- The name of the library. -->
+ <xsd:element name="name" type="xsd:normalizedString" />
+ <!-- The optional description of this add-on library. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- Optional information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ An ID string for an addon/extra name-id or vendor-id
+ can only be simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a layout library used by an addon. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in an addon.
+ .</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK extra package. This kind of package is for
+ "free" content. Such packages are installed in SDK/extras/vendor/path.
+ -->
+
+ <xsd:complexType name="extraType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ An SDK extra package. This kind of package is for "free" content.
+ Such packages are installed in SDK/vendor/path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The displayed name of the extra. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the extra. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the extra. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The install path sub-folder name. It must not be empty. -->
+ <xsd:element name="path" type="sdk:segmentType" />
+
+ <!-- A semi-colon separated list of "obsolete" path names which are equivalent
+ to the current 'path' name. When a package is seen using an old-paths' name,
+ the package manager will try to upgrade it to the new path. -->
+ <xsd:element name="old-paths" type="sdk:segmentListType" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+ <!-- The minimal API level required by this package.
+ Optional. If present, must be an int > 0. -->
+ <xsd:element name="min-api-level" type="xsd:positiveInteger" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- A list of project files contributed by this package. Optional. -->
+ <xsd:element name="project-files" type="sdk:projectFilesType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path. It must not be empty.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The &lt;archives&gt; node is mandatory in the repository packages and the
+ collection must have at least one &lt;archive&gt; declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an &lt;extra&gt; package
+ that can be installed in an Android project.
+ If present, the &lt;project-files&gt; collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an &lt;extra&gt; package
+ that can be installed in an Android project.
+ If present, the &lt;project-files&gt; collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-1.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-1.xsd
index ac2113c..176fb60 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-1.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-1.xsd
@@ -23,7 +23,7 @@
version="1">
<!--
- A simple list of add-ons site that is loaded by default by the SDK Manager.
+ A simple list of add-ons sites that is loaded by default by the SDK Manager.
-->
<xsd:element name="sdk-addons-list" type="sa1:addonsListType" />
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-2.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-2.xsd
new file mode 100755
index 0000000..dde7214
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addons-list-2.xsd
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addons-list/2"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sa1="http://schemas.android.com/sdk/android/addons-list/2"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!--
+ A simple list of add-ons sites that is loaded by default by the SDK Manager.
+
+ - v1: Defines <addon-site>
+ - v2: Adds support for <sys-img-site>
+ -->
+
+ <xsd:element name="sdk-addons-list" type="sa1:addonsListType" />
+
+ <xsd:complexType name="addonsListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A simple list of add-ons site.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="addon-site" type="sa1:addonSiteType" />
+ <xsd:element name="sys-img-site" type="sa1:sysImgSiteType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an Add-on Site. -->
+
+ <xsd:complexType name="addonSiteType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on site.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site.
+
+ This can be either the exact URL of the an XML resource conforming
+ to the latest sdk-addon-N.xsd schema, or it can be the URL of a
+ 'directory', in which case the manager will look for a resource
+ named 'addon.xml' at this location.
+
+ Examples:
+ http://www.example.com/android/my_addons.xml
+ or
+ http://www.example.com/android/
+
+ In the second example, the manager will actually look for:
+ http://www.example.com/android/addon.xml
+ -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The UI-visible name of the add-on site. -->
+ <xsd:element name="name" type="xsd:normalizedString" />
+
+ </xsd:all>
+ </xsd:complexType>
+
+ <!-- The definition of an Sys-Img Site. -->
+
+ <xsd:complexType name="sysImgSiteType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK sys-img site.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The URL of the site.
+
+ This can be either the exact URL of the an XML resource conforming
+ to the latest sdk-sys-img-N.xsd schema, or it can be the URL of a
+ 'directory', in which case the manager will look for a resource
+ named 'sysimg.xml' at this location.
+
+ Examples:
+ http://www.example.com/android/my_sys_img.xml
+ or
+ http://www.example.com/android/
+
+ In the second example, the manager will actually look for:
+ http://www.example.com/android/sysimg.xml
+ -->
+ <xsd:element name="url" type="xsd:token" />
+
+ <!-- The UI-visible name of the sys-img site. -->
+ <xsd:element name="name" type="xsd:normalizedString" />
+
+ </xsd:all>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
index 87e183a..bccce69 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 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.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd
new file mode 100755
index 0000000..ea18070
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd
@@ -0,0 +1,612 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/repository/7"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/7"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android SDK repository is a web site that contains a "repository.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r3 and r4.
+
+ - v2 is used by the SDK Updater in Tools r5:
+ - It introduces a new <sample> repository type. Previously samples
+ were included in the <platform> packages. Instead this package is used
+ and and the samples are installed in $SDK/samples.
+ - All repository types have a new <obsolete> node. It works as a marker
+ to indicate the package is obsolete and should not be selected by default.
+ The UI also hides these out by default.
+
+ - v3 is used by the SDK Updater in Tools r8:
+ - It introduces a new <platform-tool> repository type. Previously platform-specific
+ tools were included in the <platform> packages. Instead this package is used
+ and platform-specific tools are installed in $SDK/platform-tools
+ - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+ requires that at least some minimal version of <platform-tool> be installed.
+ - It removes the <addon> repository type, which is now in its own XML Schema.
+
+ - v4 is used by the SDK Updater in Tools r12:
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <platform> element now has a mandatory <layoutlib> that indicates the API
+ and revision of that layout library for this particular platform.
+
+ - v5 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+ - <platform> has a new optional <abi-included> that describes the ABI of the
+ system image included in the platform, if any.
+ - New <system-image> package type, to store system images outside of <platform>s.
+ - New <source> package type.
+
+ - v6 is used by the SDK Manager in Tools r18:
+ - <extra> packages are removed. They are served only by the addon XML.
+ - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+ and <sample> get a new optional field <beta-rc> which can be used to indicate
+ the package is a Beta Release Candidate and not a final release.
+
+ - v7 is used by the SDK Manager in Tools r20:
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - The <beta-rc> element is no longer supported, it is replaced by
+ <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+ - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+ -->
+
+ <xsd:element name="sdk-repository" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="platform" type="sdk:platformType" />
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="source" type="sdk:sourceType" />
+ <xsd:element name="tool" type="sdk:toolType" />
+ <xsd:element name="platform-tool" type="sdk:platformToolType" />
+ <xsd:element name="doc" type="sdk:docType" />
+ <xsd:element name="sample" type="sdk:sampleType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+ <!-- The definition of an SDK platform package. -->
+
+ <xsd:complexType name="platformType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android platform version. It is string such as "1.0". -->
+ <xsd:element name="version" type="xsd:normalizedString" />
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- Information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+ <!-- The ABI of the system image *included* in this platform, if any.
+ When the field is present, it means the platform already embeds one
+ system image. A platform can also have any number of external
+ &lt;system-image&gt; associated with it. -->
+ <xsd:element name="included-abi" type="sdk:abiType" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a layout library used by a platform. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a source package. -->
+
+ <xsd:complexType name="sourceType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Sources for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK tool package. -->
+
+ <xsd:complexType name="toolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- The minimal revision of platform-tools required by this package.
+ Mandatory. Must be a revision element. -->
+ <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK platform-tool package. -->
+
+ <xsd:complexType name="platformToolType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The full revision (major.minor.micro.preview), incremented each
+ time a new package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK doc package. -->
+
+ <xsd:complexType name="docType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK doc package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK sample package. -->
+
+ <xsd:complexType name="sampleType" >
+ <xsd:annotation>
+ <xsd:documentation>An SDK sample package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The Android API Level for the documentation. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this doc, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The &lt;archives&gt; node is mandatory in the repository packages and the
+ collection must have at least one &lt;archive&gt; declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an &lt;extra&gt; package
+ that can be installed in an Android project.
+ If present, the &lt;project-files&gt; collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an &lt;extra&gt; package
+ that can be installed in an Android project.
+ If present, the &lt;project-files&gt; collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-sys-img-1.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-sys-img-1.xsd
new file mode 100755
index 0000000..a19aa49
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-sys-img-1.xsd
@@ -0,0 +1,229 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2012 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.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/sys-img/1"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/sys-img/1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android Addon repository is a web site that contains an "addon.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r20. It is split out of the
+ main SDK Repository XML Schema and can only contain <system-image> packages.
+ -->
+
+ <xsd:element name="sdk-sys-img" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable system images.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="system-image" type="sdk:systemImageType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of a system image used by a platform. -->
+
+ <xsd:complexType name="systemImageType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ System Image for a platform.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- api-level+codename identifies the platform to which this system image belongs. -->
+
+ <!-- The Android API Level for the platform. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- The optional codename for this platform, if it's a preview. -->
+ <xsd:element name="codename" type="xsd:string" minOccurs="0" />
+
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- The ABI of the system emulated by this image. -->
+ <xsd:element name="abi" type="sdk:abiType" />
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of the ABI supported by a platform's system image. -->
+
+ <xsd:simpleType name="abiType">
+ <xsd:annotation>
+ <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="armeabi" />
+ <xsd:enumeration value="armeabi-v7a" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="mips" />
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The &lt;archives&gt; node is mandatory in the repository packages and the
+ collection must have at least one &lt;archive&gt; declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java
index 3b4c682..3a86ea7 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java
@@ -16,7 +16,9 @@
package com.android.sdklib.util;
-import com.android.sdklib.ISdkLog;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.ILogger;
import java.util.ArrayList;
import java.util.HashMap;
@@ -95,7 +97,7 @@ public class CommandLineParser {
*/
private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
/** Logger */
- private final ISdkLog mLog;
+ private final ILogger mLog;
/**
* Constructs a new command-line processor.
@@ -106,7 +108,7 @@ public class CommandLineParser {
*
* @see #mActions
*/
- public CommandLineParser(ISdkLog logger, String[][] actions) {
+ public CommandLineParser(ILogger logger, String[][] actions) {
mLog = logger;
mActions = actions;
@@ -801,20 +803,22 @@ public class CommandLineParser {
*
* @param mode The {@link Mode} for the argument.
* @param mandatory True if this argument is mandatory for this action.
- * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
+ * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
* @param shortName The one-letter short argument name. Can be empty but not null.
* @param longName The long argument name. Can be empty but not null.
* @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
+ * @param defaultValue The default value (or values), which depends on the selected
+ * {@link Mode}. Can be null.
*/
public Arg(Mode mode,
boolean mandatory,
- String verb,
- String directObject,
- String shortName,
- String longName,
- String description,
- Object defaultValue) {
+ @NonNull String verb,
+ @NonNull String directObject,
+ @NonNull String shortName,
+ @NonNull String longName,
+ @NonNull String description,
+ @Nullable Object defaultValue) {
mMode = mode;
mMandatory = mandatory;
mVerb = verb;
@@ -897,19 +901,23 @@ public class CommandLineParser {
*
* @param mode The {@link Mode} for the argument.
* @param mandatory The argument is required (never if {@link Mode#BOOLEAN})
- * @param verb The verb name. Can be #INTERNAL_VERB.
- * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}.
+ * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}.
* @param shortName The one-letter short argument name. Can be empty but not null.
* @param longName The long argument name. Can be empty but not null.
* @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
+ * @param defaultValue The default value (or values), which depends on the selected
+ * {@link Mode}.
*/
protected void define(Mode mode,
boolean mandatory,
- String verb,
- String directObject,
- String shortName, String longName,
- String description, Object defaultValue) {
+ @NonNull String verb,
+ @NonNull String directObject,
+ @NonNull String shortName,
+ @NonNull String longName,
+ @NonNull String description,
+ @Nullable Object defaultValue) {
+ assert verb != null;
assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
// We should always have at least a short or long name, ideally both but never none.
@@ -944,7 +952,7 @@ public class CommandLineParser {
protected void stdout(String format, Object...args) {
String output = String.format(format, args);
output = LineUtil.reflowLine(output);
- mLog.printf("%s\n", output); //$NON-NLS-1$
+ mLog.info("%s\n", output); //$NON-NLS-1$
}
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/FormatUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/FormatUtils.java
new file mode 100755
index 0000000..fc9258c
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/FormatUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 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.util;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Helper methods to do some format conversions.
+ */
+public abstract class FormatUtils {
+
+ /**
+ * Converts a byte size to a human readable string,
+ * for example "3 MiB", "1020 Bytes" or "1.2 GiB".
+ *
+ * @param size The byte size to convert.
+ * @return A new non-null string, with the size expressed in either Bytes
+ * or KiB or MiB or GiB.
+ */
+ public static @NonNull String byteSizeToString(long size) {
+ String sizeStr;
+
+ if (size < 1024) {
+ sizeStr = String.format("%d Bytes", size);
+ } else if (size < 1024 * 1024) {
+ sizeStr = String.format("%d KiB", Math.round(size / 1024.0));
+ } else if (size < 1024 * 1024 * 1024) {
+ sizeStr = String.format("%.1f MiB",
+ Math.round(10.0 * size / (1024 * 1024.0))/ 10.0);
+ } else {
+ sizeStr = String.format("%.1f GiB",
+ Math.round(10.0 * size / (1024 * 1024 * 1024.0))/ 10.0);
+ }
+
+ return sizeStr;
+ }
+
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/SparseArray.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/SparseArray.java
index 7c535a8..f0693fe 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/SparseArray.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/SparseArray.java
@@ -343,7 +343,6 @@ public class SparseArray<E> {
}
@Override
- @SuppressWarnings("unchecked")
public E valueAt(int index) {
return mStorage.valueAt(index);
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java
deleted file mode 100644
index 93cd211..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * 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.xml;
-
-import com.android.io.IAbstractFile;
-import com.android.io.IAbstractFolder;
-import com.android.io.StreamException;
-import com.android.sdklib.SdkConstants;
-
-import org.w3c.dom.Node;
-import org.xml.sax.InputSource;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Helper and Constants for the AndroidManifest.xml file.
- *
- */
-public final class AndroidManifest {
-
- public final static String NODE_MANIFEST = "manifest";
- public final static String NODE_APPLICATION = "application";
- public final static String NODE_ACTIVITY = "activity";
- public final static String NODE_ACTIVITY_ALIAS = "activity-alias";
- public final static String NODE_SERVICE = "service";
- public final static String NODE_RECEIVER = "receiver";
- public final static String NODE_PROVIDER = "provider";
- public final static String NODE_INTENT = "intent-filter";
- public final static String NODE_ACTION = "action";
- public final static String NODE_CATEGORY = "category";
- public final static String NODE_USES_SDK = "uses-sdk";
- public final static String NODE_INSTRUMENTATION = "instrumentation";
- public final static String NODE_USES_LIBRARY = "uses-library";
- public final static String NODE_SUPPORTS_SCREENS = "supports-screens";
- public final static String NODE_USES_CONFIGURATION = "uses-configuration";
- public final static String NODE_USES_FEATURE = "uses-feature";
-
- public final static String ATTRIBUTE_PACKAGE = "package";
- public final static String ATTRIBUTE_VERSIONCODE = "versionCode";
- public final static String ATTRIBUTE_NAME = "name";
- public final static String ATTRIBUTE_REQUIRED = "required";
- public final static String ATTRIBUTE_GLESVERSION = "glEsVersion";
- public final static String ATTRIBUTE_PROCESS = "process";
- public final static String ATTRIBUTE_DEBUGGABLE = "debuggable";
- public final static String ATTRIBUTE_LABEL = "label";
- public final static String ATTRIBUTE_ICON = "icon";
- public final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion";
- public final static String ATTRIBUTE_TARGET_SDK_VERSION = "targetSdkVersion";
- public final static String ATTRIBUTE_TARGET_PACKAGE = "targetPackage";
- public final static String ATTRIBUTE_TARGET_ACTIVITY = "targetActivity";
- public final static String ATTRIBUTE_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
- public final static String ATTRIBUTE_EXPORTED = "exported";
- public final static String ATTRIBUTE_RESIZEABLE = "resizeable";
- public final static String ATTRIBUTE_ANYDENSITY = "anyDensity";
- public final static String ATTRIBUTE_SMALLSCREENS = "smallScreens";
- public final static String ATTRIBUTE_NORMALSCREENS = "normalScreens";
- public final static String ATTRIBUTE_LARGESCREENS = "largeScreens";
- public final static String ATTRIBUTE_REQ_5WAYNAV = "reqFiveWayNav";
- public final static String ATTRIBUTE_REQ_NAVIGATION = "reqNavigation";
- public final static String ATTRIBUTE_REQ_HARDKEYBOARD = "reqHardKeyboard";
- public final static String ATTRIBUTE_REQ_KEYBOARDTYPE = "reqKeyboardType";
- public final static String ATTRIBUTE_REQ_TOUCHSCREEN = "reqTouchScreen";
- public static final String ATTRIBUTE_THEME = "theme";
-
- /**
- * Returns an {@link IAbstractFile} object representing the manifest for the given project.
- *
- * @param projectFolder The project containing the manifest file.
- * @return An IAbstractFile object pointing to the manifest or null if the manifest
- * is missing.
- */
- public static IAbstractFile getManifest(IAbstractFolder projectFolder) {
- IAbstractFile file = projectFolder.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
- if (file.exists()) {
- return file;
- }
-
- return null;
- }
-
- /**
- * Returns the package for a given project.
- * @param projectFolder the folder of the project.
- * @return the package info or null (or empty) if not found.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static String getPackage(IAbstractFolder projectFolder)
- throws XPathExpressionException, StreamException {
- IAbstractFile file = getManifest(projectFolder);
- if (file != null) {
- return getPackage(file);
- }
-
- return null;
- }
-
- /**
- * Returns the package for a given manifest.
- * @param manifestFile the manifest to parse.
- * @return the package info or null (or empty) if not found.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static String getPackage(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/@" + ATTRIBUTE_PACKAGE,
- new InputSource(manifestFile.getContents()));
- }
-
- /**
- * Returns whether the manifest is set to make the application debuggable.
- *
- * If the give manifest does not contain the debuggable attribute then the application
- * is considered to not be debuggable.
- *
- * @param manifestFile the manifest to parse.
- * @return true if the application is debuggable.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static boolean getDebuggable(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String value = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_DEBUGGABLE,
- new InputSource(manifestFile.getContents()));
-
- // default is not debuggable, which is the same behavior as parseBoolean
- return Boolean.parseBoolean(value);
- }
-
- /**
- * Returns the value of the versionCode attribute or -1 if the value is not set.
- * @param manifestFile the manifest file to read the attribute from.
- * @return the integer value or -1 if not set.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static int getVersionCode(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_VERSIONCODE,
- new InputSource(manifestFile.getContents()));
-
- try {
- return Integer.parseInt(result);
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-
- /**
- * Returns whether the version Code attribute is set in a given manifest.
- * @param manifestFile the manifest to check
- * @return true if the versionCode attribute is present and its value is not empty.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static boolean hasVersionCode(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- Object result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_VERSIONCODE,
- new InputSource(manifestFile.getContents()),
- XPathConstants.NODE);
-
- if (result != null) {
- Node node = (Node)result;
- if (node.getNodeValue().length() > 0) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns the value of the minSdkVersion attribute.
- * <p/>
- * If the attribute is set with an int value, the method returns an Integer object.
- * <p/>
- * If the attribute is set with a codename, it returns the codename as a String object.
- * <p/>
- * If the attribute is not set, it returns null.
- *
- * @param manifestFile the manifest file to read the attribute from.
- * @return the attribute value.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static Object getMinSdkVersion(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_USES_SDK +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_MIN_SDK_VERSION,
- new InputSource(manifestFile.getContents()));
-
- try {
- return Integer.valueOf(result);
- } catch (NumberFormatException e) {
- return result.length() > 0 ? result : null;
- }
- }
-
- /**
- * Returns the value of the targetSdkVersion attribute (defaults to 1 if the attribute is
- * not set), or -1 if the value is a codename.
- * @param manifestFile the manifest file to read the attribute from.
- * @return the integer value or -1 if not set.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static Integer getTargetSdkVersion(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- String result = xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_USES_SDK +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_TARGET_SDK_VERSION,
- new InputSource(manifestFile.getContents()));
-
- try {
- return Integer.valueOf(result);
- } catch (NumberFormatException e) {
- return result.length() > 0 ? -1 : null;
- }
- }
-
- /**
- * Returns the application icon for a given manifest.
- * @param manifestFile the manifest to parse.
- * @return the icon or null (or empty) if not found.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static String getApplicationIcon(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_ICON,
- new InputSource(manifestFile.getContents()));
- }
-
- /**
- * Returns the application label for a given manifest.
- * @param manifestFile the manifest to parse.
- * @return the label or null (or empty) if not found.
- * @throws XPathExpressionException
- * @throws StreamException If any error happens when reading the manifest.
- */
- public static String getApplicationLabel(IAbstractFile manifestFile)
- throws XPathExpressionException, StreamException {
- XPath xPath = AndroidXPathFactory.newXPath();
-
- return xPath.evaluate(
- "/" + NODE_MANIFEST +
- "/" + NODE_APPLICATION +
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX +
- ":" + ATTRIBUTE_LABEL,
- new InputSource(manifestFile.getContents()));
- }
-
- /**
- * Combines a java package, with a class value from the manifest to make a fully qualified
- * class name
- * @param javaPackage the java package from the manifest.
- * @param className the class name from the manifest.
- * @return the fully qualified class name.
- */
- public static String combinePackageAndClassName(String javaPackage, String className) {
- if (className == null || className.length() == 0) {
- return javaPackage;
- }
- if (javaPackage == null || javaPackage.length() == 0) {
- return className;
- }
-
- // the class name can be a subpackage (starts with a '.'
- // char), a simple class name (no dot), or a full java package
- boolean startWithDot = (className.charAt(0) == '.');
- boolean hasDot = (className.indexOf('.') != -1);
- if (startWithDot || hasDot == false) {
-
- // add the concatenation of the package and class name
- if (startWithDot) {
- return javaPackage + className;
- } else {
- return javaPackage + '.' + className;
- }
- } else {
- // just add the class as it should be a fully qualified java name.
- return className;
- }
- }
-
- /**
- * Given a fully qualified activity name (e.g. com.foo.test.MyClass) and given a project
- * package base name (e.g. com.foo), returns the relative activity name that would be used
- * the "name" attribute of an "activity" element.
- *
- * @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass"
- * @param packageName The project base package name, e.g. "com.foo"
- * @return The relative activity name if it can be computed or the original fullActivityName.
- */
- public static String extractActivityName(String fullActivityName, String packageName) {
- if (packageName != null && fullActivityName != null) {
- if (packageName.length() > 0 && fullActivityName.startsWith(packageName)) {
- String name = fullActivityName.substring(packageName.length());
- if (name.length() > 0 && name.charAt(0) == '.') {
- return name;
- }
- }
- }
-
- return fullActivityName;
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java
deleted file mode 100644
index 1c10828..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.xml;
-
-import com.android.io.IAbstractFile;
-import com.android.io.IAbstractFolder;
-import com.android.io.StreamException;
-import com.android.resources.Keyboard;
-import com.android.resources.Navigation;
-import com.android.resources.TouchScreen;
-import com.android.sdklib.SdkConstants;
-import com.android.sdklib.xml.ManifestData.Activity;
-import com.android.sdklib.xml.ManifestData.Instrumentation;
-import com.android.sdklib.xml.ManifestData.SupportsScreens;
-import com.android.sdklib.xml.ManifestData.UsesConfiguration;
-import com.android.sdklib.xml.ManifestData.UsesFeature;
-import com.android.sdklib.xml.ManifestData.UsesLibrary;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.Locator;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Locale;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-public class AndroidManifestParser {
-
- private final static int LEVEL_TOP = 0;
- private final static int LEVEL_INSIDE_MANIFEST = 1;
- private final static int LEVEL_INSIDE_APPLICATION = 2;
- private final static int LEVEL_INSIDE_APP_COMPONENT = 3;
- private final static int LEVEL_INSIDE_INTENT_FILTER = 4;
-
- private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
- private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
-
- public interface ManifestErrorHandler extends ErrorHandler {
- /**
- * Handles a parsing error and an optional line number.
- */
- void handleError(Exception exception, int lineNumber);
-
- /**
- * Checks that a class is valid and can be used in the Android Manifest.
- * <p/>
- * Errors are put as {@code org.eclipse.core.resources.IMarker} on the manifest file.
- *
- * @param locator
- * @param className the fully qualified name of the class to test.
- * @param superClassName the fully qualified name of the class it is supposed to extend.
- * @param testVisibility if <code>true</code>, the method will check the visibility of
- * the class or of its constructors.
- */
- void checkClass(Locator locator, String className, String superClassName,
- boolean testVisibility);
- }
-
- /**
- * XML error & data handler used when parsing the AndroidManifest.xml file.
- * <p/>
- * During parsing this will fill up the {@link ManifestData} object given to the constructor
- * and call out errors to the given {@link ManifestErrorHandler}.
- */
- private static class ManifestHandler extends DefaultHandler {
-
- //--- temporary data/flags used during parsing
- private final ManifestData mManifestData;
- private final ManifestErrorHandler mErrorHandler;
- private int mCurrentLevel = 0;
- private int mValidLevel = 0;
- private Activity mCurrentActivity = null;
- private Locator mLocator;
-
- /**
- * Creates a new {@link ManifestHandler}.
- *
- * @param manifestFile The manifest file being parsed. Can be null.
- * @param manifestData Class containing the manifest info obtained during the parsing.
- * @param errorHandler An optional error handler.
- */
- ManifestHandler(IAbstractFile manifestFile, ManifestData manifestData,
- ManifestErrorHandler errorHandler) {
- super();
- mManifestData = manifestData;
- mErrorHandler = errorHandler;
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
- */
- @Override
- public void setDocumentLocator(Locator locator) {
- mLocator = locator;
- super.setDocumentLocator(locator);
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
- * java.lang.String, org.xml.sax.Attributes)
- */
- @Override
- public void startElement(String uri, String localName, String name, Attributes attributes)
- throws SAXException {
- try {
- if (mManifestData == null) {
- return;
- }
-
- // if we're at a valid level
- if (mValidLevel == mCurrentLevel) {
- String value;
- switch (mValidLevel) {
- case LEVEL_TOP:
- if (AndroidManifest.NODE_MANIFEST.equals(localName)) {
- // lets get the package name.
- mManifestData.mPackage = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_PACKAGE,
- false /* hasNamespace */);
-
- // and the versionCode
- String tmp = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_VERSIONCODE, true);
- if (tmp != null) {
- try {
- mManifestData.mVersionCode = Integer.valueOf(tmp);
- } catch (NumberFormatException e) {
- // keep null in the field.
- }
- }
- mValidLevel++;
- }
- break;
- case LEVEL_INSIDE_MANIFEST:
- if (AndroidManifest.NODE_APPLICATION.equals(localName)) {
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_PROCESS,
- true /* hasNamespace */);
- if (value != null) {
- mManifestData.addProcessName(value);
- }
-
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_DEBUGGABLE,
- true /* hasNamespace*/);
- if (value != null) {
- mManifestData.mDebuggable = Boolean.parseBoolean(value);
- }
-
- mValidLevel++;
- } else if (AndroidManifest.NODE_USES_SDK.equals(localName)) {
- mManifestData.setMinSdkVersionString(getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
- true /* hasNamespace */));
- mManifestData.setTargetSdkVersionString(getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION,
- true /* hasNamespace */));
- } else if (AndroidManifest.NODE_INSTRUMENTATION.equals(localName)) {
- processInstrumentationNode(attributes);
-
- } else if (AndroidManifest.NODE_SUPPORTS_SCREENS.equals(localName)) {
- processSupportsScreensNode(attributes);
-
- } else if (AndroidManifest.NODE_USES_CONFIGURATION.equals(localName)) {
- processUsesConfiguration(attributes);
-
- } else if (AndroidManifest.NODE_USES_FEATURE.equals(localName)) {
- UsesFeature feature = new UsesFeature();
-
- // get the name
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (value != null) {
- feature.mName = value;
- }
-
- // read the required attribute
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_REQUIRED,
- true /*hasNamespace*/);
- if (value != null) {
- Boolean b = Boolean.valueOf(value);
- if (b != null) {
- feature.mRequired = b;
- }
- }
-
- // read the gl es attribute
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_GLESVERSION,
- true /*hasNamespace*/);
- if (value != null) {
- try {
- int version = Integer.decode(value);
- feature.mGlEsVersion = version;
- } catch (NumberFormatException e) {
- // ignore
- }
-
- }
-
- mManifestData.mFeatures.add(feature);
- }
- break;
- case LEVEL_INSIDE_APPLICATION:
- if (AndroidManifest.NODE_ACTIVITY.equals(localName)) {
- processActivityNode(attributes);
- mValidLevel++;
- } else if (AndroidManifest.NODE_SERVICE.equals(localName)) {
- processNode(attributes, SdkConstants.CLASS_SERVICE);
- mValidLevel++;
- } else if (AndroidManifest.NODE_RECEIVER.equals(localName)) {
- processNode(attributes, SdkConstants.CLASS_BROADCASTRECEIVER);
- mValidLevel++;
- } else if (AndroidManifest.NODE_PROVIDER.equals(localName)) {
- processNode(attributes, SdkConstants.CLASS_CONTENTPROVIDER);
- mValidLevel++;
- } else if (AndroidManifest.NODE_USES_LIBRARY.equals(localName)) {
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (value != null) {
- UsesLibrary library = new UsesLibrary();
- library.mName = value;
-
- // read the required attribute
- value = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_REQUIRED,
- true /*hasNamespace*/);
- if (value != null) {
- Boolean b = Boolean.valueOf(value);
- if (b != null) {
- library.mRequired = b;
- }
- }
-
- mManifestData.mLibraries.add(library);
- }
- }
- break;
- case LEVEL_INSIDE_APP_COMPONENT:
- // only process this level if we are in an activity
- if (mCurrentActivity != null &&
- AndroidManifest.NODE_INTENT.equals(localName)) {
- mCurrentActivity.resetIntentFilter();
- mValidLevel++;
- }
- break;
- case LEVEL_INSIDE_INTENT_FILTER:
- if (mCurrentActivity != null) {
- if (AndroidManifest.NODE_ACTION.equals(localName)) {
- // get the name attribute
- String action = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (action != null) {
- mCurrentActivity.setHasAction(true);
- mCurrentActivity.setHasMainAction(
- ACTION_MAIN.equals(action));
- }
- } else if (AndroidManifest.NODE_CATEGORY.equals(localName)) {
- String category = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (CATEGORY_LAUNCHER.equals(category)) {
- mCurrentActivity.setHasLauncherCategory(true);
- }
- }
-
- // no need to increase mValidLevel as we don't process anything
- // below this level.
- }
- break;
- }
- }
-
- mCurrentLevel++;
- } finally {
- super.startElement(uri, localName, name, attributes);
- }
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
- * java.lang.String)
- */
- @Override
- public void endElement(String uri, String localName, String name) throws SAXException {
- try {
- if (mManifestData == null) {
- return;
- }
-
- // decrement the levels.
- if (mValidLevel == mCurrentLevel) {
- mValidLevel--;
- }
- mCurrentLevel--;
-
- // if we're at a valid level
- // process the end of the element
- if (mValidLevel == mCurrentLevel) {
- switch (mValidLevel) {
- case LEVEL_INSIDE_APPLICATION:
- mCurrentActivity = null;
- break;
- case LEVEL_INSIDE_APP_COMPONENT:
- // if we found both a main action and a launcher category, this is our
- // launcher activity!
- if (mManifestData.mLauncherActivity == null &&
- mCurrentActivity != null &&
- mCurrentActivity.isHomeActivity() &&
- mCurrentActivity.isExported()) {
- mManifestData.mLauncherActivity = mCurrentActivity;
- }
- break;
- default:
- break;
- }
-
- }
- } finally {
- super.endElement(uri, localName, name);
- }
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
- */
- @Override
- public void error(SAXParseException e) {
- if (mErrorHandler != null) {
- mErrorHandler.handleError(e, e.getLineNumber());
- }
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
- */
- @Override
- public void fatalError(SAXParseException e) {
- if (mErrorHandler != null) {
- mErrorHandler.handleError(e, e.getLineNumber());
- }
- }
-
- /* (non-Javadoc)
- * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
- */
- @Override
- public void warning(SAXParseException e) throws SAXException {
- if (mErrorHandler != null) {
- mErrorHandler.warning(e);
- }
- }
-
- /**
- * Processes the activity node.
- * @param attributes the attributes for the activity node.
- */
- private void processActivityNode(Attributes attributes) {
- // lets get the activity name, and add it to the list
- String activityName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (activityName != null) {
- activityName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage,
- activityName);
-
- // get the exported flag.
- String exportedStr = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_EXPORTED, true);
- boolean exported = exportedStr == null ||
- exportedStr.toLowerCase(Locale.US).equals("true"); //$NON-NLS-1$
- mCurrentActivity = new Activity(activityName, exported);
- mManifestData.mActivities.add(mCurrentActivity);
-
- if (mErrorHandler != null) {
- mErrorHandler.checkClass(mLocator, activityName, SdkConstants.CLASS_ACTIVITY,
- true /* testVisibility */);
- }
- } else {
- // no activity found! Aapt will output an error,
- // so we don't have to do anything
- mCurrentActivity = null;
- }
-
- String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS,
- true /* hasNamespace */);
- if (processName != null) {
- mManifestData.addProcessName(processName);
- }
- }
-
- /**
- * Processes the service/receiver/provider nodes.
- * @param attributes the attributes for the activity node.
- * @param superClassName the fully qualified name of the super class that this
- * node is representing
- */
- private void processNode(Attributes attributes, String superClassName) {
- // lets get the class name, and check it if required.
- String serviceName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (serviceName != null) {
- serviceName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage,
- serviceName);
-
- if (mErrorHandler != null) {
- mErrorHandler.checkClass(mLocator, serviceName, superClassName,
- false /* testVisibility */);
- }
- }
-
- String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS,
- true /* hasNamespace */);
- if (processName != null) {
- mManifestData.addProcessName(processName);
- }
- }
-
- /**
- * Processes the instrumentation node.
- * @param attributes the attributes for the instrumentation node.
- */
- private void processInstrumentationNode(Attributes attributes) {
- // lets get the class name, and check it if required.
- String instrumentationName = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (instrumentationName != null) {
- String instrClassName = AndroidManifest.combinePackageAndClassName(
- mManifestData.mPackage, instrumentationName);
- String targetPackage = getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_TARGET_PACKAGE,
- true /* hasNamespace */);
- mManifestData.mInstrumentations.add(
- new Instrumentation(instrClassName, targetPackage));
- if (mErrorHandler != null) {
- mErrorHandler.checkClass(mLocator, instrClassName,
- SdkConstants.CLASS_INSTRUMENTATION, true /* testVisibility */);
- }
- }
- }
-
- /**
- * Processes the supports-screens node.
- * @param attributes the attributes for the supports-screens node.
- */
- private void processSupportsScreensNode(Attributes attributes) {
- mManifestData.mSupportsScreensFromManifest = new SupportsScreens();
-
- mManifestData.mSupportsScreensFromManifest.setResizeable(getAttributeBooleanValue(
- attributes, AndroidManifest.ATTRIBUTE_RESIZEABLE, true /*hasNamespace*/));
-
- mManifestData.mSupportsScreensFromManifest.setAnyDensity(getAttributeBooleanValue(
- attributes, AndroidManifest.ATTRIBUTE_ANYDENSITY, true /*hasNamespace*/));
-
- mManifestData.mSupportsScreensFromManifest.setSmallScreens(getAttributeBooleanValue(
- attributes, AndroidManifest.ATTRIBUTE_SMALLSCREENS, true /*hasNamespace*/));
-
- mManifestData.mSupportsScreensFromManifest.setNormalScreens(getAttributeBooleanValue(
- attributes, AndroidManifest.ATTRIBUTE_NORMALSCREENS, true /*hasNamespace*/));
-
- mManifestData.mSupportsScreensFromManifest.setLargeScreens(getAttributeBooleanValue(
- attributes, AndroidManifest.ATTRIBUTE_LARGESCREENS, true /*hasNamespace*/));
- }
-
- /**
- * Processes the supports-screens node.
- * @param attributes the attributes for the supports-screens node.
- */
- private void processUsesConfiguration(Attributes attributes) {
- mManifestData.mUsesConfiguration = new UsesConfiguration();
-
- mManifestData.mUsesConfiguration.mReqFiveWayNav = getAttributeBooleanValue(
- attributes,
- AndroidManifest.ATTRIBUTE_REQ_5WAYNAV, true /*hasNamespace*/);
- mManifestData.mUsesConfiguration.mReqNavigation = Navigation.getEnum(
- getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_REQ_NAVIGATION, true /*hasNamespace*/));
- mManifestData.mUsesConfiguration.mReqHardKeyboard = getAttributeBooleanValue(
- attributes,
- AndroidManifest.ATTRIBUTE_REQ_HARDKEYBOARD, true /*hasNamespace*/);
- mManifestData.mUsesConfiguration.mReqKeyboardType = Keyboard.getEnum(
- getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_REQ_KEYBOARDTYPE, true /*hasNamespace*/));
- mManifestData.mUsesConfiguration.mReqTouchScreen = TouchScreen.getEnum(
- getAttributeValue(attributes,
- AndroidManifest.ATTRIBUTE_REQ_TOUCHSCREEN, true /*hasNamespace*/));
- }
-
- /**
- * Searches through the attributes list for a particular one and returns its value.
- * @param attributes the attribute list to search through
- * @param attributeName the name of the attribute to look for.
- * @param hasNamespace Indicates whether the attribute has an android namespace.
- * @return a String with the value or null if the attribute was not found.
- * @see SdkConstants#NS_RESOURCES
- */
- private String getAttributeValue(Attributes attributes, String attributeName,
- boolean hasNamespace) {
- int count = attributes.getLength();
- for (int i = 0 ; i < count ; i++) {
- if (attributeName.equals(attributes.getLocalName(i)) &&
- ((hasNamespace &&
- SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
- (hasNamespace == false && attributes.getURI(i).length() == 0))) {
- return attributes.getValue(i);
- }
- }
-
- return null;
- }
-
- /**
- * Searches through the attributes list for a particular one and returns its value as a
- * Boolean. If the attribute is not present, this will return null.
- * @param attributes the attribute list to search through
- * @param attributeName the name of the attribute to look for.
- * @param hasNamespace Indicates whether the attribute has an android namespace.
- * @return a String with the value or null if the attribute was not found.
- * @see SdkConstants#NS_RESOURCES
- */
- private Boolean getAttributeBooleanValue(Attributes attributes, String attributeName,
- boolean hasNamespace) {
- int count = attributes.getLength();
- for (int i = 0 ; i < count ; i++) {
- if (attributeName.equals(attributes.getLocalName(i)) &&
- ((hasNamespace &&
- SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
- (hasNamespace == false && attributes.getURI(i).length() == 0))) {
- String attr = attributes.getValue(i);
- if (attr != null) {
- return Boolean.valueOf(attr);
- } else {
- return null;
- }
- }
- }
-
- return null;
- }
-
- }
-
- private final static SAXParserFactory sParserFactory;
-
- static {
- sParserFactory = SAXParserFactory.newInstance();
- sParserFactory.setNamespaceAware(true);
- }
-
- /**
- * Parses the Android Manifest, and returns a {@link ManifestData} object containing the
- * result of the parsing.
- *
- * @param manifestFile the {@link IAbstractFile} representing the manifest file.
- * @param gatherData indicates whether the parsing will extract data from the manifest. If false
- * the method will always return null.
- * @param errorHandler an optional errorHandler.
- * @return A class containing the manifest info obtained during the parsing, or null on error.
- *
- * @throws StreamException
- * @throws IOException
- * @throws SAXException
- * @throws ParserConfigurationException
- */
- public static ManifestData parse(
- IAbstractFile manifestFile,
- boolean gatherData,
- ManifestErrorHandler errorHandler)
- throws SAXException, IOException, StreamException, ParserConfigurationException {
- if (manifestFile != null) {
- SAXParser parser = sParserFactory.newSAXParser();
-
- ManifestData data = null;
- if (gatherData) {
- data = new ManifestData();
- }
-
- ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
- data, errorHandler);
- parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
-
- return data;
- }
-
- return null;
- }
-
- /**
- * Parses the Android Manifest, and returns an object containing the result of the parsing.
- *
- * <p/>
- * This is the equivalent of calling <pre>parse(manifestFile, true, null)</pre>
- *
- * @param manifestFile the manifest file to parse.
- *
- * @throws ParserConfigurationException
- * @throws StreamException
- * @throws IOException
- * @throws SAXException
- */
- public static ManifestData parse(IAbstractFile manifestFile)
- throws SAXException, IOException, StreamException, ParserConfigurationException {
- return parse(manifestFile, true, null);
- }
-
- public static ManifestData parse(IAbstractFolder projectFolder)
- throws SAXException, IOException, StreamException, ParserConfigurationException {
- IAbstractFile manifestFile = AndroidManifest.getManifest(projectFolder);
- if (manifestFile == null) {
- throw new FileNotFoundException();
- }
-
- return parse(manifestFile, true, null);
- }
-
- /**
- * Parses the Android Manifest from an {@link InputStream}, and returns a {@link ManifestData}
- * object containing the result of the parsing.
- *
- * @param manifestFileStream the {@link InputStream} representing the manifest file.
- * @return A class containing the manifest info obtained during the parsing or null on error.
- *
- * @throws StreamException
- * @throws IOException
- * @throws SAXException
- * @throws ParserConfigurationException
- */
- public static ManifestData parse(InputStream manifestFileStream)
- throws SAXException, IOException, StreamException, ParserConfigurationException {
- if (manifestFileStream != null) {
- SAXParser parser = sParserFactory.newSAXParser();
-
- ManifestData data = new ManifestData();
-
- ManifestHandler manifestHandler = new ManifestHandler(null, data, null);
- parser.parse(new InputSource(manifestFileStream), manifestHandler);
-
- return data;
- }
-
- return null;
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidXPathFactory.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidXPathFactory.java
deleted file mode 100644
index d6b2a6b..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidXPathFactory.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.xml;
-
-import com.android.sdklib.SdkConstants;
-
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.xml.XMLConstants;
-import javax.xml.namespace.NamespaceContext;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathFactory;
-
-/**
- * XPath factory with automatic support for the android name space.
- */
-public class AndroidXPathFactory {
- /** Default prefix for android name space: 'android' */
- public final static String DEFAULT_NS_PREFIX = "android"; //$NON-NLS-1$
-
- private final static XPathFactory sFactory = XPathFactory.newInstance();
-
- /** Name space context for Android resource XML files. */
- private static class AndroidNamespaceContext implements NamespaceContext {
- private final static AndroidNamespaceContext sThis = new AndroidNamespaceContext(
- DEFAULT_NS_PREFIX);
-
- private final String mAndroidPrefix;
- private final List<String> mAndroidPrefixes;
-
- /**
- * Returns the default {@link AndroidNamespaceContext}.
- */
- private static AndroidNamespaceContext getDefault() {
- return sThis;
- }
-
- /**
- * Construct the context with the prefix associated with the android namespace.
- * @param androidPrefix the Prefix
- */
- public AndroidNamespaceContext(String androidPrefix) {
- mAndroidPrefix = androidPrefix;
- mAndroidPrefixes = Collections.singletonList(mAndroidPrefix);
- }
-
- @Override
- public String getNamespaceURI(String prefix) {
- if (prefix != null) {
- if (prefix.equals(mAndroidPrefix)) {
- return SdkConstants.NS_RESOURCES;
- }
- }
-
- return XMLConstants.NULL_NS_URI;
- }
-
- @Override
- public String getPrefix(String namespaceURI) {
- if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
- return mAndroidPrefix;
- }
-
- return null;
- }
-
- @Override
- public Iterator<?> getPrefixes(String namespaceURI) {
- if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
- return mAndroidPrefixes.iterator();
- }
-
- return null;
- }
- }
-
- /**
- * Creates a new XPath object, specifying which prefix in the query is used for the
- * android namespace.
- * @param androidPrefix The namespace prefix.
- */
- public static XPath newXPath(String androidPrefix) {
- XPath xpath = sFactory.newXPath();
- xpath.setNamespaceContext(new AndroidNamespaceContext(androidPrefix));
- return xpath;
- }
-
- /**
- * Creates a new XPath object using the default prefix for the android namespace.
- * @see #DEFAULT_NS_PREFIX
- */
- public static XPath newXPath() {
- XPath xpath = sFactory.newXPath();
- xpath.setNamespaceContext(AndroidNamespaceContext.getDefault());
- return xpath;
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java
deleted file mode 100644
index 501a237..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/ManifestData.java
+++ /dev/null
@@ -1,747 +0,0 @@
-/*
- * Copyright (C) 2010 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.xml;
-
-import com.android.resources.Keyboard;
-import com.android.resources.Navigation;
-import com.android.resources.TouchScreen;
-
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * Class containing the manifest info obtained during the parsing.
- */
-public final class ManifestData {
-
- /**
- * Value returned by {@link #getMinSdkVersion()} when the value of the minSdkVersion attribute
- * in the manifest is a codename and not an integer value.
- */
- public final static int MIN_SDK_CODENAME = 0;
-
- /**
- * Value returned by {@link #getGlEsVersion()} when there are no <uses-feature> node with the
- * attribute glEsVersion set.
- */
- public final static int GL_ES_VERSION_NOT_SET = -1;
-
- /** Application package */
- String mPackage;
- /** Application version Code, null if the attribute is not present. */
- Integer mVersionCode = null;
- /** List of all activities */
- final ArrayList<Activity> mActivities = new ArrayList<Activity>();
- /** Launcher activity */
- Activity mLauncherActivity = null;
- /** list of process names declared by the manifest */
- Set<String> mProcesses = null;
- /** debuggable attribute value. If null, the attribute is not present. */
- Boolean mDebuggable = null;
- /** API level requirement. if null the attribute was not present. */
- private String mMinSdkVersionString = null;
- /** API level requirement. Default is 1 even if missing. If value is a codename, then it'll be
- * 0 instead. */
- private int mMinSdkVersion = 1;
- private int mTargetSdkVersion = 0;
- /** List of all instrumentations declared by the manifest */
- final ArrayList<Instrumentation> mInstrumentations =
- new ArrayList<Instrumentation>();
- /** List of all libraries in use declared by the manifest */
- final ArrayList<UsesLibrary> mLibraries = new ArrayList<UsesLibrary>();
- /** List of all feature in use declared by the manifest */
- final ArrayList<UsesFeature> mFeatures = new ArrayList<UsesFeature>();
-
- SupportsScreens mSupportsScreensFromManifest;
- SupportsScreens mSupportsScreensValues;
- UsesConfiguration mUsesConfiguration;
-
- /**
- * Instrumentation info obtained from manifest
- */
- public final static class Instrumentation {
- private final String mName;
- private final String mTargetPackage;
-
- Instrumentation(String name, String targetPackage) {
- mName = name;
- mTargetPackage = targetPackage;
- }
-
- /**
- * Returns the fully qualified instrumentation class name
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the Android app package that is the target of this instrumentation
- */
- public String getTargetPackage() {
- return mTargetPackage;
- }
- }
-
- /**
- * Activity info obtained from the manifest.
- */
- public final static class Activity {
- private final String mName;
- private final boolean mIsExported;
- private boolean mHasAction = false;
- private boolean mHasMainAction = false;
- private boolean mHasLauncherCategory = false;
-
- public Activity(String name, boolean exported) {
- mName = name;
- mIsExported = exported;
- }
-
- public String getName() {
- return mName;
- }
-
- public boolean isExported() {
- return mIsExported;
- }
-
- public boolean hasAction() {
- return mHasAction;
- }
-
- public boolean isHomeActivity() {
- return mHasMainAction && mHasLauncherCategory;
- }
-
- void setHasAction(boolean hasAction) {
- mHasAction = hasAction;
- }
-
- /** If the activity doesn't yet have a filter set for the launcher, this resets both
- * flags. This is to handle multiple intent-filters where one could have the valid
- * action, and another one of the valid category.
- */
- void resetIntentFilter() {
- if (isHomeActivity() == false) {
- mHasMainAction = mHasLauncherCategory = false;
- }
- }
-
- void setHasMainAction(boolean hasMainAction) {
- mHasMainAction = hasMainAction;
- }
-
- void setHasLauncherCategory(boolean hasLauncherCategory) {
- mHasLauncherCategory = hasLauncherCategory;
- }
- }
-
- /**
- * Class representing the <code>supports-screens</code> node in the manifest.
- * By default, all the getters will return null if there was no value defined in the manifest.
- *
- * To get an instance with all the actual values, use {@link #resolveSupportsScreensValues(int)}
- */
- public final static class SupportsScreens {
- private Boolean mResizeable;
- private Boolean mAnyDensity;
- private Boolean mSmallScreens;
- private Boolean mNormalScreens;
- private Boolean mLargeScreens;
-
- public SupportsScreens() {
- }
-
- /**
- * Instantiate an instance from a string. The string must have been created with
- * {@link #getEncodedValues()}.
- * @param value the string.
- */
- public SupportsScreens(String value) {
- String[] values = value.split("\\|");
-
- mAnyDensity = Boolean.valueOf(values[0]);
- mResizeable = Boolean.valueOf(values[1]);
- mSmallScreens = Boolean.valueOf(values[2]);
- mNormalScreens = Boolean.valueOf(values[3]);
- mLargeScreens = Boolean.valueOf(values[4]);
- }
-
- /**
- * Returns an instance of {@link SupportsScreens} initialized with the default values
- * based on the given targetSdkVersion.
- * @param targetSdkVersion
- */
- public static SupportsScreens getDefaultValues(int targetSdkVersion) {
- SupportsScreens result = new SupportsScreens();
-
- result.mNormalScreens = Boolean.TRUE;
- // Screen size and density became available in Android 1.5/API3, so before that
- // non normal screens were not supported by default. After they are considered
- // supported.
- result.mResizeable = result.mAnyDensity = result.mSmallScreens = result.mLargeScreens =
- targetSdkVersion <= 3 ? Boolean.FALSE : Boolean.TRUE;
-
- return result;
- }
-
- /**
- * Returns a version of the receiver for which all values have been set, even if they
- * were not present in the manifest.
- * @param targetSdkVersion the target api level of the app, since this has an effect
- * on default values.
- */
- public SupportsScreens resolveSupportsScreensValues(int targetSdkVersion) {
- SupportsScreens result = getDefaultValues(targetSdkVersion);
-
- // Override the default with the existing values:
- if (mResizeable != null) result.mResizeable = mResizeable;
- if (mAnyDensity != null) result.mAnyDensity = mAnyDensity;
- if (mSmallScreens != null) result.mSmallScreens = mSmallScreens;
- if (mNormalScreens != null) result.mNormalScreens = mNormalScreens;
- if (mLargeScreens != null) result.mLargeScreens = mLargeScreens;
-
- return result;
- }
-
- /**
- * returns the value of the <code>resizeable</code> attribute or null if not present.
- */
- public Boolean getResizeable() {
- return mResizeable;
- }
-
- void setResizeable(Boolean resizeable) {
- mResizeable = getConstantBoolean(resizeable);
- }
-
- /**
- * returns the value of the <code>anyDensity</code> attribute or null if not present.
- */
- public Boolean getAnyDensity() {
- return mAnyDensity;
- }
-
- void setAnyDensity(Boolean anyDensity) {
- mAnyDensity = getConstantBoolean(anyDensity);
- }
-
- /**
- * returns the value of the <code>smallScreens</code> attribute or null if not present.
- */
- public Boolean getSmallScreens() {
- return mSmallScreens;
- }
-
- void setSmallScreens(Boolean smallScreens) {
- mSmallScreens = getConstantBoolean(smallScreens);
- }
-
- /**
- * returns the value of the <code>normalScreens</code> attribute or null if not present.
- */
- public Boolean getNormalScreens() {
- return mNormalScreens;
- }
-
- void setNormalScreens(Boolean normalScreens) {
- mNormalScreens = getConstantBoolean(normalScreens);
- }
-
- /**
- * returns the value of the <code>largeScreens</code> attribute or null if not present.
- */
- public Boolean getLargeScreens() {
- return mLargeScreens;
- }
-
- void setLargeScreens(Boolean largeScreens) {
- mLargeScreens = getConstantBoolean(largeScreens);
- }
-
- /**
- * Returns either {@link Boolean#TRUE} or {@link Boolean#FALSE} based on the value of
- * the given Boolean object.
- */
- private Boolean getConstantBoolean(Boolean v) {
- if (v != null) {
- if (v.equals(Boolean.TRUE)) {
- return Boolean.TRUE;
- } else {
- return Boolean.FALSE;
- }
- }
-
- return null;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SupportsScreens) {
- SupportsScreens support = (SupportsScreens) obj;
- // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE
- // (or null), we can simply check they are identical and not bother with
- // calling equals (which would require to check != null.
- // see #getConstanntBoolean(Boolean)
- return mResizeable == support.mResizeable &&
- mAnyDensity == support.mAnyDensity &&
- mSmallScreens == support.mSmallScreens &&
- mNormalScreens == support.mNormalScreens &&
- mLargeScreens == support.mLargeScreens;
- }
-
- return false;
- }
-
- /* Override hashCode, mostly to make Eclipse happy and not warn about it.
- * And if you ever put this in a Map or Set, it will avoid surprises. */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mAnyDensity == null) ? 0 : mAnyDensity.hashCode());
- result = prime * result + ((mLargeScreens == null) ? 0 : mLargeScreens.hashCode());
- result = prime * result + ((mNormalScreens == null) ? 0 : mNormalScreens.hashCode());
- result = prime * result + ((mResizeable == null) ? 0 : mResizeable.hashCode());
- result = prime * result + ((mSmallScreens == null) ? 0 : mSmallScreens.hashCode());
- return result;
- }
-
- /**
- * Returns true if the two instances support the same screen sizes.
- * This is similar to {@link #equals(Object)} except that it ignores the values of
- * {@link #getAnyDensity()} and {@link #getResizeable()}.
- * @param support the other instance to compare to.
- * @return true if the two instances support the same screen sizes.
- */
- public boolean hasSameScreenSupportAs(SupportsScreens support) {
- // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE
- // (or null), we can simply check they are identical and not bother with
- // calling equals (which would require to check != null.
- // see #getConstanntBoolean(Boolean)
-
- // This only checks that matter here are the screen sizes. resizeable and anyDensity
- // are not checked.
- return mSmallScreens == support.mSmallScreens &&
- mNormalScreens == support.mNormalScreens &&
- mLargeScreens == support.mLargeScreens;
- }
-
- /**
- * Returns true if the two instances have strictly different screen size support.
- * This means that there is no screen size that they both support.
- * @param support the other instance to compare to.
- * @return true if they are stricly different.
- */
- public boolean hasStrictlyDifferentScreenSupportAs(SupportsScreens support) {
- // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE
- // (or null), we can simply check they are identical and not bother with
- // calling equals (which would require to check != null.
- // see #getConstanntBoolean(Boolean)
-
- // This only checks that matter here are the screen sizes. resizeable and anyDensity
- // are not checked.
- return (mSmallScreens != Boolean.TRUE || support.mSmallScreens != Boolean.TRUE) &&
- (mNormalScreens != Boolean.TRUE || support.mNormalScreens != Boolean.TRUE) &&
- (mLargeScreens != Boolean.TRUE || support.mLargeScreens != Boolean.TRUE);
- }
-
- /**
- * Comparison of 2 Supports-screens. This only uses screen sizes (ignores resizeable and
- * anyDensity), and considers that
- * {@link #hasStrictlyDifferentScreenSupportAs(SupportsScreens)} returns true and
- * {@link #overlapWith(SupportsScreens)} returns false.
- * @throws IllegalArgumentException if the two instanced are not strictly different or
- * overlap each other
- * @see #hasStrictlyDifferentScreenSupportAs(SupportsScreens)
- * @see #overlapWith(SupportsScreens)
- */
- public int compareScreenSizesWith(SupportsScreens o) {
- if (hasStrictlyDifferentScreenSupportAs(o) == false) {
- throw new IllegalArgumentException("The two instances are not strictly different.");
- }
- if (overlapWith(o)) {
- throw new IllegalArgumentException("The two instances overlap each other.");
- }
-
- int comp = mLargeScreens.compareTo(o.mLargeScreens);
- if (comp != 0) return comp;
-
- comp = mNormalScreens.compareTo(o.mNormalScreens);
- if (comp != 0) return comp;
-
- comp = mSmallScreens.compareTo(o.mSmallScreens);
- if (comp != 0) return comp;
-
- return 0;
- }
-
- /**
- * Returns a string encoding of the content of the instance. This string can be used to
- * instantiate a {@link SupportsScreens} object through
- * {@link #SupportsScreens(String)}.
- */
- public String getEncodedValues() {
- return String.format("%1$s|%2$s|%3$s|%4$s|%5$s",
- mAnyDensity, mResizeable, mSmallScreens, mNormalScreens, mLargeScreens);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
-
- boolean alreadyOutputSomething = false;
-
- if (Boolean.TRUE.equals(mSmallScreens)) {
- alreadyOutputSomething = true;
- sb.append("small");
- }
-
- if (Boolean.TRUE.equals(mNormalScreens)) {
- if (alreadyOutputSomething) {
- sb.append(", ");
- }
- alreadyOutputSomething = true;
- sb.append("normal");
- }
-
- if (Boolean.TRUE.equals(mLargeScreens)) {
- if (alreadyOutputSomething) {
- sb.append(", ");
- }
- alreadyOutputSomething = true;
- sb.append("large");
- }
-
- if (alreadyOutputSomething == false) {
- sb.append("<none>");
- }
-
- return sb.toString();
- }
-
- /**
- * Returns true if the two instance overlap with each other.
- * This can happen if one instances supports a size, when the other instance doesn't while
- * supporting a size above and a size below.
- * @param otherSS the other supports-screens to compare to.
- */
- public boolean overlapWith(SupportsScreens otherSS) {
- if (mSmallScreens == null || mNormalScreens == null || mLargeScreens == null ||
- otherSS.mSmallScreens == null || otherSS.mNormalScreens == null ||
- otherSS.mLargeScreens == null) {
- throw new IllegalArgumentException("Some screen sizes Boolean are not initialized");
- }
-
- if (mSmallScreens == Boolean.TRUE && mNormalScreens == Boolean.FALSE &&
- mLargeScreens == Boolean.TRUE) {
- return otherSS.mNormalScreens == Boolean.TRUE;
- }
-
- if (otherSS.mSmallScreens == Boolean.TRUE && otherSS.mNormalScreens == Boolean.FALSE &&
- otherSS.mLargeScreens == Boolean.TRUE) {
- return mNormalScreens == Boolean.TRUE;
- }
-
- return false;
- }
- }
-
- /**
- * Class representing a <code>uses-library</code> node in the manifest.
- */
- public final static class UsesLibrary {
- String mName;
- Boolean mRequired = Boolean.TRUE; // default is true even if missing
-
- public String getName() {
- return mName;
- }
-
- public Boolean getRequired() {
- return mRequired;
- }
- }
-
- /**
- * Class representing a <code>uses-feature</code> node in the manifest.
- */
- public final static class UsesFeature {
- String mName;
- int mGlEsVersion = 0;
- Boolean mRequired = Boolean.TRUE; // default is true even if missing
-
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the value of the glEsVersion attribute, or 0 if the attribute was not present.
- */
- public int getGlEsVersion() {
- return mGlEsVersion;
- }
-
- public Boolean getRequired() {
- return mRequired;
- }
- }
-
- /**
- * Class representing the <code>uses-configuration</code> node in the manifest.
- */
- public final static class UsesConfiguration {
- Boolean mReqFiveWayNav;
- Boolean mReqHardKeyboard;
- Keyboard mReqKeyboardType;
- TouchScreen mReqTouchScreen;
- Navigation mReqNavigation;
-
- /**
- * returns the value of the <code>reqFiveWayNav</code> attribute or null if not present.
- */
- public Boolean getReqFiveWayNav() {
- return mReqFiveWayNav;
- }
-
- /**
- * returns the value of the <code>reqNavigation</code> attribute or null if not present.
- */
- public Navigation getReqNavigation() {
- return mReqNavigation;
- }
-
- /**
- * returns the value of the <code>reqHardKeyboard</code> attribute or null if not present.
- */
- public Boolean getReqHardKeyboard() {
- return mReqHardKeyboard;
- }
-
- /**
- * returns the value of the <code>reqKeyboardType</code> attribute or null if not present.
- */
- public Keyboard getReqKeyboardType() {
- return mReqKeyboardType;
- }
-
- /**
- * returns the value of the <code>reqTouchScreen</code> attribute or null if not present.
- */
- public TouchScreen getReqTouchScreen() {
- return mReqTouchScreen;
- }
- }
-
- /**
- * Returns the package defined in the manifest, if found.
- * @return The package name or null if not found.
- */
- public String getPackage() {
- return mPackage;
- }
-
- /**
- * Returns the versionCode value defined in the manifest, if found, null otherwise.
- * @return the versionCode or null if not found.
- */
- public Integer getVersionCode() {
- return mVersionCode;
- }
-
- /**
- * Returns the list of activities found in the manifest.
- * @return An array of fully qualified class names, or empty if no activity were found.
- */
- public Activity[] getActivities() {
- return mActivities.toArray(new Activity[mActivities.size()]);
- }
-
- /**
- * Returns the name of one activity found in the manifest, that is configured to show
- * up in the HOME screen.
- * @return the fully qualified name of a HOME activity or null if none were found.
- */
- public Activity getLauncherActivity() {
- return mLauncherActivity;
- }
-
- /**
- * Returns the list of process names declared by the manifest.
- */
- public String[] getProcesses() {
- if (mProcesses != null) {
- return mProcesses.toArray(new String[mProcesses.size()]);
- }
-
- return new String[0];
- }
-
- /**
- * Returns the <code>debuggable</code> attribute value or null if it is not set.
- */
- public Boolean getDebuggable() {
- return mDebuggable;
- }
-
- /**
- * Returns the <code>minSdkVersion</code> attribute, or null if it's not set.
- */
- public String getMinSdkVersionString() {
- return mMinSdkVersionString;
- }
-
- /**
- * Sets the value of the <code>minSdkVersion</code> attribute.
- * @param minSdkVersion the string value of the attribute in the manifest.
- */
- public void setMinSdkVersionString(String minSdkVersion) {
- mMinSdkVersionString = minSdkVersion;
- if (mMinSdkVersionString != null) {
- try {
- mMinSdkVersion = Integer.parseInt(mMinSdkVersionString);
- } catch (NumberFormatException e) {
- mMinSdkVersion = MIN_SDK_CODENAME;
- }
- }
- }
-
- /**
- * Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set or is a codename.
- * @see #getMinSdkVersionString()
- */
- public int getMinSdkVersion() {
- return mMinSdkVersion;
- }
-
-
- /**
- * Sets the value of the <code>minSdkVersion</code> attribute.
- * @param targetSdkVersion the string value of the attribute in the manifest.
- */
- public void setTargetSdkVersionString(String targetSdkVersion) {
- if (targetSdkVersion != null) {
- try {
- mTargetSdkVersion = Integer.parseInt(targetSdkVersion);
- } catch (NumberFormatException e) {
- // keep the value at 0.
- }
- }
- }
-
- /**
- * Returns the <code>targetSdkVersion</code> attribute, or the same value as
- * {@link #getMinSdkVersion()} if it was not set in the manifest.
- */
- public int getTargetSdkVersion() {
- if (mTargetSdkVersion == 0) {
- return getMinSdkVersion();
- }
-
- return mTargetSdkVersion;
- }
-
- /**
- * Returns the list of instrumentations found in the manifest.
- * @return An array of {@link Instrumentation}, or empty if no instrumentations were
- * found.
- */
- public Instrumentation[] getInstrumentations() {
- return mInstrumentations.toArray(new Instrumentation[mInstrumentations.size()]);
- }
-
- /**
- * Returns the list of libraries in use found in the manifest.
- * @return An array of {@link UsesLibrary} objects, or empty if no libraries were found.
- */
- public UsesLibrary[] getUsesLibraries() {
- return mLibraries.toArray(new UsesLibrary[mLibraries.size()]);
- }
-
- /**
- * Returns the list of features in use found in the manifest.
- * @return An array of {@link UsesFeature} objects, or empty if no libraries were found.
- */
- public UsesFeature[] getUsesFeatures() {
- return mFeatures.toArray(new UsesFeature[mFeatures.size()]);
- }
-
- /**
- * Returns the glEsVersion from a <uses-feature> or {@link #GL_ES_VERSION_NOT_SET} if not set.
- */
- public int getGlEsVersion() {
- for (UsesFeature feature : mFeatures) {
- if (feature.mGlEsVersion > 0) {
- return feature.mGlEsVersion;
- }
- }
- return GL_ES_VERSION_NOT_SET;
- }
-
- /**
- * Returns the {@link SupportsScreens} object representing the <code>supports-screens</code>
- * node, or null if the node doesn't exist at all.
- * Some values in the {@link SupportsScreens} instance maybe null, indicating that they
- * were not present in the manifest. To get an instance that contains the values, as seen
- * by the Android platform when the app is running, use {@link #getSupportsScreensValues()}.
- */
- public SupportsScreens getSupportsScreensFromManifest() {
- return mSupportsScreensFromManifest;
- }
-
- /**
- * Returns an always non-null instance of {@link SupportsScreens} that's been initialized with
- * the default values, and the values from the manifest.
- * The default values depends on the manifest values for minSdkVersion and targetSdkVersion.
- */
- public synchronized SupportsScreens getSupportsScreensValues() {
- if (mSupportsScreensValues == null) {
- if (mSupportsScreensFromManifest == null) {
- mSupportsScreensValues = SupportsScreens.getDefaultValues(getTargetSdkVersion());
- } else {
- // get a SupportsScreen that replace the missing values with default values.
- mSupportsScreensValues = mSupportsScreensFromManifest.resolveSupportsScreensValues(
- getTargetSdkVersion());
- }
- }
-
- return mSupportsScreensValues;
- }
-
- /**
- * Returns the {@link UsesConfiguration} object representing the <code>uses-configuration</code>
- * node, or null if the node doesn't exist at all.
- */
- public UsesConfiguration getUsesConfiguration() {
- return mUsesConfiguration;
- }
-
- void addProcessName(String processName) {
- if (mProcesses == null) {
- mProcesses = new TreeSet<String>();
- }
-
- if (processName.startsWith(":")) {
- mProcesses.add(mPackage + processName);
- } else {
- mProcesses.add(processName);
- }
- }
-
-}