diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
index ef62f6e8c..13c3ea19a 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
@@ -40,13 +40,14 @@ import java.util.Properties;
public class AndroidVersion {
private static final String PROP_API_LEVEL = "AndroidVersion.ApiLevel"; //$NON-NLS-1$
- private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
+ private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
private final int mApiLevel;
private final String mCodename;
/**
* Creates an {@link AndroidVersion} with the given api level and codename.
+ * Codename should be null for a release version, otherwise it's a preview codename.
*/
public AndroidVersion(int apiLevel, String codename) {
mApiLevel = apiLevel;
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
index b28019fab..2c9b3fbe8 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
@@ -89,6 +89,8 @@ public class AddonPackage extends Package {
* {@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.
+ *
+ * By design, this creates a package with one and only one archive.
*/
AddonPackage(IAndroidTarget target, Properties props) {
super( null, //source
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index 75879b84d..e54b5506b 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -56,6 +56,8 @@ public class DocPackage extends Package {
* 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.
+ *
+ * By design, this creates a package with one and only one archive.
*/
DocPackage(RepoSource source,
Properties props,
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
index 74e1c5933..587be1d09 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
@@ -72,6 +72,8 @@ public class ExtraPackage extends Package {
* 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.
+ *
+ * By design, this creates a package with one and only one archive.
*/
ExtraPackage(RepoSource source,
Properties props,
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index 69d526b1d..657bb143a 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -96,8 +96,10 @@ public abstract class Package implements IDescription {
* 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.
- *
+ *
* Properties from props are used first when possible, e.g. if props is non null.
+ *
+ * By design, this creates a package with one and only one archive.
*/
public Package(
RepoSource source,
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index e95656a21..e3fb3f293 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -35,8 +35,8 @@ import java.util.Properties;
*/
public class PlatformPackage extends Package {
- private static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$
- private static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
+ protected static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$
+ protected static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
@@ -80,6 +80,8 @@ public class PlatformPackage extends Package {
* 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.
+ *
+ * By design, this creates a package with one and only one archive.
*/
PlatformPackage(IAndroidTarget target, Properties props) {
super( null, //source
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index ee133793e..974d8ac60 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -45,6 +45,8 @@ public class ToolPackage extends Package {
* 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.
+ *
+ * By design, this creates a package with one and only one archive.
*/
ToolPackage(
RepoSource source,
diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockAddonPackage.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockAddonPackage.java
new file mode 100755
index 000000000..4d4199f4e
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockAddonPackage.java
@@ -0,0 +1,136 @@
+/*
+ * 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.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+
+/**
+ * A mock {@link AddonPackage} for testing.
+ *
+ * By design, this package contains one and only one archive.
+ */
+public class MockAddonPackage extends AddonPackage {
+
+ /**
+ * Creates a {@link MockAddonTarget} with the requested base platform and addon revision
+ * and then a {@link MockAddonPackage} wrapping it.
+ *
+ * By design, this package contains one and only one archive.
+ */
+ public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {
+ super(new MockAddonTarget(basePlatform.getTarget(), revision), null /*props*/);
+ }
+
+ /**
+ * A mock AddonTarget.
+ * This reimplements the minimum needed from the interface for our limited testing needs.
+ */
+ static class MockAddonTarget implements IAndroidTarget {
+
+ private final IAndroidTarget mParentTarget;
+ private final int mRevision;
+
+ public MockAddonTarget(IAndroidTarget parentTarget, int revision) {
+ mParentTarget = parentTarget;
+ mRevision = revision;
+ }
+
+ public String getClasspathName() {
+ return null;
+ }
+
+ public String getDefaultSkin() {
+ return null;
+ }
+
+ public String getDescription() {
+ return "mock addon target";
+ }
+
+ public String getFullName() {
+ return "mock addon target";
+ }
+
+ public String getLocation() {
+ return "";
+ }
+
+ public String getName() {
+ return "mock addon target";
+ }
+
+ public IOptionalLibrary[] getOptionalLibraries() {
+ return null;
+ }
+
+ public IAndroidTarget getParent() {
+ return mParentTarget;
+ }
+
+ public String getPath(int pathId) {
+ return null;
+ }
+
+ public String[] getPlatformLibraries() {
+ return null;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ public String[] getSkins() {
+ return null;
+ }
+
+ public int getUsbVendorId() {
+ return 0;
+ }
+
+ public String getVendor() {
+ return null;
+ }
+
+ public AndroidVersion getVersion() {
+ return mParentTarget.getVersion();
+ }
+
+ public String getVersionName() {
+ return String.format("mock-addon-%1$d", getVersion().getApiLevel());
+ }
+
+ public String hashString() {
+ return getVersionName();
+ }
+
+ /** Returns false for an addon. */
+ public boolean isPlatform() {
+ return false;
+ }
+
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ public int compareTo(IAndroidTarget o) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ }
+
+}
diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformPackage.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformPackage.java
new file mode 100755
index 000000000..b840b82bc
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformPackage.java
@@ -0,0 +1,169 @@
+/*
+ * 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.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+
+import java.util.Properties;
+
+/**
+ * A mock {@link PlatformPackage} for testing.
+ *
+ * By design, this package contains one and only one archive.
+ */
+public class MockPlatformPackage extends PlatformPackage {
+
+ private final IAndroidTarget mTarget;
+
+ /**
+ * Creates a {@link MockPlatformTarget} with the requested API and revision
+ * and then a {@link MockPlatformPackage} wrapping it.
+ *
+ * By design, this package contains one and only one archive.
+ */
+ public MockPlatformPackage(int apiLevel, int revision) {
+ this(new MockPlatformTarget(apiLevel, revision), null /*props*/);
+ }
+
+ /**
+ * Creates a {@link MockPlatformTarget} with the requested API and revision
+ * and then a {@link MockPlatformPackage} wrapping it.
+ *
+ * Also sets the min-tools-rev of the platform.
+ *
+ * By design, this package contains one and only one archive.
+ */
+ public MockPlatformPackage(int apiLevel, int revision, int min_tools_rev) {
+ this(new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));
+ }
+
+ /** A little trick to be able to capture the target new after passing it to the super. */
+ private MockPlatformPackage(IAndroidTarget target, Properties props) {
+ super(target, props);
+ mTarget = target;
+ }
+
+ private static Properties createProps(int min_tools_rev) {
+ Properties props = new Properties();
+ props.setProperty(PlatformPackage.PROP_MIN_TOOLS_REV, Integer.toString((min_tools_rev)));
+ return props;
+ }
+
+ public IAndroidTarget getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * A mock PlatformTarget.
+ * This reimplements the minimum needed from the interface for our limited testing needs.
+ */
+ static class MockPlatformTarget implements IAndroidTarget {
+
+ private final int mApiLevel;
+ private final int mRevision;
+
+ public MockPlatformTarget(int apiLevel, int revision) {
+ mApiLevel = apiLevel;
+ mRevision = revision;
+
+ }
+
+ public String getClasspathName() {
+ return null;
+ }
+
+ public String getDefaultSkin() {
+ return null;
+ }
+
+ public String getDescription() {
+ return "mock platform target";
+ }
+
+ public String getFullName() {
+ return "mock platform target";
+ }
+
+ public String getLocation() {
+ return "";
+ }
+
+ public String getName() {
+ return "mock platform target";
+ }
+
+ public IOptionalLibrary[] getOptionalLibraries() {
+ return null;
+ }
+
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ public String getPath(int pathId) {
+ return null;
+ }
+
+ public String[] getPlatformLibraries() {
+ return null;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ public String[] getSkins() {
+ return null;
+ }
+
+ public int getUsbVendorId() {
+ return 0;
+ }
+
+ public String getVendor() {
+ return null;
+ }
+
+ public AndroidVersion getVersion() {
+ return new AndroidVersion(mApiLevel, null /*codename*/);
+ }
+
+ public String getVersionName() {
+ return String.format("android-%1$d", mApiLevel);
+ }
+
+ public String hashString() {
+ return getVersionName();
+ }
+
+ /** Returns true for a platform. */
+ public boolean isPlatform() {
+ return true;
+ }
+
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ public int compareTo(IAndroidTarget o) {
+ throw new UnsupportedOperationException("Implement this as needed for tests");
+ }
+
+ }
+
+}
diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockToolPackage.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockToolPackage.java
new file mode 100755
index 000000000..e1ce621e5
--- /dev/null
+++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockToolPackage.java
@@ -0,0 +1,48 @@
+/*
+ * 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.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+/**
+ * A mock {@link ToolPackage} for testing.
+ *
+ * By design, this package contains one and only one archive.
+ */
+public class MockToolPackage extends ToolPackage {
+
+ /**
+ * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ *
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockToolPackage(int revision) {
+ super(
+ null, // source,
+ null, // props,
+ revision,
+ null, // license,
+ "desc", // description,
+ "url", // descUrl,
+ Os.getCurrentOs(), // archiveOs,
+ Arch.getCurrentArch(), // archiveArch,
+ "foo" // archiveOsPath
+ );
+ }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/.classpath b/tools/sdkmanager/libs/sdkuilib/.classpath
index 5a1790fc2..73592c49f 100644
--- a/tools/sdkmanager/libs/sdkuilib/.classpath
+++ b/tools/sdkmanager/libs/sdkuilib/.classpath
@@ -1,9 +1,11 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java
new file mode 100755
index 000000000..f60316946
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ArchiveInfo.java
@@ -0,0 +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.sdkuilib.internal.repository;
+
+import com.android.sdklib.internal.repository.Archive;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Represents an archive that we want to install.
+ * Note that the installer deals with archives whereas the user mostly sees packages
+ * but as far as we are concerned for installation there's a 1-to-1 mapping.
+ *
+ * A new archive is always a remote archive that needs to be downloaded and then
+ * 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.
+ *
+ * @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo)
+ */
+class ArchiveInfo {
+
+ private final Archive mNewArchive;
+ private final Archive mReplaced;
+ private final ArchiveInfo mDependsOn;
+ private final ArrayList mDependencyFor = new ArrayList();
+ private boolean mAccepted;
+ private boolean mRejected;
+
+ /**
+ *
+ * @param newArchive A "new archive" to be installed. This is always an archive
+ * that comes from a remote site. This can not 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
+ * this 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.
+ */
+ public ArchiveInfo(Archive newArchive, Archive replaced, ArchiveInfo dependsOn) {
+ mNewArchive = newArchive;
+ mReplaced = replaced;
+ mDependsOn = dependsOn;
+ }
+
+ /**
+ * Returns the "new archive" to be installed.
+ * This is always an archive that comes from a remote site.
+ */
+ 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 an optional new or local dependency, that is an archive that this
+ * 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.
+ */
+ public ArchiveInfo getDependsOn() {
+ return mDependsOn;
+ }
+
+ /**
+ * Returns true if this new archive is a dependency for another one that we
+ * want to install.
+ */
+ public boolean isDependencyFor() {
+ return mDependencyFor.size() > 0;
+ }
+
+ /**
+ * Set to true if this new archive is a dependency for another one that we
+ * want to install.
+ */
+ public void addDependencyFor(ArchiveInfo dependencyFor) {
+ mDependencyFor.add(dependencyFor);
+ }
+
+ public Collection getDependenciesFor() {
+ return mDependencyFor;
+ }
+
+ /**
+ * Sets whether this archive was accepted (either manually by the user or
+ * automatically if it doesn't have a license) for installation.
+ */
+ public void setAccepted(boolean accepted) {
+ mAccepted = accepted;
+ }
+
+ /**
+ * Returns whether this archive was accepted (either manually by the user or
+ * automatically if it doesn't have a license) for installation.
+ */
+ public boolean isAccepted() {
+ return mAccepted;
+ }
+
+ /**
+ * Sets whether this archive was rejected manually by the user.
+ * An archive can neither accepted nor rejected.
+ */
+ public void setRejected(boolean rejected) {
+ mRejected = rejected;
+ }
+
+ /**
+ * Returns whether this archive was rejected manually by the user.
+ * An archive can neither accepted nor rejected.
+ */
+ public boolean isRejected() {
+ return mRejected;
+ }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
index d7d3a9024..5b8da837e 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
@@ -52,11 +52,6 @@ import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.TreeMap;
/**
@@ -64,21 +59,16 @@ import java.util.TreeMap;
*/
final class UpdateChooserDialog extends Dialog {
- /**
- * Min Y location for dialog. Need to deal with the menu bar on mac os.
- */
- private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ?
- 20 : 0;
+ /** Min Y location for dialog. Need to deal with the menu bar on mac os. */
+ private final static int MIN_Y =
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 20 : 0;
/** Last dialog size for this session. */
private static Point sLastSize;
+ private boolean mCancelled = true;
private boolean mCompleted;
- private final Map mNewToOldArchiveMap;
private boolean mLicenseAcceptAll;
private boolean mInternalLicenseRadioUpdate;
- private HashSet mAccepted = new HashSet();
- private HashSet mRejected = new HashSet();
- private ArrayList mResult = new ArrayList();
// UI fields
private Shell mDialogShell;
@@ -96,40 +86,56 @@ final class UpdateChooserDialog extends Dialog {
private Group mPackageTextGroup;
private final UpdaterData mUpdaterData;
private Group mTableGroup;
+ private Label mErrorLabel;
+
+ /**
+ * List of all archives to be installed with dependency information.
+ *
+ * Note: in a lot of cases, we need to find the archive info for a given archive. This
+ * is currently done using a simple linear search, which is fine since we only have a very
+ * limited number of archives to deal with (e.g. < 10 now). We might want to revisit
+ * this later if it becomes an issue. Right now just do the simple thing.
+ *
+ * Typically we could add a map Archive=>ArchiveInfo later.
+ */
+ private final ArrayList mArchives;
+
/**
* Create the dialog.
* @param parentShell The shell to use, typically updaterData.getWindowShell()
* @param updaterData The updater data
- * @param newToOldUpdates The map [new archive => old archive] of potential updates
+ * @param archives The archives to be installed
*/
public UpdateChooserDialog(Shell parentShell,
UpdaterData updaterData,
- Map newToOldUpdates) {
+ ArrayList archives) {
super(parentShell,
SWT.APPLICATION_MODAL);
mUpdaterData = updaterData;
-
- mNewToOldArchiveMap = new TreeMap(new Comparator() {
- public int compare(Archive a1, Archive a2) {
- // The items are archive but what we show are packages so we'll
- // sort of packages short descriptions
- String desc1 = a1.getParentPackage().getShortDescription();
- String desc2 = a2.getParentPackage().getShortDescription();
- return desc1.compareTo(desc2);
- }
- });
- mNewToOldArchiveMap.putAll(newToOldUpdates);
+ mArchives = archives;
}
/**
* Returns the results, i.e. the list of selected new archives to install.
- * The list is always non null. It is empty when cancel is selected or when
- * all potential updates have been refused.
+ * This is similar to the {@link ArchiveInfo} list instance given to the constructor
+ * except only accepted archives are present.
+ *
+ * An empty list is returned if cancel was choosen.
*/
- public Collection getResult() {
- return mResult;
+ public ArrayList getResult() {
+ ArrayList ais = new ArrayList();
+
+ if (!mCancelled) {
+ for (ArchiveInfo ai : mArchives) {
+ if (ai.isAccepted()) {
+ ais.add(ai);
+ }
+ }
+ }
+
+ return ais;
}
/**
@@ -249,7 +255,14 @@ final class UpdateChooserDialog extends Dialog {
mSashForm.setWeights(new int[] {200, 300});
- // Bottom buttons
+ // Error message area
+
+ mErrorLabel = new Label(mDialogShell, SWT.NONE);
+ mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
+
+
+ // Bottom buttons area
+
placeholder = new Label(mDialogShell, SWT.NONE);
placeholder.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1));
@@ -293,24 +306,19 @@ final class UpdateChooserDialog extends Dialog {
private void postCreate() {
setWindowImage();
- // Automatically accept those with an empty license
- for (Archive a : mNewToOldArchiveMap.keySet()) {
+ // 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();
- if (license != null) {
- license = license.trim();
- if (license.length() == 0) {
- mAccepted.add(a);
- }
- } else {
- mAccepted.add(a);
- }
+ ai.setAccepted(license == null || license.trim().length() == 0);
}
// Fill the list with the replacement packages
mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());
mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
- mTableViewPackage.setInput(mNewToOldArchiveMap);
+ mTableViewPackage.setInput(mArchives);
adjustColumnsWidth();
@@ -403,12 +411,10 @@ final class UpdateChooserDialog extends Dialog {
}
/**
- * Callback invoked when the Install button is selected. Fills {@link #mResult} and
- * completes the dialog.
+ * Callback invoked when the Install button is selected. Completes the dialog.
*/
private void onInstallSelected() {
- // get list of accepted items
- mResult.addAll(mAccepted);
+ mCancelled = false;
mCompleted = true;
}
@@ -416,6 +422,7 @@ final class UpdateChooserDialog extends Dialog {
* Callback invoked when the Cancel button is selected.
*/
private void onCancelSelected() {
+ mCancelled = true;
mCompleted = true;
}
@@ -423,49 +430,123 @@ final class UpdateChooserDialog extends Dialog {
* Callback invoked when a package item is selected in the list.
*/
private void onPackageSelected() {
- Archive a = getSelectedArchive();
- displayInformation(a);
- updateLicenceRadios(a);
+ ArchiveInfo ai = getSelectedArchive();
+ displayInformation(ai);
+ displayMissingDependency(ai);
+ updateLicenceRadios(ai);
}
- /** Returns the currently selected Archive or null. */
- private Archive getSelectedArchive() {
+ /** Returns the currently selected {@link ArchiveInfo} or null. */
+ private ArchiveInfo getSelectedArchive() {
ISelection sel = mTableViewPackage.getSelection();
if (sel instanceof IStructuredSelection) {
Object elem = ((IStructuredSelection) sel).getFirstElement();
- if (elem instanceof Archive) {
- return (Archive) elem;
+ if (elem instanceof ArchiveInfo) {
+ return (ArchiveInfo) elem;
}
}
return null;
}
- private void displayInformation(Archive a) {
- if (a == null) {
+ /**
+ * Updates the package description and license text depending on the selected package.
+ */
+ private void displayInformation(ArchiveInfo ai) {
+ if (ai == null) {
mPackageText.setText("Please select a package.");
return;
}
- mPackageText.setText(""); //$NON-NLS-1$
+ Archive anew = ai.getNewArchive();
+
+ mPackageText.setText(""); //$NON-NLS-1$
addSectionTitle("Package Description\n");
- addText(a.getParentPackage().getLongDescription(), "\n\n"); //$NON-NLS-1$
+ addText(anew.getParentPackage().getLongDescription(), "\n\n"); //$NON-NLS-1$
- Archive aold = mNewToOldArchiveMap.get(a);
+ Archive aold = ai.getReplaced();
if (aold != null) {
addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",
aold.getParentPackage().getRevision(),
- a.getParentPackage().getRevision()));
+ anew.getParentPackage().getRevision()));
}
+ ArchiveInfo adep = ai.getDependsOn();
+ if (adep != null || ai.isDependencyFor()) {
+ addSectionTitle("Dependencies\n");
+
+ if (adep != null) {
+ addText(String.format("This package depends on %1$s.\n\n",
+ adep.getNewArchive().getParentPackage().getShortDescription()));
+ }
+
+ if (ai.isDependencyFor()) {
+ addText("This package is a dependency for:");
+ for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
+ addText("\n- " +
+ ai2.getNewArchive().getParentPackage().getShortDescription());
+ }
+ addText("\n\n");
+ }
+ }
addSectionTitle("Archive Description\n");
- addText(a.getLongDescription(), "\n\n"); //$NON-NLS-1$
+ addText(anew.getLongDescription(), "\n\n"); //$NON-NLS-1$
- String license = a.getParentPackage().getLicense();
+ String license = anew.getParentPackage().getLicense();
if (license != null) {
addSectionTitle("License\n");
- addText(license.trim(), "\n"); //$NON-NLS-1$
+ addText(license.trim(), "\n"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Computes and display missing dependency.
+ * If there's a selected package, check the dependency for that one.
+ * Otherwise display the first missing dependency.
+ */
+ private void displayMissingDependency(ArchiveInfo ai) {
+ String error = null;
+
+ try {
+ if (ai != null) {
+
+ if (!ai.isAccepted()) {
+ // Case where this package blocks another one when not accepted
+ for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
+ // 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());
+ 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;
+ }
+ }
+ }
+
+ // If there's no selection, just find the first missing dependency of any accepted
+ // package.
+ for (ArchiveInfo ai2 : mArchives) {
+ if (ai2.isAccepted()) {
+ ArchiveInfo adep = ai.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;
+ }
+ }
+ }
+ } finally {
+ mErrorLabel.setText(error == null ? "" : error); //$NON-NLS-1$
}
}
@@ -488,25 +569,41 @@ final class UpdateChooserDialog extends Dialog {
mPackageText.setStyleRange(sr);
}
- private void updateLicenceRadios(Archive a) {
+ private void updateLicenceRadios(ArchiveInfo ai) {
if (mInternalLicenseRadioUpdate) {
return;
}
mInternalLicenseRadioUpdate = true;
+ boolean oneAccepted = false;
+
if (mLicenseAcceptAll) {
mLicenseRadioAcceptAll.setSelection(true);
+ mLicenseRadioAccept.setEnabled(true);
+ mLicenseRadioReject.setEnabled(true);
mLicenseRadioAccept.setSelection(false);
mLicenseRadioReject.setSelection(false);
} else {
mLicenseRadioAcceptAll.setSelection(false);
- mLicenseRadioAccept.setSelection(mAccepted.contains(a));
- mLicenseRadioReject.setSelection(mRejected.contains(a));
+ oneAccepted = ai != null && ai.isAccepted();
+ mLicenseRadioAccept.setEnabled(ai != null);
+ mLicenseRadioReject.setEnabled(ai != null);
+ mLicenseRadioAccept.setSelection(oneAccepted);
+ mLicenseRadioReject.setSelection(ai != null && ai.isRejected());
}
- // The install button is enabled if there's at least one
- // package accepted.
- mInstallButton.setEnabled(mAccepted.size() > 0);
+ // The install button is enabled if there's at least one package accepted.
+ // If the current one isn't, look for another one.
+ boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;
+ if (!missing && !oneAccepted) {
+ for(ArchiveInfo ai2 : mArchives) {
+ if (ai2.isAccepted()) {
+ oneAccepted = true;
+ break;
+ }
+ }
+ }
+ mInstallButton.setEnabled(!missing && oneAccepted);
mInternalLicenseRadioUpdate = false;
}
@@ -523,26 +620,28 @@ final class UpdateChooserDialog extends Dialog {
}
mInternalLicenseRadioUpdate = true;
- Archive a = getSelectedArchive();
+ ArchiveInfo ai = getSelectedArchive();
boolean needUpdate = true;
if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
// Accept all has been switched on. Mark all packages as accepted
mLicenseAcceptAll = true;
- mAccepted.addAll(mNewToOldArchiveMap.keySet());
- mRejected.clear();
+ for(ArchiveInfo ai2 : mArchives) {
+ ai2.setAccepted(true);
+ ai2.setRejected(false);
+ }
} else if (mLicenseRadioAccept.getSelection()) {
// Accept only this one
mLicenseAcceptAll = false;
- mAccepted.add(a);
- mRejected.remove(a);
+ ai.setAccepted(true);
+ ai.setRejected(false);
} else if (mLicenseRadioReject.getSelection()) {
// Reject only this one
mLicenseAcceptAll = false;
- mAccepted.remove(a);
- mRejected.add(a);
+ ai.setAccepted(false);
+ ai.setRejected(true);
} else {
needUpdate = false;
@@ -554,9 +653,10 @@ final class UpdateChooserDialog extends Dialog {
if (mLicenseAcceptAll) {
mTableViewPackage.refresh();
} else {
- mTableViewPackage.refresh(a);
+ mTableViewPackage.refresh(ai);
}
- updateLicenceRadios(a);
+ displayMissingDependency(ai);
+ updateLicenceRadios(ai);
}
}
@@ -564,32 +664,29 @@ final class UpdateChooserDialog extends Dialog {
* Callback invoked when a package item is double-clicked in the list.
*/
private void onPackageDoubleClick() {
- Archive a = getSelectedArchive();
+ ArchiveInfo ai = getSelectedArchive();
- if (mAccepted.contains(a)) {
- // toggle from accepted to rejected
- mAccepted.remove(a);
- mRejected.add(a);
- } else {
- // toggle from rejected or unknown to accepted
- mAccepted.add(a);
- mRejected.remove(a);
- }
+ boolean wasAccepted = ai.isAccepted();
+ ai.setAccepted(!wasAccepted);
+ ai.setRejected(wasAccepted);
// update state
mLicenseAcceptAll = false;
- mTableViewPackage.refresh(a);
- updateLicenceRadios(a);
+ mTableViewPackage.refresh(ai);
+ updateLicenceRadios(ai);
}
private class NewArchivesLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
+ assert element instanceof ArchiveInfo;
+ ArchiveInfo ai = (ArchiveInfo) element;
+
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
- if (mAccepted.contains(element)) {
+ if (ai.isAccepted()) {
return imgFactory.getImageByName("accept_icon16.png");
- } else if (mRejected.contains(element)) {
+ } else if (ai.isRejected()) {
return imgFactory.getImageByName("reject_icon16.png");
}
return imgFactory.getImageByName("unknown_icon16.png");
@@ -599,10 +696,16 @@ final class UpdateChooserDialog extends Dialog {
@Override
public String getText(Object element) {
- if (element instanceof Archive) {
- return ((Archive) element).getParentPackage().getShortDescription();
+ assert element instanceof ArchiveInfo;
+ ArchiveInfo ai = (ArchiveInfo) element;
+
+ String desc = ai.getNewArchive().getParentPackage().getShortDescription();
+
+ if (ai.isDependencyFor()) {
+ desc += " [*]";
}
- return super.getText(element);
+
+ return desc;
}
}
@@ -613,11 +716,11 @@ final class UpdateChooserDialog extends Dialog {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- // Ignore. The input is always mNewArchives
+ // Ignore. The input is always mArchives
}
public Object[] getElements(Object inputElement) {
- return mNewToOldArchiveMap.keySet().toArray();
+ return mArchives.toArray();
}
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index bec00f8c4..184142160 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -30,7 +30,6 @@ import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdklib.internal.repository.ToolPackage;
-import com.android.sdklib.internal.repository.Package.UpdateInfo;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
@@ -42,8 +41,7 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
/**
* Data shared between {@link UpdaterWindowImpl} and its pages.
@@ -268,9 +266,9 @@ class UpdaterData {
* Install the list of given {@link Archive}s. This is invoked by the user selecting some
* packages in the remote page and then clicking "install selected".
*
- * @param archives The archives to install. Incompatible ones will be skipped.
+ * @param result The archives to install. Incompatible ones will be skipped.
*/
- public void installArchives(final Collection archives) {
+ public void installArchives(final ArrayList result) {
if (mTaskFactory == null) {
throw new IllegalArgumentException("Task Factory is null");
}
@@ -281,14 +279,23 @@ class UpdaterData {
public void run(ITaskMonitor monitor) {
final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
- monitor.setProgressMax(archives.size() * progressPerArchive);
+ monitor.setProgressMax(result.size() * progressPerArchive);
monitor.setDescription("Preparing to install archives");
boolean installedAddon = false;
boolean installedTools = false;
+ // Mark all current local archives as already installed.
+ HashSet installedArchives = new HashSet();
+ for (Package p : getInstalledPackage()) {
+ for (Archive a : p.getArchives()) {
+ installedArchives.add(a);
+ }
+ }
+
int numInstalled = 0;
- for (Archive archive : archives) {
+ for (ArchiveInfo ai : result) {
+ Archive archive = ai.getNewArchive();
int nextProgress = monitor.getProgress() + progressPerArchive;
try {
@@ -296,9 +303,24 @@ class UpdaterData {
break;
}
+ ArchiveInfo adep = ai.getDependsOn();
+ if (adep != null && !installedArchives.contains(adep)) {
+ // 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());
+ }
+
if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
+ // We installed this archive.
+ installedArchives.add(archive);
numInstalled++;
+ // If this package was replacing an existing one, the old one
+ // is no longer installed.
+ installedArchives.remove(ai.getReplaced());
+
// Check if we successfully installed a tool or add-on package.
if (archive.getParentPackage() instanceof AddonPackage) {
installedAddon = true;
@@ -435,28 +457,20 @@ class UpdaterData {
refreshSources(true);
}
- final Map updates = findUpdates(selectedArchives);
+ UpdaterLogic ul = new UpdaterLogic();
+ ArrayList archives = ul.computeUpdates(
+ selectedArchives,
+ getSources(),
+ getLocalSdkParser().getPackages());
- if (selectedArchives != null) {
- // Not only we want to perform updates but we also want to install the
- // selected archives. If they do not match an update, list them anyway
- // except they map themselves to null (no "old" archive)
- for (Archive a : selectedArchives) {
- if (!updates.containsKey(a)) {
- updates.put(a, null);
- }
- }
- }
-
- UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, updates);
+ UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, archives);
dialog.open();
- Collection result = dialog.getResult();
+ ArrayList result = dialog.getResult();
if (result != null && result.size() > 0) {
installArchives(result);
}
}
-
/**
* Refresh all sources. This is invoked either internally (reusing an existing monitor)
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is
@@ -485,108 +499,4 @@ class UpdaterData {
}
});
}
-
- /**
- * Check the local archives vs the remote available packages to find potential updates.
- * Return a map [remote archive => local archive] of suitable update candidates.
- * Returns null if there's an unexpected error. Otherwise returns a map that can be
- * empty but not null.
- *
- * @param selectedArchives The list of remote archive to consider for the update.
- * This can be null, in which case a list of remote archive is fetched from all
- * available sources.
- */
- private Map findUpdates(Collection selectedArchives) {
- // Map [remote archive => local archive] of suitable update candidates
- Map result = new HashMap();
-
- // First go thru all sources and make a list of all available remote archives
- // sorted by package class.
- HashMap, ArrayList> availablePkgs =
- new HashMap, ArrayList>();
-
- if (selectedArchives != null) {
- // Only consider the archives given
-
- for (Archive a : selectedArchives) {
- // Only add compatible archives
- if (a.isCompatible()) {
- Class extends Package> clazz = a.getParentPackage().getClass();
-
- ArrayList list = availablePkgs.get(clazz);
- if (list == null) {
- availablePkgs.put(clazz, list = new ArrayList());
- }
-
- list.add(a);
- }
- }
-
- } else {
- // Get all the available archives from all loaded sources
- RepoSource[] remoteSources = getSources().getSources();
-
- for (RepoSource remoteSrc : remoteSources) {
- Package[] remotePkgs = remoteSrc.getPackages();
- if (remotePkgs != null) {
- for (Package remotePkg : remotePkgs) {
- Class extends Package> clazz = remotePkg.getClass();
-
- ArrayList list = availablePkgs.get(clazz);
- if (list == null) {
- availablePkgs.put(clazz, list = new ArrayList());
- }
-
- for (Archive a : remotePkg.getArchives()) {
- // Only add compatible archives
- if (a.isCompatible()) {
- list.add(a);
- }
- }
- }
- }
- }
- }
-
- Package[] localPkgs = getLocalSdkParser().getPackages();
- if (localPkgs == null) {
- // This is unexpected. The local sdk parser should have been called first.
- return null;
- }
-
- for (Package localPkg : localPkgs) {
- // get the available archive list for this package type
- ArrayList list = availablePkgs.get(localPkg.getClass());
-
- // if this list is empty, we'll never find anything that matches
- if (list == null || list.size() == 0) {
- continue;
- }
-
- // local packages should have one archive at most
- Archive[] localArchives = localPkg.getArchives();
- if (localArchives != null && localArchives.length > 0) {
- Archive localArchive = localArchives[0];
- // only consider archives compatible with the current platform
- if (localArchive != null && localArchive.isCompatible()) {
-
- // We checked all this archive stuff because that's what eventually gets
- // installed, but the "update" mechanism really works on packages. So now
- // the real question: is there a remote package that can update this
- // local package?
-
- for (Archive availArchive : list) {
- UpdateInfo info = localPkg.canBeUpdatedBy(availArchive.getParentPackage());
- if (info == UpdateInfo.UPDATE) {
- // Found one!
- result.put(availArchive, localArchive);
- break;
- }
- }
- }
- }
- }
-
- return result;
- }
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
new file mode 100755
index 000000000..6d9935d2a
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.AddonPackage;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.PlatformPackage;
+import com.android.sdklib.internal.repository.RepoSource;
+import com.android.sdklib.internal.repository.RepoSources;
+import com.android.sdklib.internal.repository.ToolPackage;
+import com.android.sdklib.internal.repository.Package.UpdateInfo;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+class UpdaterLogic {
+
+ private RepoSources mSources;
+
+ public ArrayList computeUpdates(
+ Collection selectedArchives,
+ RepoSources sources,
+ Package[] localPkgs) {
+
+ mSources = sources;
+ ArrayList archives = new ArrayList();
+ ArrayList remotePkgs = new ArrayList();
+
+ if (selectedArchives == null) {
+ selectedArchives = findUpdates(localPkgs, remotePkgs);
+ }
+
+ for (Archive a : selectedArchives) {
+ insertArchive(a, archives, selectedArchives, remotePkgs, localPkgs, false);
+ }
+
+ return archives;
+ }
+
+
+ /**
+ * Find suitable updates to all current local packages.
+ */
+ private Collection findUpdates(Package[] localPkgs, ArrayList remotePkgs) {
+ ArrayList updates = new ArrayList();
+
+ fetchRemotePackages(remotePkgs);
+
+ for (Package localPkg : localPkgs) {
+ for (Package remotePkg : remotePkgs) {
+ if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
+ // Found a suitable update. Only accept the remote package
+ // if it provides at least one compatible archive.
+
+ for (Archive a : remotePkg.getArchives()) {
+ if (a.isCompatible()) {
+ updates.add(a);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return updates;
+ }
+
+ private ArchiveInfo insertArchive(Archive archive,
+ ArrayList outArchives,
+ Collection selectedArchives,
+ ArrayList remotePkgs,
+ Package[] localPkgs,
+ 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];
+ }
+ }
+
+ // find dependencies
+ ArchiveInfo dep = findDependency(p, outArchives, selectedArchives, remotePkgs, localPkgs);
+
+ ArchiveInfo ai = new ArchiveInfo(
+ archive, //newArchive
+ updatedArchive, //replaced
+ dep //dependsOn
+ );
+
+ outArchives.add(ai);
+ if (dep != null) {
+ dep.addDependencyFor(ai);
+ }
+
+ return ai;
+ }
+
+ private ArchiveInfo findDependency(Package pkg,
+ ArrayList outArchives,
+ Collection selectedArchives,
+ ArrayList remotePkgs,
+ Package[] localPkgs) {
+
+ // Current dependencies can be:
+ // - addon: *always* depends on platform of same API level
+ // - platform: *might* depends on tools of rev >= min-tools-rev
+
+ if (pkg instanceof AddonPackage) {
+ AddonPackage addon = (AddonPackage) pkg;
+
+ return findAddonDependency(
+ addon, outArchives, selectedArchives, remotePkgs, localPkgs);
+
+ } else if (pkg instanceof PlatformPackage) {
+ PlatformPackage platform = (PlatformPackage) pkg;
+
+ return findPlatformDependency(
+ platform, outArchives, selectedArchives, remotePkgs, localPkgs);
+ }
+
+ return null;
+ }
+
+ /**
+ * A platform can have a min-tools-rev, in which case it depends on having a tools package
+ * of the requested revision.
+ * Finds the tools dependency. If found, add it to the list of things to install.
+ * Returns the archive info dependency, if any.
+ */
+ protected ArchiveInfo findPlatformDependency(PlatformPackage platform,
+ ArrayList outArchives,
+ Collection selectedArchives,
+ ArrayList remotePkgs,
+ Package[] localPkgs) {
+ // This is the requirement to match.
+ int rev = platform.getMinToolsRevision();
+
+ if (rev == PlatformPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
+ // Well actually there's no requirement.
+ 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;
+ }
+ }
+ }
+
+ // Look in archives already scheduled for install
+ for (ArchiveInfo ai : outArchives) {
+ Package p = ai.getNewArchive().getParentPackage();
+ if (p instanceof PlatformPackage) {
+ if (((ToolPackage) p).getRevision() >= rev) {
+ // The dependency is already scheduled for install, nothing else to do.
+ return ai;
+ }
+ }
+ }
+
+ // Otherwise look in the selected archives.
+ for (Archive a : selectedArchives) {
+ Package p = a.getParentPackage();
+ 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, localPkgs,
+ true);
+ }
+ }
+ }
+
+ // Finally nothing matched, so let's look at all available remote packages
+ fetchRemotePackages(remotePkgs);
+ for (Package p : remotePkgs) {
+ if (p instanceof ToolPackage) {
+ if (((ToolPackage) p).getRevision() >= rev) {
+ // 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()) {
+ return insertArchive(a, outArchives,
+ selectedArchives, remotePkgs, localPkgs,
+ true);
+ }
+ }
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ /**
+ * 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 findAddonDependency(AddonPackage addon,
+ ArrayList outArchives,
+ Collection selectedArchives,
+ ArrayList remotePkgs,
+ Package[] localPkgs) {
+ // 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;
+ }
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ // Otherwise look in the selected archives.
+ for (Archive a : selectedArchives) {
+ Package p = a.getParentPackage();
+ 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, localPkgs,
+ true);
+ }
+ }
+ }
+
+ // Finally nothing matched, so let's look at all available remote packages
+ fetchRemotePackages(remotePkgs);
+ for (Package p : remotePkgs) {
+ if (p instanceof PlatformPackage) {
+ if (v.equals(((PlatformPackage) p).getVersion())) {
+ // 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()) {
+ return insertArchive(a, outArchives,
+ selectedArchives, remotePkgs, localPkgs,
+ true);
+ }
+ }
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ /** Fetch all remote packages only if really needed. */
+ protected void fetchRemotePackages(ArrayList remotePkgs) {
+ if (remotePkgs.size() > 0) {
+ return;
+ }
+
+ // Get all the available packages from all loaded sources
+ RepoSource[] remoteSources = mSources.getSources();
+
+ for (RepoSource remoteSrc : remoteSources) {
+ Package[] pkgs = remoteSrc.getPackages();
+ if (pkgs != null) {
+ nextPackage: for (Package pkg : pkgs) {
+ for (Archive a : pkg.getArchives()) {
+ // Only add a package if it contains at least one compatible archive
+ if (a.isCompatible()) {
+ remotePkgs.add(pkg);
+ continue nextPackage;
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java b/tools/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java
new file mode 100755
index 000000000..bb1f52e99
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.MockAddonPackage;
+import com.android.sdklib.internal.repository.MockPlatformPackage;
+import com.android.sdklib.internal.repository.MockToolPackage;
+import com.android.sdklib.internal.repository.Package;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class UpdaterLogicTest extends TestCase {
+
+ private static class MockUpdaterLogic extends UpdaterLogic {
+ private final Package[] mRemotePackages;
+
+ public MockUpdaterLogic(Package[] remotePackages) {
+ mRemotePackages = remotePackages;
+ }
+
+ @Override
+ protected void fetchRemotePackages(ArrayList remotePkgs) {
+ if (mRemotePackages != null) {
+ remotePkgs.addAll(Arrays.asList(mRemotePackages));
+ }
+ }
+ }
+
+ public void testFindAddonDependency() throws Exception {
+ MockUpdaterLogic mul = new MockUpdaterLogic(null);
+
+ MockPlatformPackage p1 = new MockPlatformPackage(1, 1);
+ MockPlatformPackage p2 = new MockPlatformPackage(2, 1);
+
+ MockAddonPackage a1 = new MockAddonPackage(p1, 1);
+ MockAddonPackage a2 = new MockAddonPackage(p2, 2);
+
+ ArrayList out = new ArrayList();
+ ArrayList selected = new ArrayList();
+ ArrayList remote = new ArrayList();
+
+ // a2 depends on p2, which is not in the locals
+ Package[] locals = { p1, a1 };
+ assertNull(mul.findAddonDependency(a2, out, selected, remote, locals));
+ assertEquals(0, out.size());
+
+ // p2 is now selected, and should be scheduled for install in out
+ Archive p2_archive = p2.getArchives()[0];
+ selected.add(p2_archive);
+ ArchiveInfo ai2 = mul.findAddonDependency(a2, out, selected, remote, locals);
+ assertNotNull(ai2);
+ assertSame(p2_archive, ai2.getNewArchive());
+ assertEquals(1, out.size());
+ assertSame(p2_archive, out.get(0).getNewArchive());
+ }
+
+ public void testFindPlatformDependency() throws Exception {
+ MockUpdaterLogic mul = new MockUpdaterLogic(null);
+
+ MockToolPackage t1 = new MockToolPackage(1);
+ MockToolPackage t2 = new MockToolPackage(2);
+
+ MockPlatformPackage p2 = new MockPlatformPackage(2, 1, 2);
+
+ ArrayList out = new ArrayList();
+ ArrayList selected = new ArrayList();
+ ArrayList remote = new ArrayList();
+
+ // p2 depends on t2, which is not locally installed
+ Package[] locals = { t1 };
+ assertNull(mul.findPlatformDependency(p2, out, selected, remote, locals));
+ assertEquals(0, out.size());
+
+ // t2 is now selected and can be used as a dependency
+ Archive t2_archive = t2.getArchives()[0];
+ selected.add(t2_archive);
+ ArchiveInfo ai2 = mul.findPlatformDependency(p2, out, selected, remote, locals);
+ assertNotNull(ai2);
+ assertSame(t2_archive, ai2.getNewArchive());
+ assertEquals(1, out.size());
+ assertSame(t2_archive, out.get(0).getNewArchive());
+ }
+}