aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java31
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java31
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java13
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java4
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd3
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd45
-rwxr-xr-xsdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java12
-rwxr-xr-xsdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml292
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java55
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java75
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java34
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java483
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java24
14 files changed, 934 insertions, 172 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
index 05a5990..9315c7d 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
@@ -248,28 +248,41 @@ public final class AndroidVersion implements Comparable<AndroidVersion> {
* less than, equal to, or greater than the specified object.
*/
public int compareTo(AndroidVersion o) {
+ return compareTo(o.mApiLevel, o.mCodename);
+ }
+
+ private int compareTo(int apiLevel, String codename) {
if (mCodename == null) {
- if (o.mCodename == null) {
- return mApiLevel - o.mApiLevel;
+ if (codename == null) {
+ return mApiLevel - apiLevel;
} else {
- if (mApiLevel == o.mApiLevel) {
- return -1; // same api level but o is a preview for next version
+ if (mApiLevel == apiLevel) {
+ return -1; // same api level but argument is a preview for next version
}
- return mApiLevel - o.mApiLevel;
+ return mApiLevel - apiLevel;
}
} else {
// 'this' is a preview
- if (mApiLevel == o.mApiLevel) {
- if (o.mCodename == null) {
+ if (mApiLevel == apiLevel) {
+ if (codename == null) {
return +1;
} else {
- return mCodename.compareTo(o.mCodename); // strange case where the 2 previews
+ return mCodename.compareTo(codename); // strange case where the 2 previews
// have different codename?
}
} else {
- return mApiLevel - o.mApiLevel;
+ return mApiLevel - apiLevel;
}
}
}
+
+ /**
+ * Compares this version with the specified API and returns true if this version
+ * is greater or equal than the requested API -- that is the current version is a
+ * suitable min-api-level for the argument API.
+ */
+ public boolean isGreaterOrEqualThan(int api) {
+ return compareTo(api, null /*codename*/) >= 0;
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
index 7f3d0e7..faa7c26 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
@@ -34,6 +34,7 @@ import java.util.Properties;
public class ExtraPackage extends MinToolsPackage {
private static final String PROP_PATH = "Extra.Path"; //$NON-NLS-1$
+ private static final String PROP_MIN_API_LEVEL = "Extra.MinApiLevel"; //$NON-NLS-1$
/**
* The install folder name. It must be a single-segment path.
@@ -44,6 +45,18 @@ public class ExtraPackage extends MinToolsPackage {
private final String mPath;
/**
+ * 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 value of {@link #mMinApiLevel} when the {@link SdkRepository#NODE_MIN_TOOLS_REV}
+ * was not specified in the XML source.
+ */
+ public static final int MIN_API_LEVEL_NOT_SPECIFIED = 0;
+
+ /**
* Creates a new tool package from the attributes and elements of the given XML node.
* <p/>
* This constructor should throw an exception if the package cannot be created.
@@ -52,6 +65,9 @@ public class ExtraPackage extends MinToolsPackage {
super(source, packageNode, licenses);
mPath = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_PATH);
+
+ mMinApiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepository.NODE_MIN_API_LEVEL,
+ MIN_API_LEVEL_NOT_SPECIFIED);
}
/**
@@ -83,6 +99,13 @@ public class ExtraPackage extends MinToolsPackage {
// The path argument comes before whatever could be in the properties
mPath = path != null ? path : getProperty(props, PROP_PATH, path);
+
+ mMinApiLevel = Integer.parseInt(
+ getProperty(props, PROP_MIN_API_LEVEL, Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));
+ }
+
+ public int getMinApiLevel() {
+ return mMinApiLevel;
}
/**
@@ -95,8 +118,8 @@ public class ExtraPackage extends MinToolsPackage {
props.setProperty(PROP_PATH, mPath);
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
+ if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {
+ props.setProperty(PROP_MIN_API_LEVEL, Integer.toString(getMinApiLevel()));
}
}
@@ -176,6 +199,10 @@ public class ExtraPackage extends MinToolsPackage {
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());
+ }
+
return s;
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
index d5892f3..43d0a1e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java
@@ -79,8 +79,8 @@ public abstract class MinToolsPackage extends Package {
super(source, props, revision, license, description, descUrl,
archiveOs, archiveArch, archiveOsPath);
- mMinToolsRevision = Integer.parseInt(getProperty(props, PROP_MIN_TOOLS_REV,
- Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
+ mMinToolsRevision = Integer.parseInt(
+ getProperty(props, PROP_MIN_TOOLS_REV, Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
}
/**
@@ -90,4 +90,13 @@ public abstract class MinToolsPackage extends Package {
public int getMinToolsRevision() {
return mMinToolsRevision;
}
+
+ @Override
+ void saveProperties(Properties props) {
+ super.saveProperties(props);
+
+ if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
+ props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
+ }
+ }
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index 299a32e..07218af 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -97,10 +97,6 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion
if (mVersionName != null) {
props.setProperty(PROP_VERSION, mVersionName);
}
-
- if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
- props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
- }
}
/** Returns the version, a string, for platform packages. */
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
index 725dcbb..be9384a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
@@ -40,7 +40,7 @@ public class SdkRepository {
/** 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 = 1;
+ public static final int NS_LATEST_VERSION = 2;
/** The XML namespace of the latest sdk-repository XML. */
public static final String NS_SDK_REPOSITORY = getSchemaUri(NS_LATEST_VERSION);
@@ -75,6 +75,8 @@ public class SdkRepository {
public static final String NODE_RELEASE_URL = "release-url"; //$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 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$
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd
index f00e337..5637400 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd
@@ -28,6 +28,9 @@
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 Tools r4.
-->
<xsd:element name="sdk-repository">
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd
index 79b7628..37038c3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd
@@ -28,6 +28,10 @@
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.
-->
<xsd:element name="sdk-repository" type="sr2:repositoryType" />
@@ -35,7 +39,7 @@
<xsd:complexType name="repositoryType">
<xsd:annotation>
<xsd:documentation>
- The repository contains collections of downloadable packages.
+ The repository contains a collection of downloadable packages.
</xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
@@ -44,7 +48,6 @@
<xsd:element name="tool" type="sr2:toolType" />
<xsd:element name="doc" type="sr2:docType" />
<xsd:element name="extra" type="sr2:extraType" />
- <xsd:element name="sample" type="sr2:sampleType" />
<xsd:element name="license" type="sr2:licenseType" />
</xsd:choice>
</xsd:complexType>
@@ -247,40 +250,9 @@
<!-- The minimal revision of tools required by this package.
Optional. If present, must be an int > 0. -->
<xsd:element name="min-tools-rev" type="xsd:positiveInteger" minOccurs="0" />
- </xsd:all>
- </xsd:complexType>
-
-
- <!-- The definition of a 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 sample. An int > 0. -->
- <xsd:element name="api-level" type="xsd:positiveInteger" />
- <!-- The optional codename for this sample, if the platform is 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="sr2: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="sr2:archivesType" />
+ <!-- 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" />
</xsd:all>
</xsd:complexType>
@@ -302,6 +274,7 @@
</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.
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java b/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java
index 17bbaa1..045b02a 100755
--- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java
+++ b/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java
@@ -146,6 +146,18 @@ public class SdkRepositoryTest extends TestCase {
handler.verify();
}
+ /** Validate a valid sample using namespace version 1 using an InputStream */
+ public void testValidateLocalRepositoryFile2() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/repository_sample_2.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getValidator(2, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
/** A document should at least have a root to be valid */
public void testEmptyXml() throws Exception {
String document = "<?xml version=\"1.0\"?>";
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml b/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml
new file mode 100755
index 0000000..6259794
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml
@@ -0,0 +1,292 @@
+<?xml version="1.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.
+-->
+<sdk:sdk-repository
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:sdk="http://schemas.android.com/sdk/android/repository/2">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <sdk:license type="text" id="license1">
+ This is the license
+ for this platform.
+ </sdk:license>
+
+ <sdk:license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </sdk:license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+
+ <sdk:platform>
+ <sdk:version>1.0</sdk:version>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <sdk:release-note>This is an optional release note
+ for this package. It's a free multi-line text.
+ </sdk:release-note>
+ <sdk:release-url>http://some/url/for/the/release/note.html</sdk:release-url>
+ <sdk:min-tools-rev>2</sdk:min-tools-rev>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform>
+
+ <sdk:doc>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <!-- the license element is not mandatory. -->
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/docs/docs1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:add-on>
+ <sdk:name>My First add-on</sdk:name>
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:vendor>John Doe</sdk:vendor>
+ <sdk:revision>1</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/add-ons/first.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <!-- sdk:description is optional, name is not -->
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ </sdk:add-on>
+
+ <sdk:platform>
+ <sdk:version>1.1</sdk:version>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>12</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <!-- sdk:description and sdk:desc-url are optional -->
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <!-- arch attribute is optional -->
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx" arch="ppc">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux" arch="x86_64">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform>
+
+ <sdk:add-on>
+ <sdk:name>My Second add-on</sdk:name>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:vendor>John Deer</sdk:vendor>
+ <sdk:revision>42</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ <sdk:uses-license ref="license2" />
+ </sdk:add-on>
+
+ <sdk:platform>
+ <sdk:version>Pastry</sdk:version>
+ <sdk:api-level>5</sdk:api-level>
+ <sdk:codename>Pastry</sdk:codename>
+ <sdk:revision>3</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:description>Preview version for Pastry</sdk:description>
+ <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+ <!-- The archives node is mandatory and it cannot be empty. -->
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:platform>
+
+ <sdk:tool>
+ <sdk:revision>1</sdk:revision>
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/files/tools1.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:doc>
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/docs2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:doc>
+
+ <sdk:tool>
+ <sdk:revision>42</sdk:revision>
+ <sdk:uses-license ref="license1" />
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools/2.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="macosx">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/tools2-mac.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ </sdk:tool>
+
+ <sdk:add-on>
+ <sdk:uses-license ref="license2" />
+ <sdk:name>This add-on has no libraries</sdk:name>
+ <sdk:api-level>4</sdk:api-level>
+ <sdk:vendor>Joe Bar</sdk:vendor>
+ <sdk:revision>3</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/imnotanarchiveimadoctorjim.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs />
+ </sdk:add-on>
+
+ <sdk:extra>
+ <sdk:path>usb_driver</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>43</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extraduff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>An Extra package for the USB driver, it will install in $SDK/usb_driver</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>3</sdk:min-tools-rev>
+ </sdk:extra>
+
+ <sdk:extra>
+ <sdk:path>extra_api_dep</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>2</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>3</sdk:min-tools-rev>
+ <sdk:min-api-level>42</sdk:min-api-level>
+ </sdk:extra>
+
+</sdk:sdk-repository>
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java
index 83dc8ca..593f007 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java
@@ -17,6 +17,8 @@
package com.android.sdkuilib.internal.repository;
import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.Package;
import java.util.ArrayList;
import java.util.Collection;
@@ -30,14 +32,21 @@ import java.util.Collection;
* installed. It can replace an existing local one. It can also depends on another
* (new or local) archive, which means the dependent archive needs to be successfully
* installed first. Finally this archive can also be a dependency for another one.
+ * <p/>
+ * The accepted and rejected flags are used by {@link UpdateChooserDialog} to follow
+ * user choices. The installer should never install something that is not accepted.
+ * <p/>
+ * <em>Note</em>: There is currently no logic to support more than one level of
+ * dependency, either here or in the {@link UpdateChooserDialog}, since we currently
+ * have no need for it.
*
- * @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo)
+ * @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo[])
*/
-class ArchiveInfo {
+class ArchiveInfo implements IDescription {
private final Archive mNewArchive;
private final Archive mReplaced;
- private final ArchiveInfo mDependsOn;
+ private final ArchiveInfo[] mDependsOn;
private final ArrayList<ArchiveInfo> mDependencyFor = new ArrayList<ArchiveInfo>();
private boolean mAccepted;
private boolean mRejected;
@@ -45,15 +54,16 @@ class ArchiveInfo {
/**
*
* @param newArchive A "new archive" to be installed. This is always an archive
- * that comes from a remote site. This can not be null.
+ * 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.
* @param dependsOn An optional new or local dependency, that is an archive that
* <em>this</em> archive depends upon. In other words, we can only install
* this archive if the dependency has been successfully installed. It also
- * means we need to install the dependency first.
+ * means we need to install the dependency first. Can be null or empty.
+ * However it cannot contain nulls.
*/
- public ArchiveInfo(Archive newArchive, Archive replaced, ArchiveInfo dependsOn) {
+ public ArchiveInfo(Archive newArchive, Archive replaced, ArchiveInfo[] dependsOn) {
mNewArchive = newArchive;
mReplaced = replaced;
mDependsOn = dependsOn;
@@ -61,7 +71,7 @@ class ArchiveInfo {
/**
* Returns the "new archive" to be installed.
- * This is always an archive that comes from a remote site.
+ * This <em>may</em> be null for missing archives.
*/
public Archive getNewArchive() {
return mNewArchive;
@@ -80,8 +90,9 @@ class ArchiveInfo {
* archive depends upon. In other words, we can only install this archive if the
* dependency has been successfully installed. It also means we need to install the
* dependency first.
+ * This array can be null or empty. It can't contain nulls though.
*/
- public ArchiveInfo getDependsOn() {
+ public ArchiveInfo[] getDependsOn() {
return mDependsOn;
}
@@ -138,4 +149,32 @@ class ArchiveInfo {
public boolean isRejected() {
return mRejected;
}
+
+ /**
+ * Returns the long description of the parent package of the new archive, if not null.
+ * Otherwise returns an empty string.
+ */
+ 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.
+ */
+ public String getShortDescription() {
+ if (mNewArchive != null) {
+ Package p = mNewArchive.getParentPackage();
+ if (p != null) {
+ return p.getShortDescription();
+ }
+ }
+ return "";
+ }
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
index 6a139de..8a104e4 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
@@ -286,10 +286,10 @@ final class UpdateChooserDialog extends GridDialog {
// Automatically accept those with an empty license or no license
for (ArchiveInfo ai : mArchives) {
Archive a = ai.getNewArchive();
- assert a != null;
-
- String license = a.getParentPackage().getLicense();
- ai.setAccepted(license == null || license.trim().length() == 0);
+ if (a != null) {
+ String license = a.getParentPackage().getLicense();
+ ai.setAccepted(license == null || license.trim().length() == 0);
+ }
}
// Fill the list with the replacement packages
@@ -391,6 +391,12 @@ final class UpdateChooserDialog extends GridDialog {
/**
* Updates the package description and license text depending on the selected package.
+ * <p/>
+ * Note that right now there is no logic to support more than one level of dependencies
+ * (e.g. A <- B <- C and A is disabled so C should be disabled; currently C's state depends
+ * solely on B's state). We currently don't need this. It would be straightforward to add
+ * if we had a need for it, though. This would require changes to {@link ArchiveInfo} and
+ * {@link UpdaterLogic}.
*/
private void displayInformation(ArchiveInfo ai) {
if (ai == null) {
@@ -399,12 +405,17 @@ final class UpdateChooserDialog extends GridDialog {
}
Archive aNew = ai.getNewArchive();
+ if (aNew == null) {
+ // Only missing archives have a null archive, so we shouldn't be here.
+ return;
+ }
+
Package pNew = aNew.getParentPackage();
- mPackageText.setText(""); //$NON-NLS-1$
+ mPackageText.setText(""); //$NON-NLS-1$
addSectionTitle("Package Description\n");
- addText(pNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
+ addText(pNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
Archive aOld = ai.getReplaced();
if (aOld != null) {
@@ -435,20 +446,24 @@ final class UpdateChooserDialog extends GridDialog {
}
}
- ArchiveInfo aDep = ai.getDependsOn();
- if (aDep != null || ai.isDependencyFor()) {
+ ArchiveInfo[] aDeps = ai.getDependsOn();
+ if ((aDeps != null && aDeps.length > 0) || ai.isDependencyFor()) {
addSectionTitle("Dependencies\n");
- if (aDep != null) {
- addText(String.format("This package depends on %1$s.\n\n",
- aDep.getNewArchive().getParentPackage().getShortDescription()));
+ if (aDeps != null && aDeps.length > 0) {
+ addText("This package depends on:");
+ for (ArchiveInfo aDep : aDeps) {
+ addText(String.format("\n- %1$s",
+ aDep.getShortDescription()));
+ }
+ addText("\n\n");
}
if (ai.isDependencyFor()) {
addText("This package is a dependency for:");
for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
- addText("\n- " +
- ai2.getNewArchive().getParentPackage().getShortDescription());
+ addText(String.format("\n- %1$s",
+ ai2.getShortDescription()));
}
addText("\n\n");
}
@@ -484,17 +499,21 @@ final class UpdateChooserDialog extends GridDialog {
// It only matters if the blocked one is accepted
if (ai2.isAccepted()) {
error = String.format("Package '%1$s' depends on this one.",
- ai2.getNewArchive().getParentPackage().getShortDescription());
+ ai2.getShortDescription());
return;
}
}
} else {
// Case where this package is accepted but blocked by another non-accepted one
- ArchiveInfo adep = ai.getDependsOn();
- if (adep != null && !adep.isAccepted()) {
- error = String.format("This package depends on '%1$s'.",
- adep.getNewArchive().getParentPackage().getShortDescription());
- return;
+ ArchiveInfo[] adeps = ai.getDependsOn();
+ if (adeps != null) {
+ for (ArchiveInfo adep : adeps) {
+ if (!adep.isAccepted()) {
+ error = String.format("This package depends on '%1$s'.",
+ adep.getShortDescription());
+ return;
+ }
+ }
}
}
}
@@ -503,12 +522,16 @@ final class UpdateChooserDialog extends GridDialog {
// package.
for (ArchiveInfo ai2 : mArchives) {
if (ai2.isAccepted()) {
- ArchiveInfo adep = ai2.getDependsOn();
- if (adep != null && !adep.isAccepted()) {
- error = String.format("Package '%1$s' depends on '%2$s'",
- ai2.getNewArchive().getParentPackage().getShortDescription(),
- adep.getNewArchive().getParentPackage().getShortDescription());
- return;
+ ArchiveInfo[] adeps = ai2.getDependsOn();
+ if (adeps != null) {
+ for (ArchiveInfo adep : adeps) {
+ if (!adep.isAccepted()) {
+ error = String.format("Package '%1$s' depends on '%2$s'",
+ ai2.getShortDescription(),
+ adep.getShortDescription());
+ return;
+ }
+ }
}
}
}
@@ -667,7 +690,7 @@ final class UpdateChooserDialog extends GridDialog {
assert element instanceof ArchiveInfo;
ArchiveInfo ai = (ArchiveInfo) element;
- String desc = ai.getNewArchive().getParentPackage().getShortDescription();
+ String desc = ai.getShortDescription();
if (ai.isDependencyFor()) {
desc += " [*]";
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index ed6c6e2..9387826 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -298,8 +298,12 @@ class UpdaterData {
}
int numInstalled = 0;
- for (ArchiveInfo ai : result) {
+ nextArchive: for (ArchiveInfo ai : result) {
Archive archive = ai.getNewArchive();
+ if (archive == null) {
+ // This is not supposed to happen.
+ continue nextArchive;
+ }
int nextProgress = monitor.getProgress() + progressPerArchive;
try {
@@ -307,13 +311,27 @@ class UpdaterData {
break;
}
- ArchiveInfo adep = ai.getDependsOn();
- if (adep != null && !installedArchives.contains(adep.getNewArchive())) {
- // This archive depends on another one that was not installed.
- // Skip it.
- monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
- archive.getParentPackage().getShortDescription(),
- adep.getNewArchive().getParentPackage().getShortDescription());
+ ArchiveInfo[] adeps = ai.getDependsOn();
+ if (adeps != null) {
+ for (ArchiveInfo adep : adeps) {
+ Archive na = adep.getNewArchive();
+ if (na == null) {
+ // This archive depends on a missing archive.
+ // We shouldn't get here.
+ // Skip it.
+ monitor.setResult("Skipping '%1$s'; it depends on a missing package.",
+ archive.getParentPackage().getShortDescription());
+ continue nextArchive;
+ } else if (!installedArchives.contains(na)) {
+ // This archive depends on another one that was not installed.
+ // We shouldn't get here.
+ // Skip it.
+ monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
+ archive.getParentPackage().getShortDescription(),
+ adep.getShortDescription());
+ continue nextArchive;
+ }
+ }
}
if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
index 4478448..192eb3e 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
@@ -47,7 +47,7 @@ class UpdaterLogic {
* Compute which packages to install by taking the user selection
* and adding dependent packages as needed.
*
- * When the user doesn't provide a selection, looks at local package to find
+ * When the user doesn't provide a selection, looks at local packages to find
* those that can be updated and compute dependencies too.
*/
public ArrayList<ArchiveInfo> computeUpdates(
@@ -59,8 +59,11 @@ class UpdaterLogic {
ArrayList<Package> remotePkgs = new ArrayList<Package>();
RepoSource[] remoteSources = sources.getSources();
+ // Create ArchiveInfos out of local (installed) packages.
+ ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
+
if (selectedArchives == null) {
- selectedArchives = findUpdates(localPkgs, remotePkgs, remoteSources);
+ selectedArchives = findUpdates(localArchives, remotePkgs, remoteSources);
}
for (Archive a : selectedArchives) {
@@ -69,7 +72,7 @@ class UpdaterLogic {
selectedArchives,
remotePkgs,
remoteSources,
- localPkgs,
+ localArchives,
false /*automated*/);
}
@@ -84,6 +87,9 @@ class UpdaterLogic {
RepoSources sources,
Package[] localPkgs) {
+ // Create ArchiveInfos out of local (installed) packages.
+ ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
+
// Find the highest platform installed
float currentPlatformScore = 0;
float currentAddonScore = 0;
@@ -159,7 +165,7 @@ class UpdaterLogic {
null /*selectedArchives*/,
remotePkgs,
remoteSources,
- localPkgs,
+ localArchives,
true /*automated*/);
}
}
@@ -175,7 +181,7 @@ class UpdaterLogic {
null /*selectedArchives*/,
remotePkgs,
remoteSources,
- localPkgs,
+ localArchives,
true /*automated*/);
}
}
@@ -184,16 +190,53 @@ class UpdaterLogic {
}
/**
+ * Create a array of {@link ArchiveInfo} based on all local (already installed)
+ * packages. The array is always non-null but may be empty.
+ * <p/>
+ * The local {@link ArchiveInfo} are guaranteed to have one non-null archive
+ * that you can retrieve using {@link ArchiveInfo#getNewArchive()}.
+ */
+ protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) {
+
+ if (localPkgs != null) {
+ ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
+ for (Package p : localPkgs) {
+ // Only accept packages that have one compatible archive.
+ // Local package should have 1 and only 1 compatible archive anyway.
+ for (Archive a : p.getArchives()) {
+ if (a != null && a.isCompatible()) {
+ // We create an "installed" archive info to wrap the local package.
+ // Note that dependencies are not computed since right now we don't
+ // deal with more than one level of dependencies and installed archives
+ // are deemed implicitly accepted anyway.
+ list.add(new LocalArchiveInfo(a));
+ }
+ }
+ }
+
+ return list.toArray(new ArchiveInfo[list.size()]);
+ }
+
+ return new ArchiveInfo[0];
+ }
+
+ /**
* Find suitable updates to all current local packages.
*/
- private Collection<Archive> findUpdates(Package[] localPkgs,
+ private Collection<Archive> findUpdates(ArchiveInfo[] localArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources) {
ArrayList<Archive> updates = new ArrayList<Archive>();
fetchRemotePackages(remotePkgs, remoteSources);
- for (Package localPkg : localPkgs) {
+ for (ArchiveInfo ai : localArchives) {
+ Archive na = ai.getNewArchive();
+ if (na == null) {
+ continue;
+ }
+ Package localPkg = na.getParentPackage();
+
for (Package remotePkg : remotePkgs) {
if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
// Found a suitable update. Only accept the remote package
@@ -217,32 +260,37 @@ class UpdaterLogic {
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
- Package[] localPkgs,
+ ArchiveInfo[] localArchives,
boolean automated) {
Package p = archive.getParentPackage();
// Is this an update?
Archive updatedArchive = null;
- for (Package lp : localPkgs) {
- assert lp.getArchives().length == 1;
- if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
- updatedArchive = lp.getArchives()[0];
+ for (ArchiveInfo ai : localArchives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package lp = a.getParentPackage();
+
+ if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
+ updatedArchive = a;
+ }
}
}
- // find dependencies
- ArchiveInfo dep = findDependency(p,
+ // Find dependencies
+ ArchiveInfo[] deps = findDependency(p,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
- localPkgs);
+ localArchives);
// Make sure it's not a dup
ArchiveInfo ai = null;
for (ArchiveInfo ai2 : outArchives) {
- if (ai2.getNewArchive().getParentPackage().sameItemAs(archive.getParentPackage())) {
+ Archive a2 = ai2.getNewArchive();
+ if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {
ai = ai2;
break;
}
@@ -250,52 +298,82 @@ class UpdaterLogic {
if (ai == null) {
ai = new ArchiveInfo(
- archive, //newArchive
+ archive, //newArchive
updatedArchive, //replaced
- dep //dependsOn
+ deps //dependsOn
);
outArchives.add(ai);
}
- if (dep != null) {
- dep.addDependencyFor(ai);
+ if (deps != null) {
+ for (ArchiveInfo d : deps) {
+ d.addDependencyFor(ai);
+ }
}
return ai;
}
- private ArchiveInfo findDependency(Package pkg,
+ private ArchiveInfo[] findDependency(Package pkg,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
- Package[] localPkgs) {
+ ArchiveInfo[] localArchives) {
// Current dependencies can be:
// - addon: *always* depends on platform of same API level
// - platform: *might* depends on tools of rev >= min-tools-rev
+ // - extra: *might* depends on platform with api >= min-api-level
if (pkg instanceof AddonPackage) {
AddonPackage addon = (AddonPackage) pkg;
- return findPlatformDependency(
+ ArchiveInfo ai = findPlatformDependency(
addon,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
- localPkgs);
+ localArchives);
+
+ if (ai != null) {
+ return new ArchiveInfo[] { ai };
+ }
} else if (pkg instanceof MinToolsPackage) {
MinToolsPackage platformOrExtra = (MinToolsPackage) pkg;
- return findToolsDependency(
+ int n = 0;
+ ArchiveInfo ai1 = findToolsDependency(
platformOrExtra,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
- localPkgs);
+ localArchives);
+
+ n += ai1 == null ? 0 : 1;
+
+ ArchiveInfo ai2 = null;
+ if (pkg instanceof ExtraPackage) {
+ ai2 = findExtraPlatformDependency(
+ (ExtraPackage) pkg,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives);
+ }
+
+ n += ai2 == null ? 0 : 1;
+
+ if (n > 0) {
+ ArchiveInfo[] ais = new ArchiveInfo[n];
+ ais[0] = ai1 != null ? ai1 : ai2;
+ if (n > 1) ais[1] = ai2;
+ return ais;
+ }
}
return null;
@@ -314,7 +392,7 @@ class UpdaterLogic {
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
- Package[] localPkgs) {
+ ArchiveInfo[] localArchives) {
// This is the requirement to match.
int rev = platformOrExtra.getMinToolsRevision();
@@ -323,24 +401,30 @@ class UpdaterLogic {
return null;
}
- // First look in local packages.
- for (Package p : localPkgs) {
- if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
- // We found one already installed. We don't report this dependency
- // as the UI only cares about resolving "newly added dependencies".
- return null;
+ // First look in locally installed packages.
+ for (ArchiveInfo ai : localArchives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof ToolPackage) {
+ if (((ToolPackage) p).getRevision() >= rev) {
+ // We found one already installed.
+ return ai;
+ }
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
- Package p = ai.getNewArchive().getParentPackage();
- if (p instanceof ToolPackage) {
- if (((ToolPackage) p).getRevision() >= rev) {
- // The dependency is already scheduled for install, nothing else to do.
- return ai;
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof ToolPackage) {
+ if (((ToolPackage) p).getRevision() >= rev) {
+ // The dependency is already scheduled for install, nothing else to do.
+ return ai;
+ }
}
}
}
@@ -352,8 +436,12 @@ class UpdaterLogic {
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision() >= rev) {
// It's not already in the list of things to install, so add it now
- return insertArchive(a, outArchives,
- selectedArchives, remotePkgs, remoteSources, localPkgs,
+ return insertArchive(a,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives,
true /*automated*/);
}
}
@@ -369,8 +457,12 @@ class UpdaterLogic {
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
- return insertArchive(a, outArchives,
- selectedArchives, remotePkgs, remoteSources, localPkgs,
+ return insertArchive(a,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives,
true /*automated*/);
}
}
@@ -378,48 +470,56 @@ class UpdaterLogic {
}
}
- // We end up here if nothing matches. We don't have a good tools to match.
- // Seriously, that can't happens unless we totally screwed our repo manifest.
- // We'll let this one go through anyway.
- return null;
+ // We end up here if nothing matches. We don't have a good platform to match.
+ // We need to indicate this extra depends on a missing platform archive
+ // so that it can be impossible to install later on.
+ return new MissingToolArchiveInfo(rev);
}
/**
- * Resolves dependencies on platform.
+ * Resolves dependencies on platform for an addon.
+ *
+ * An addon depends on having a platform with the same API level.
*
- * An addon depends on having a platform with the same API version.
* Finds the platform dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
- protected ArchiveInfo findPlatformDependency(AddonPackage addon,
+ protected ArchiveInfo findPlatformDependency(
+ AddonPackage addon,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
- Package[] localPkgs) {
+ ArchiveInfo[] localArchives) {
// This is the requirement to match.
AndroidVersion v = addon.getVersion();
// Find a platform that would satisfy the requirement.
- // First look in local packages.
- for (Package p : localPkgs) {
- if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
- // We found one already installed. We don't report this dependency
- // as the UI only cares about resolving "newly added dependencies".
- return null;
+ // First look in locally installed packages.
+ for (ArchiveInfo ai : localArchives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (v.equals(((PlatformPackage) p).getVersion())) {
+ // We found one already installed.
+ return ai;
+ }
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
- Package p = ai.getNewArchive().getParentPackage();
- if (p instanceof PlatformPackage) {
- if (v.equals(((PlatformPackage) p).getVersion())) {
- // The dependency is already scheduled for install, nothing else to do.
- return ai;
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (v.equals(((PlatformPackage) p).getVersion())) {
+ // The dependency is already scheduled for install, nothing else to do.
+ return ai;
+ }
}
}
}
@@ -431,8 +531,12 @@ class UpdaterLogic {
if (p instanceof PlatformPackage) {
if (v.equals(((PlatformPackage) p).getVersion())) {
// It's not already in the list of things to install, so add it now
- return insertArchive(a, outArchives,
- selectedArchives, remotePkgs, remoteSources, localPkgs,
+ return insertArchive(a,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives,
true /*automated*/);
}
}
@@ -448,8 +552,12 @@ class UpdaterLogic {
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
- return insertArchive(a, outArchives,
- selectedArchives, remotePkgs, remoteSources, localPkgs,
+ return insertArchive(a,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives,
true /*automated*/);
}
}
@@ -458,11 +566,129 @@ class UpdaterLogic {
}
// We end up here if nothing matches. We don't have a good platform to match.
- // Seriously, that can't happens unless the repository contains a bogus addon
- // entry that does not match any existing platform API level.
- // It's conceivable that a 3rd part addon repo might have error, in which case
- // we'll let this one go through anyway.
- return null;
+ // We need to indicate this addon depends on a missing platform archive
+ // so that it can be impossible to install later on.
+ return new MissingPlatformArchiveInfo(addon.getVersion());
+ }
+
+ /**
+ * Resolves platform dependencies for extras.
+ * An extra depends on having a platform with a minimun API level.
+ *
+ * We try to return the highest API level available above the specified minimum.
+ * Note that installed packages have priority so if one installed platform satisfies
+ * the dependency, we'll use it even if there's a higher API platform available but
+ * not installed yet.
+ *
+ * Finds the platform dependency. If found, add it to the list of things to install.
+ * Returns the archive info dependency, if any.
+ */
+ protected ArchiveInfo findExtraPlatformDependency(
+ ExtraPackage extra,
+ ArrayList<ArchiveInfo> outArchives,
+ Collection<Archive> selectedArchives,
+ ArrayList<Package> remotePkgs,
+ RepoSource[] remoteSources,
+ ArchiveInfo[] localArchives) {
+
+ int api = extra.getMinApiLevel();
+
+ if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) {
+ return null;
+ }
+
+ // Find a platform that would satisfy the requirement.
+
+ // First look in locally installed packages.
+ for (ArchiveInfo ai : localArchives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ // We found one already installed.
+ return ai;
+ }
+ }
+ }
+ }
+
+ // Look in archives already scheduled for install
+ int foundApi = 0;
+ ArchiveInfo foundAi = null;
+
+ for (ArchiveInfo ai : outArchives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (api > foundApi) {
+ foundApi = api;
+ foundAi = ai;
+ }
+ }
+ }
+ }
+ }
+
+ if (foundAi != null) {
+ // The dependency is already scheduled for install, nothing else to do.
+ return foundAi;
+ }
+
+ // Otherwise look in the selected archives *or* available remote packages
+ // and takes the best out of the two sets.
+ foundApi = 0;
+ Archive foundArchive = null;
+ if (selectedArchives != null) {
+ for (Archive a : selectedArchives) {
+ Package p = a.getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (api > foundApi) {
+ foundApi = api;
+ foundArchive = a;
+ }
+ }
+ }
+ }
+ }
+
+ // Finally nothing matched, so let's look at all available remote packages
+ fetchRemotePackages(remotePkgs, remoteSources);
+ for (Package p : remotePkgs) {
+ if (p instanceof PlatformPackage) {
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
+ if (api > foundApi) {
+ // It's not already in the list of things to install, so add the
+ // first compatible archive we can find.
+ for (Archive a : p.getArchives()) {
+ if (a.isCompatible()) {
+ foundApi = api;
+ foundArchive = a;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (foundArchive != null) {
+ // It's not already in the list of things to install, so add it now
+ return insertArchive(foundArchive,
+ outArchives,
+ selectedArchives,
+ remotePkgs,
+ remoteSources,
+ localArchives,
+ true /*automated*/);
+ }
+
+ // We end up here if nothing matches. We don't have a good platform to match.
+ // We need to indicate this extra depends on a missing platform archive
+ // so that it can be impossible to install later on.
+ return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
}
/** Fetch all remote packages only if really needed. */
@@ -487,4 +713,117 @@ class UpdaterLogic {
}
}
+
+ /**
+ * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed
+ * "local" package/archive.
+ * <p/>
+ * In this case, the "new Archive" is still expected to be non null and the
+ * "replaced Archive" isnull. Installed archives are always accepted and never
+ * rejected.
+ * <p/>
+ * Dependencies are not set.
+ */
+ private static class LocalArchiveInfo extends ArchiveInfo {
+
+ public LocalArchiveInfo(Archive localArchive) {
+ super(localArchive, null /*replaced*/, null /*dependsOn*/);
+ }
+
+ /** Installed archives are always accepted. */
+ @Override
+ public boolean isAccepted() {
+ return true;
+ }
+
+ /** Installed archives are never rejected. */
+ @Override
+ public boolean isRejected() {
+ return false;
+ }
+ }
+
+ /**
+ * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a
+ * package/archive that we <em>really</em> need as a dependency but that we don't have.
+ * <p/>
+ * This is currently used for addons and extras in case we can't find a matching base platform.
+ * <p/>
+ * This kind of archive has specific properties: the new archive to install is null,
+ * there are no dependencies and no archive is being replaced. The info can never be
+ * accepted and is always rejected.
+ */
+ private static class MissingPlatformArchiveInfo extends ArchiveInfo {
+
+ private final AndroidVersion mVersion;
+
+ /**
+ * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
+ * given platform version is missing.
+ */
+ public MissingPlatformArchiveInfo(AndroidVersion version) {
+ super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
+ mVersion = version;
+ }
+
+ /** Missing archives are never accepted. */
+ @Override
+ public boolean isAccepted() {
+ return false;
+ }
+
+ /** Missing archives are always rejected. */
+ @Override
+ public boolean isRejected() {
+ return true;
+ }
+
+ @Override
+ public String getShortDescription() {
+ return String.format("Missing SDK Platform Android%1$s, API %2$d",
+ mVersion.isPreview() ? " Preview" : "",
+ mVersion.getApiLevel());
+ }
+ }
+
+ /**
+ * A {@link MissingToolArchiveInfo} is an {@link ArchiveInfo} that represents a
+ * package/archive that we <em>really</em> need as a dependency but that we don't have.
+ * <p/>
+ * This is currently used for extras in case we can't find a matching tool revision.
+ * <p/>
+ * This kind of archive has specific properties: the new archive to install is null,
+ * there are no dependencies and no archive is being replaced. The info can never be
+ * accepted and is always rejected.
+ */
+ private static class MissingToolArchiveInfo extends ArchiveInfo {
+
+ private final int mRevision;
+
+ /**
+ * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
+ * given platform version is missing.
+ */
+ public MissingToolArchiveInfo(int revision) {
+ super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
+ mRevision = revision;
+ }
+
+ /** Missing archives are never accepted. */
+ @Override
+ public boolean isAccepted() {
+ return false;
+ }
+
+ /** Missing archives are always rejected. */
+ @Override
+ public boolean isRejected() {
+ return true;
+ }
+
+ @Override
+ public String getShortDescription() {
+ return String.format("Missing Android SDK Tools, revision %1$d", mRevision);
+ }
+ }
}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java
index 3e22b35..4c86c3e 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java
@@ -62,9 +62,17 @@ public class UpdaterLogicTest extends TestCase {
ArrayList<Package> remote = new ArrayList<Package>();
// a2 depends on p2, which is not in the locals
- Package[] locals = { p1, a1 };
+ Package[] localPkgs = { p1, a1 };
+ ArchiveInfo[] locals = mul.createLocalArchives(localPkgs);
+
RepoSource[] sources = null;
- assertNull(mul.findPlatformDependency(a2, out, selected, remote, sources, locals));
+
+ // a2 now depends on a "fake" archive info with no newArchive that wraps the missing
+ // underlying platform
+ ArchiveInfo fai = mul.findPlatformDependency(a2, out, selected, remote, sources, locals);
+ assertNotNull(fai);
+ assertNull(fai.getNewArchive());
+ assertTrue(fai.isRejected());
assertEquals(0, out.size());
// p2 is now selected, and should be scheduled for install in out
@@ -90,9 +98,17 @@ public class UpdaterLogicTest extends TestCase {
ArrayList<Package> remote = new ArrayList<Package>();
// p2 depends on t2, which is not locally installed
- Package[] locals = { t1 };
+ Package[] localPkgs = { t1 };
+ ArchiveInfo[] locals = mul.createLocalArchives(localPkgs);
+
RepoSource[] sources = null;
- assertNull(mul.findToolsDependency(p2, out, selected, remote, sources, locals));
+
+ // p2 now depends on a "fake" archive info with no newArchive that wraps the missing
+ // underlying tool
+ ArchiveInfo fai = mul.findToolsDependency(p2, out, selected, remote, sources, locals);
+ assertNotNull(fai);
+ assertNull(fai.getNewArchive());
+ assertTrue(fai.isRejected());
assertEquals(0, out.size());
// t2 is now selected and can be used as a dependency