diff options
Diffstat (limited to 'sdkmanager/libs/sdklib/src')
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 <description> 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 <description> 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 <description> 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 <description> 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 <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 "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 <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 "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 <description> 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 <description> 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><obsolete></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><obsolete></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 <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 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 <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 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 <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 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 <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 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 <tool> 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 <tool> and the <platform-tools> 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 <tool> and the <platform-tools> elements must have at
- * least one <archive> compatible with this platform.
- * <p/>
- * Starting the sdk-repository schema v3, <tools> has a <min-platform-tools-rev>
- * node, so technically the corresponding XML schema will be usable only if there's a
- * <platform-tools> 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 <tool>
- * and <platform-tools> 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 <tool>
- * and <platform-tools> 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 <tool> and the <platform-tools> 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 <tool> and the <platform-tools> elements must have at + * least one <archive> compatible with this platform. + * <p/> + * Starting the sdk-repository schema v3, <tools> has a <min-platform-tools-rev> + * node, so technically the corresponding XML schema will be usable only if there's a + * <platform-tools> 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 <tool> + * and <platform-tools> 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 <tool> + * and <platform-tools> 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 <tool> 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 <archives> node is mandatory in the repository packages 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: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 <extra> package + that can be installed in an Android project. + If present, the <project-files> 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 <extra> package + that can be installed in an Android project. + If present, the <project-files> 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 + <system-image> 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 <archives> node is mandatory in the repository packages 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: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 <extra> package + that can be installed in an Android project. + If present, the <project-files> 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 <extra> package + that can be installed in an Android project. + If present, the <project-files> 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 <archives> node is mandatory in the repository packages 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: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); - } - } - -} |