aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2011-07-13 16:48:23 -0700
committerAndroid Code Review <code-review@android.com>2011-07-13 16:48:23 -0700
commit721d33f854f3e866b05aa76b997a71ca0bcc41e2 (patch)
treec640e98b00000df8011c7d7f4165f835456ea6ac
parentcbe918621e701dec553f0a434f6f75886f154f6a (diff)
parent122ed4977a42719239e501fc522592994267bd9d (diff)
downloadsdk-721d33f854f3e866b05aa76b997a71ca0bcc41e2.zip
sdk-721d33f854f3e866b05aa76b997a71ca0bcc41e2.tar.gz
sdk-721d33f854f3e866b05aa76b997a71ca0bcc41e2.tar.bz2
Merge "SdkManager: fix issue with double-packages on reload."
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java5
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java34
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java59
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java18
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java283
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java722
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java104
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java164
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java262
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java173
10 files changed, 1201 insertions, 623 deletions
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 793c98d..ab074ce 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
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.repository;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.NullSdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
@@ -159,7 +161,8 @@ public class ExtraPackage extends MinToolsPackage
}
}
- private ExtraPackage(SdkSource source,
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected ExtraPackage(SdkSource source,
Properties props,
String vendor,
String path,
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
index 1279529..76f645a 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
@@ -31,12 +31,22 @@ public class MockAddonPackage extends AddonPackage {
/**
* Creates a {@link MockAddonTarget} with the requested base platform and addon revision
- * and then a {@link MockAddonPackage} wrapping it.
+ * and then a {@link MockAddonPackage} wrapping it and a default name of "addon".
*
* By design, this package contains one and only one archive.
*/
public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {
- super(new MockAddonTarget(basePlatform.getTarget(), revision), null /*props*/);
+ this("addon", basePlatform, revision); //$NON-NLS-1$
+ }
+
+ /**
+ * 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(String name, MockPlatformPackage basePlatform, int revision) {
+ super(new MockAddonTarget(name, basePlatform.getTarget(), revision), null /*props*/);
}
/**
@@ -47,8 +57,10 @@ public class MockAddonPackage extends AddonPackage {
private final IAndroidTarget mParentTarget;
private final int mRevision;
+ private final String mName;
- public MockAddonTarget(IAndroidTarget parentTarget, int revision) {
+ public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {
+ mName = name;
mParentTarget = parentTarget;
mRevision = revision;
}
@@ -85,10 +97,6 @@ public class MockAddonPackage extends AddonPackage {
return "";
}
- public String getName() {
- return "mock addon target";
- }
-
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
@@ -133,14 +141,18 @@ public class MockAddonPackage extends AddonPackage {
return 0;
}
- public String getVendor() {
- return null;
- }
-
public AndroidVersion getVersion() {
return mParentTarget.getVersion();
}
+ public String getName() {
+ return mName;
+ }
+
+ public String getVendor() {
+ return mParentTarget.getVendor();
+ }
+
public String getVersionName() {
return String.format("mock-addon-%1$d", getVersion().getApiLevel());
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java
new file mode 100755
index 0000000..9319c6c
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockExtraPackage.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.sdklib.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+import java.util.Properties;
+
+/**
+ * A mock {@link ToolPackage} for testing.
+ *
+ * By design, this package contains one and only one archive.
+ */
+public class MockExtraPackage extends ExtraPackage {
+
+ /**
+ * Creates a {@link MockExtraPackage} with the given revision and hardcoded defaults
+ * for everything else.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MockExtraPackage(String vendor, String path, int revision, int min_platform_tools_rev) {
+ super(
+ null, // source,
+ createProps(min_platform_tools_rev), // props,
+ vendor,
+ path,
+ revision,
+ null, // license,
+ "desc", // description,
+ "url", // descUrl,
+ Os.getCurrentOs(), // archiveOs,
+ Arch.getCurrentArch(), // archiveArch,
+ "foo" // archiveOsPath
+ );
+ }
+
+ private static Properties createProps(int min_platform_tools_rev) {
+ Properties props = new Properties();
+ props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
+ Integer.toString((min_platform_tools_rev)));
+ return props;
+ }
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
index d3c1235..fc24658 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java
@@ -117,10 +117,6 @@ public class MockPlatformPackage extends PlatformPackage {
return "";
}
- public String getName() {
- return "mock platform target";
- }
-
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
@@ -165,8 +161,20 @@ public class MockPlatformPackage extends PlatformPackage {
return 0;
}
+ /**
+ * Returns a vendor that depends on the parent *platform* API.
+ * This works well in Unit Tests where we'll typically have different
+ * platforms as unique identifiers.
+ */
public String getVendor() {
- return null;
+ return "vendor " + Integer.toString(mApiLevel);
+ }
+
+ /**
+ * Create a synthetic name using the target API level.
+ */
+ public String getName() {
+ return "platform r" + Integer.toString(mApiLevel);
}
public AndroidVersion getVersion() {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
index 6a6b423..6aaabe9 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageLoader.java
@@ -30,7 +30,6 @@ import org.eclipse.swt.widgets.Shell;
import java.io.File;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -142,16 +141,15 @@ class PackageLoader {
}
// get local packages and offer them to the callback
- final List<PkgItem> allPkgItems = loadLocalPackages();
- if (!allPkgItems.isEmpty()) {
- // Notify the callback by giving it a copy of the current list.
- // (in case the callback holds to the list... we still need this list of
- // ourselves below).
- if (!sourceLoadedCallback.onSourceLoaded(new ArrayList<PkgItem>(allPkgItems))) {
+ List<PkgItem> localPkgItems = loadLocalPackages();
+ if (!localPkgItems.isEmpty()) {
+ if (!sourceLoadedCallback.onSourceLoaded(localPkgItems)) {
return;
}
}
+ final int[] numPackages = { localPkgItems.size() };
+
// get remote packages
final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
mUpdaterData.loadRemoteAddonsList();
@@ -170,26 +168,13 @@ class PackageLoader {
}
List<PkgItem> sourcePkgItems = new ArrayList<PkgItem>();
-
- nextPkg: for(Package pkg : pkgs) {
- boolean isUpdate = false;
- for (PkgItem pi: allPkgItems) {
- if (pi.isSamePackageAs(pkg)) {
- continue nextPkg;
- }
- if (pi.isUpdatedBy(pkg)) {
- isUpdate = true;
- break;
- }
- }
-
- if (!isUpdate) {
- PkgItem pi = new PkgItem(pkg, PkgState.NEW);
- sourcePkgItems.add(pi);
- allPkgItems.add(pi);
- }
+ for(Package pkg : pkgs) {
+ PkgItem pi = new PkgItem(pkg, PkgState.NEW);
+ sourcePkgItems.add(pi);
}
+ numPackages[0] += sourcePkgItems.size();
+
// Notify the callback a new source has finished loading.
// If the callback requests so, stop right away.
if (!sourceLoadedCallback.onSourceLoaded(sourcePkgItems)) {
@@ -200,7 +185,7 @@ class PackageLoader {
monitor.logError("Loading source failed: %1$s", e.toString());
} finally {
monitor.setDescription("Done loading %1$d packages from %2$d sources",
- allPkgItems.size(),
+ numPackages[0],
sources.length);
}
}
@@ -265,23 +250,18 @@ class PackageLoader {
Package acceptedPkg = null;
switch(item.getState()) {
case NEW:
- if (installTask.acceptPackage(item.getPackage())) {
- acceptedPkg = item.getPackage();
+ if (installTask.acceptPackage(item.getMainPackage())) {
+ acceptedPkg = item.getMainPackage();
}
- break;
- case HAS_UPDATE:
- for (Package upd : item.getUpdatePkgs()) {
- if (installTask.acceptPackage(upd)) {
- acceptedPkg = upd;
- break;
- }
+ if (item.hasUpdatePkg() && installTask.acceptPackage(item.getUpdatePkg())) {
+ acceptedPkg = item.getUpdatePkg();
}
break;
case INSTALLED:
- if (installTask.acceptPackage(item.getPackage())) {
+ if (installTask.acceptPackage(item.getMainPackage())) {
// If the caller is accepting an installed package,
// return a success and give the package's install path
- acceptedPkg = item.getPackage();
+ acceptedPkg = item.getMainPackage();
Archive[] a = acceptedPkg.getArchives();
// an installed package should have one local compatible archive
if (a.length == 1 && a[0].isCompatible()) {
@@ -366,7 +346,7 @@ class PackageLoader {
// Try to locate the installed package in the new package list
for (PkgItem localItem : localPkgItems) {
- Package localPkg = localItem.getPackage();
+ Package localPkg = localItem.getMainPackage();
if (localPkg.canBeUpdatedBy(packageToInstall) == UpdateInfo.NOT_UPDATE) {
Archive[] localArchive = localPkg.getArchives();
if (localArchive.length == 1 && localArchive[0].isCompatible()) {
@@ -397,117 +377,65 @@ class PackageLoader {
*/
public enum PkgState {
/**
- * Package is locally installed and has no update available on remote sites.
+ * Package is locally installed and may or may not have an update.
*/
INSTALLED,
/**
- * Package is installed and has an update available.
- * In this case, {@link PkgItem#getUpdatePkgs()} provides the list of 1 or more
- * packages that can update this {@link PkgItem}.
- * <p/>
- * Although not structurally enforced, it can be reasonably expected that
- * the original package and the updating packages all come from the same source.
- */
- HAS_UPDATE,
-
- /**
- * There's a new package available on the remote site that isn't
- * installed locally.
+ * There's a new package available on the remote site that isn't installed locally.
*/
NEW
}
/**
- * A {@link PkgItem} represents one {@link Package} combined with its state.
+ * A {@link PkgItem} represents one main {@link Package} combined with its state
+ * and an optional update package.
* <p/>
- * It can be either a locally installed package, or a remotely available package.
- * If the later, it can be either a new package or an update for a locally installed one.
- * <p/>
- * In the case of an update, the {@link PkgItem#getPackage()} represents the currently
- * installed package and there's a separate list of {@link PkgItem#getUpdatePkgs()} that
- * links to the updating packages. Note that in a typical repository there should only
- * one update for a given installed package however the system is designed to be more
- * generic and allow many.
+ * The main package is final and cannot change since it's what "defines" this PkgItem.
+ * The state or update package can change later.
*/
public static class PkgItem implements Comparable<PkgItem> {
- private final Package mPkg;
private PkgState mState;
- private List<Package> mUpdatePkgs;
+ private final Package mMainPkg;
+ private Package mUpdatePkg;
- public PkgItem(Package pkg, PkgState state) {
- mPkg = pkg;
+ /**
+ * Create a new {@link PkgItem} for this main package.
+ * The main package is final and cannot change since it's what "defines" this PkgItem.
+ * The state or update package can change later.
+ */
+ public PkgItem(Package mainPkg, PkgState state) {
+ mMainPkg = mainPkg;
mState = state;
- assert mPkg != null;
+ assert mMainPkg != null;
}
public boolean isObsolete() {
- return mPkg.isObsolete();
- }
-
- public boolean isSameItemAs(PkgItem item) {
- boolean same = this.mState == item.mState;
- if (same) {
- same = isSamePackageAs(item.getPackage());
- }
- // check updating packages are the same
- if (same) {
- List<Package> u1 = getUpdatePkgs();
- List<Package> u2 = item.getUpdatePkgs();
- same = (u1 == null && u2 == null) ||
- (u1 != null && u2 != null);
- if (same && u1 != null && u2 != null) {
- int n = u1.size();
- same = n == u2.size();
- if (same) {
- for (int i = 0; same && i < n; i++) {
- Package p1 = u1.get(i);
- Package p2 = u2.get(i);
- same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
- }
- }
- }
- }
-
- return same;
+ return mMainPkg.isObsolete();
}
- public boolean isSamePackageAs(Package pkg) {
- return mPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE;
+ public Package getUpdatePkg() {
+ return mUpdatePkg;
}
- /**
- * Check whether the 'pkg' argument updates this package.
- * If it does, record it as a sub-package.
- * Returns true if it was recorded as an update, false otherwise.
- */
- public boolean isUpdatedBy(Package pkg) {
- if (mPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
- if (mUpdatePkgs == null) {
- mUpdatePkgs = new ArrayList<Package>();
- }
- mUpdatePkgs.add(pkg);
- mState = PkgState.HAS_UPDATE;
- return true;
- }
-
- return false;
+ public boolean hasUpdatePkg() {
+ return mState == PkgState.INSTALLED && mUpdatePkg != null;
}
public String getName() {
- return mPkg.getListDescription();
+ return mMainPkg.getListDescription();
}
public int getRevision() {
- return mPkg.getRevision();
+ return mMainPkg.getRevision();
}
public String getDescription() {
- return mPkg.getDescription();
+ return mMainPkg.getDescription();
}
- public Package getPackage() {
- return mPkg;
+ public Package getMainPackage() {
+ return mMainPkg;
}
public PkgState getState() {
@@ -516,65 +444,146 @@ class PackageLoader {
public SdkSource getSource() {
if (mState == PkgState.NEW) {
- return mPkg.getParentSource();
+ return mMainPkg.getParentSource();
} else {
return null;
}
}
public int getApi() {
- return mPkg instanceof IPackageVersion ?
- ((IPackageVersion) mPkg).getVersion().getApiLevel() :
+ return mMainPkg instanceof IPackageVersion ?
+ ((IPackageVersion) mMainPkg).getVersion().getApiLevel() :
-1;
}
- public List<Package> getUpdatePkgs() {
- return mUpdatePkgs;
- }
-
public Archive[] getArchives() {
- return mPkg.getArchives();
+ return mMainPkg.getArchives();
}
public int compareTo(PkgItem pkg) {
- return getPackage().compareTo(pkg.getPackage());
+ return getMainPackage().compareTo(pkg.getMainPackage());
}
/**
- * Returns true if this package or any of the updating packages contains
+ * Returns true if this package or its updating packages contains
* the exact given archive.
* Important: This compares object references, not object equality.
*/
public boolean hasArchive(Archive archive) {
- if (mPkg.hasArchive(archive)) {
+ if (mMainPkg.hasArchive(archive)) {
return true;
}
- if (mUpdatePkgs != null && !mUpdatePkgs.isEmpty()) {
- for (Package p : mUpdatePkgs) {
- if (p.hasArchive(archive)) {
- return true;
- }
+ if (mUpdatePkg != null && mUpdatePkg.hasArchive(archive)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the main packages are of the same type and are
+ * not an update of each other.
+ */
+ public boolean isSameMainPackageAs(Package pkg) {
+ return mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE;
+ }
+
+ /**
+ * Checks whether too {@link PkgItem} are the same.
+ * This checks both items have the same state, both main package are similar
+ * and that they have the same updating packages.
+ */
+ public boolean isSameItemAs(PkgItem item) {
+ if (this == item) {
+ return true;
+ }
+ boolean same = this.mState == item.mState;
+ if (same) {
+ same = isSameMainPackageAs(item.getMainPackage());
+ }
+
+ if (same) {
+ // check updating packages are the same
+ Package p1 = this.mUpdatePkg;
+ Package p2 = item.getUpdatePkg();
+ same = (p1 == p2) || (p1 == null && p2 == null) || (p1 != null && p2 != null);
+
+ if (same && p1 != null) {
+ same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
+ }
+ }
+
+ return same;
+ }
+
+ /**
+ * Equality is defined as {@link #isSameItemAs(PkgItem)}: state, main package
+ * and update package must be the similar.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof PkgItem) && this.isSameItemAs((PkgItem) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mState == null) ? 0 : mState.hashCode());
+ result = prime * result + ((mMainPkg == null) ? 0 : mMainPkg.hashCode());
+ result = prime * result + ((mUpdatePkg == null) ? 0 : mUpdatePkg.hashCode());
+ return result;
+ }
+
+ /**
+ * Check whether the 'pkg' argument is an update for this package.
+ * If it is, record it as an updating package.
+ * If there's already an updating package, only keep the most recent update.
+ * Returns true if it is update (even if there was already an update and this
+ * ended up not being the most recent), false if incompatible or not an update.
+ *
+ * This should only be used for installed packages.
+ */
+ public boolean mergeUpdate(Package pkg) {
+ if (mUpdatePkg == pkg) {
+ return true;
+ }
+ if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
+ if (mUpdatePkg == null) {
+ mUpdatePkg = pkg;
+ } else if (mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
+ // If we have more than one, keep only the most recent update
+ mUpdatePkg = pkg;
}
+ return true;
}
+
return false;
}
+ public void removeUpdate() {
+ mUpdatePkg = null;
+ }
+
/** Returns a string representation of this item, useful when debugging. */
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(mState.toString());
+ StringBuilder sb = new StringBuilder();
+ sb.append('<');
+ sb.append(mState.toString());
- if (mPkg != null) {
+ if (mMainPkg != null) {
sb.append(", pkg:"); //$NON-NLS-1$
- sb.append(mPkg.toString());
+ sb.append(mMainPkg.toString());
}
- if (mUpdatePkgs != null && !mUpdatePkgs.isEmpty()) {
+ if (mUpdatePkg != null) {
sb.append(", updated by:"); //$NON-NLS-1$
- sb.append(Arrays.toString(mUpdatePkgs.toArray()));
+ sb.append(mUpdatePkg.toString());
}
+ sb.append('>');
return sb.toString();
}
+
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
index e858831..f690a8e 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java
@@ -33,7 +33,6 @@ import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.ISdkChangeListener;
import com.android.sdkuilib.ui.GridDataBuilder;
import com.android.sdkuilib.ui.GridLayoutBuilder;
-import com.android.util.Pair;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
@@ -125,13 +124,7 @@ public class PackagesPage extends UpdaterPage
private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>();
- private final PackageLoader mPackageLoader;
-
- private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>();
- /** Access to this list must be synchronized on {@link #mPackages}. */
- private final List<PkgItem> mPackages = new ArrayList<PkgItem>();
- private final UpdaterData mUpdaterData;
-
+ private final PackagesPageLogic mLogic;
private boolean mDisplayArchives = false;
private Text mTextSdkOsPath;
@@ -153,21 +146,24 @@ public class PackagesPage extends UpdaterPage
private TreeViewerColumn mColumnStatus;
private Font mTreeFontItalic;
private TreeColumn mTreeColumnName;
- private boolean mLastSortWasByApi;
private boolean mOperationPending;
public PackagesPage(Composite parent, int swtStyle, UpdaterData updaterData) {
super(parent, swtStyle);
- mUpdaterData = updaterData;
- mPackageLoader = new PackageLoader(updaterData);
- createContents(this);
+ mLogic = new PackagesPageLogic(updaterData) {
+ @Override
+ boolean keepItem(PkgItem item) {
+ return PackagesPage.this.keepItem(item);
+ }
+ };
+ createContents(this);
postCreate(); //$hide$
}
public void onPageSelected() {
- if (mPackages.isEmpty()) {
+ if (mLogic.mAllPkgItems.isEmpty()) {
// Initialize the package list the first time the page is shown.
loadPackages();
}
@@ -309,7 +305,7 @@ public class PackagesPage extends UpdaterPage
public void widgetSelected(SelectionEvent e) {
sortPackages(true /*updateButtons*/);
// Reset the expanded state when changing sort algorithm
- expandInitial(mCategories);
+ expandInitial(mLogic.mCurrentCategories);
}
});
mCheckSortApi.setText("API level");
@@ -323,7 +319,7 @@ public class PackagesPage extends UpdaterPage
public void widgetSelected(SelectionEvent e) {
sortPackages(true /*updateButtons*/);
// Reset the expanded state when changing sort algorithm
- expandInitial(mCategories);
+ expandInitial(mLogic.mCurrentCategories);
}
});
@@ -354,8 +350,8 @@ public class PackagesPage extends UpdaterPage
}
private Image getImage(String filename) {
- if (mUpdaterData != null) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (mLogic.mUpdaterData != null) {
+ ImageFactory imgFactory = mLogic.mUpdaterData.getImageFactory();
if (imgFactory != null) {
return imgFactory.getImageByName(filename);
}
@@ -382,7 +378,7 @@ public class PackagesPage extends UpdaterPage
loadPackages();
break;
case SHOW_ADDON_SITES:
- AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData);
+ AddonSitesDialog d = new AddonSitesDialog(getShell(), mLogic.mUpdaterData);
if (d.open()) {
loadPackages();
}
@@ -487,8 +483,8 @@ public class PackagesPage extends UpdaterPage
}
private void postCreate() {
- if (mUpdaterData != null) {
- mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
+ if (mLogic.mUpdaterData != null) {
+ mTextSdkOsPath.setText(mLogic.mUpdaterData.getOsSdkRoot());
}
mTreeViewer.setContentProvider(new PkgContentProvider());
@@ -515,31 +511,31 @@ public class PackagesPage extends UpdaterPage
}
private void loadPackages() {
- if (mUpdaterData == null) {
+ if (mLogic.mUpdaterData == null) {
return;
}
- final boolean firstLoad = mPackages.isEmpty();
+ final boolean firstLoad = mLogic.mAllPkgItems.isEmpty();
- // Load package is synchronous but does not block the UI.
+ // LoadPackage is synchronous but does not block the UI.
// Consequently it's entirely possible for the user
// to request the app to close whilst the packages are loading. Any
// action done after loadPackages must check the UI hasn't been
// disposed yet. Otherwise hilarity ensues.
- mPackageLoader.loadPackages(new ISourceLoadedCallback() {
+ mLogic.mPackageLoader.loadPackages(new ISourceLoadedCallback() {
public boolean onSourceLoaded(List<PkgItem> newPkgItems) {
boolean somethingNew = false;
- synchronized(mPackages) {
+ synchronized(mLogic.mAllPkgItems) {
nextNewItem: for (PkgItem newItem : newPkgItems) {
- for (PkgItem existingItem : mPackages) {
+ for (PkgItem existingItem : mLogic.mAllPkgItems) {
if (existingItem.isSameItemAs(newItem)) {
// This isn't a new package, we already have it.
continue nextNewItem;
}
}
- mPackages.add(newItem);
+ mLogic.mAllPkgItems.add(newItem);
somethingNew = true;
}
}
@@ -556,7 +552,7 @@ public class PackagesPage extends UpdaterPage
if (!mGroupPackages.isDisposed()) {
if (firstLoad) {
// set the initial expanded state
- expandInitial(mCategories);
+ expandInitial(mLogic.mCurrentCategories);
}
updateButtonsState();
updateMenuCheckmarks();
@@ -599,165 +595,20 @@ public class PackagesPage extends UpdaterPage
/**
* Recompute the tree by sorting all the packages by API.
- * This does an update in-place of the mCategories list so that the table
+ * This does an update in-place of the mApiCategories list so that the table
* can preserve its state (checked / expanded / selected) properly.
*/
private void sortByApiLevel() {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
-
if (!mTreeColumnName.isDisposed()) {
mTreeColumnName.setImage(getImage(ICON_SORT_BY_API));
}
- // If the sorting mode changed, clear the categories.
- if (!mLastSortWasByApi) {
- mLastSortWasByApi = true;
- mCategories.clear();
- }
-
- // keep a map of the initial state so that we can detect which items or categories are
- // no longer being used, so that we can removed them at the end of the in-place update.
- final Map<Integer, Pair<PkgApiCategory, HashSet<PkgItem>> > unusedItemsMap =
- new HashMap<Integer, Pair<PkgApiCategory, HashSet<PkgItem>> >();
- final Set<PkgApiCategory> unusedCatSet = new HashSet<PkgApiCategory>();
-
- // get existing categories
- for (PkgCategory cat : mCategories) {
- if (cat instanceof PkgApiCategory) {
- PkgApiCategory acat = (PkgApiCategory) cat;
- unusedCatSet.add(acat);
- unusedItemsMap.put(acat.getKey(),
- Pair.of(acat, new HashSet<PkgItem>(acat.getItems())));
- }
- }
-
- // always add the tools & extras categories, even if empty (unlikely anyway)
- if (!unusedItemsMap.containsKey(PkgApiCategory.KEY_TOOLS)) {
- PkgApiCategory cat = new PkgApiCategory(
- PkgApiCategory.KEY_TOOLS,
- null,
- imgFactory.getImageByName(ICON_CAT_OTHER));
- unusedItemsMap.put(PkgApiCategory.KEY_TOOLS, Pair.of(cat, new HashSet<PkgItem>()));
- mCategories.add(cat);
- }
-
- if (!unusedItemsMap.containsKey(PkgApiCategory.KEY_EXTRA)) {
- PkgApiCategory cat = new PkgApiCategory(
- PkgApiCategory.KEY_EXTRA,
- null,
- imgFactory.getImageByName(ICON_CAT_OTHER));
- unusedItemsMap.put(PkgApiCategory.KEY_EXTRA, Pair.of(cat, new HashSet<PkgItem>()));
- mCategories.add(cat);
- }
-
- synchronized (mPackages) {
- for (PkgItem item : mPackages) {
- if (!keepItem(item)) {
- continue;
- }
-
- int apiKey = item.getApi();
-
- if (apiKey < 1) {
- Package p = item.getPackage();
- if (p instanceof ToolPackage || p instanceof PlatformToolPackage) {
- apiKey = PkgApiCategory.KEY_TOOLS;
- } else {
- apiKey = PkgApiCategory.KEY_EXTRA;
- }
- }
-
- Pair<PkgApiCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apiKey);
-
- if (mapEntry == null) {
- // This is a new category. Create it and add it to the map.
-
- // We need a label for the category.
- // If we have an API level, try to get the info from the SDK Manager.
- // If we don't (e.g. when installing a new platform that isn't yet available
- // locally in the SDK Manager), it's OK we'll try to find the first platform
- // package available.
- String platformName = null;
- if (apiKey != -1) {
- for (IAndroidTarget target : mUpdaterData.getSdkManager().getTargets()) {
- if (target.isPlatform() && target.getVersion().getApiLevel() == apiKey) {
- platformName = target.getVersionName();
- break;
- }
- }
- }
-
- PkgApiCategory cat = new PkgApiCategory(
- apiKey,
- platformName,
- imgFactory.getImageByName(ICON_CAT_PLATFORM));
- mapEntry = Pair.of(cat, new HashSet<PkgItem>());
- unusedItemsMap.put(apiKey, mapEntry);
- mCategories.add(0, cat);
- }
- PkgApiCategory cat = mapEntry.getFirst();
- assert cat != null;
- unusedCatSet.remove(cat);
-
- HashSet<PkgItem> unusedItemsSet = mapEntry.getSecond();
- unusedItemsSet.remove(item);
- if (!cat.getItems().contains(item)) {
- cat.getItems().add(item);
- }
-
- if (apiKey != -1 && cat.getPlatformName() == null) {
- // Check whether we can get the actual platform version name (e.g. "1.5")
- // from the first Platform package we find in this category.
- Package p = item.getPackage();
- if (p instanceof PlatformPackage) {
- String platformName = ((PlatformPackage) p).getVersionName();
- cat.setPlatformName(platformName);
- }
- }
- }
- }
-
- for (Iterator<PkgCategory> iterCat = mCategories.iterator(); iterCat.hasNext(); ) {
- PkgCategory cat = iterCat.next();
-
- // Remove any unused categories.
- if (unusedCatSet.contains(cat)) {
- iterCat.remove();
- continue;
- }
+ mLogic.sortByApiLevel();
- // Remove any unused items in the category.
- int apikey = cat.getKey();
- Pair<PkgApiCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apikey);
- if (mapEntry == null) { //DEBUG
- apikey = (apikey + 1) - 1;
- }
- assert mapEntry != null;
- HashSet<PkgItem> unusedItems = mapEntry.getSecond();
- for (Iterator<PkgItem> iterItem = cat.getItems().iterator(); iterItem.hasNext(); ) {
- PkgItem item = iterItem.next();
- if (unusedItems.contains(item)) {
- iterItem.remove();
- }
- }
-
- // Sort the items
- Collections.sort(cat.getItems());
- }
-
- // Sort the categories list.
- Collections.sort(mCategories, new Comparator<PkgCategory>() {
- public int compare(PkgCategory cat1, PkgCategory cat2) {
- // We always want categories in order tools..platforms..extras.
- // For platform, we compare in descending order (o2-o1).
- return cat2.getKey() - cat1.getKey();
- }
- });
-
- if (mTreeViewer.getInput() != mCategories) {
+ if (mTreeViewer.getInput() != mLogic.mCurrentCategories) {
// set initial input
- mTreeViewer.setInput(mCategories);
+ mTreeViewer.setInput(mLogic.mCurrentCategories);
} else {
// refresh existing, which preserves the expanded state, the selection
// and the checked state.
@@ -774,64 +625,17 @@ public class PackagesPage extends UpdaterPage
mTreeColumnName.setImage(getImage(ICON_SORT_BY_SOURCE));
}
- mLastSortWasByApi = false;
- mCategories.clear();
-
- Map<SdkSource, List<PkgItem>> sourceMap = new HashMap<SdkSource, List<PkgItem>>();
-
- synchronized(mPackages) {
- for (PkgItem item : mPackages) {
- if (keepItem(item)) {
- SdkSource source = item.getSource();
- List<PkgItem> list = sourceMap.get(source);
- if (list == null) {
- list = new ArrayList<PkgItem>();
- sourceMap.put(source, list);
- }
- list.add(item);
- }
- }
- }
-
- // Sort the sources so that we can create categories sorted the same way
- // (the categories don't link to the sources, so we can't just sort the categories.)
- Set<SdkSource> sources = new TreeSet<SdkSource>(new Comparator<SdkSource>() {
- public int compare(SdkSource o1, SdkSource o2) {
- if (o1 == o2) {
- return 0;
- } else if (o1 == null && o2 != null) {
- return -1;
- } else if (o1 != null && o2 == null) {
- return 1;
- }
- assert o1 != null;
- return o1.toString().compareTo(o2.toString());
- }
- });
- sources.addAll(sourceMap.keySet());
-
- for (SdkSource source : sources) {
- Object key = source != null ? source : "Locally Installed Packages";
- Object iconRef = source != null ? source :
- mUpdaterData.getImageFactory().getImageByName(ICON_PKG_INSTALLED);
-
- PkgCategory cat = new PkgCategory(
- key.hashCode(),
- key.toString(),
- iconRef);
-
- for (PkgItem item : sourceMap.get(source)) {
- if (item.getSource() == source) {
- cat.getItems().add(item);
- }
- }
-
- mCategories.add(cat);
- }
+ mLogic.sortBySource();
// We don't support in-place incremental updates so the table gets reset
// each time we load when sorted by source.
- mTreeViewer.setInput(mCategories);
+ if (mTreeViewer.getInput() != mLogic.mCurrentCategories) {
+ mTreeViewer.setInput(mLogic.mCurrentCategories);
+ } else {
+ // refresh existing, which preserves the expanded state, the selection
+ // and the checked state.
+ mTreeViewer.refresh();
+ }
}
/**
@@ -851,8 +655,7 @@ public class PackagesPage extends UpdaterPage
}
if (!mCheckFilterNew.getSelection()) {
- if (item.getState() == PkgState.NEW ||
- item.getState() == PkgState.HAS_UPDATE) {
+ if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
return false;
}
}
@@ -875,8 +678,7 @@ public class PackagesPage extends UpdaterPage
if (pkg instanceof PkgCategory) {
PkgCategory cat = (PkgCategory) pkg;
for (PkgItem item : cat.getItems()) {
- if (item.getState() == PkgState.INSTALLED
- || item.getState() == PkgState.HAS_UPDATE) {
+ if (item.getState() == PkgState.INSTALLED) {
expandInitial(pkg);
break;
}
@@ -986,7 +788,7 @@ public class PackagesPage extends UpdaterPage
break;
}
} else if (c instanceof PkgItem) {
- if (((PkgItem) c).getPackage().hasCompatibleArchive()) {
+ if (((PkgItem) c).getMainPackage().hasCompatibleArchive()) {
canInstall = true;
break;
}
@@ -1004,7 +806,7 @@ public class PackagesPage extends UpdaterPage
for (Object c : checked) {
if (c instanceof PkgItem) {
PkgState state = ((PkgItem) c).getState();
- if (state == PkgState.INSTALLED || state == PkgState.HAS_UPDATE) {
+ if (state == PkgState.INSTALLED) {
canDelete = true;
break;
}
@@ -1017,12 +819,11 @@ public class PackagesPage extends UpdaterPage
private void onSelectNewUpdates() {
ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
- synchronized(mPackages) {
- for (PkgCategory cat : mCategories) {
+ synchronized(mLogic.mAllPkgItems) {
+ for (PkgCategory cat : mLogic.mCurrentCategories) {
boolean selected = false;
for (PkgItem item : cat.getItems()) {
- PkgState state = item.getState();
- if (state == PkgState.NEW || state == PkgState.HAS_UPDATE) {
+ if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
mTreeViewer.setChecked(item, true);
checkExpandItem(item, provider);
selected = true;
@@ -1071,14 +872,14 @@ public class PackagesPage extends UpdaterPage
// This is an update package
p = (Package) c;
} else if (c instanceof PkgItem) {
- p = ((PkgItem) c).getPackage();
+ p = ((PkgItem) c).getMainPackage();
PkgItem pi = (PkgItem) c;
- if (pi.getState() == PkgState.HAS_UPDATE) {
- List<Package> updates = pi.getUpdatePkgs();
+ if (pi.getState() == PkgState.INSTALLED) {
+ Package updPkg = pi.getUpdatePkg();
+ if (updPkg != null) {
// If there's one and only one update, auto-select it instead.
- if (updates != null && updates.size() == 1) {
- p = updates.get(0);
+ p = updPkg;
}
}
}
@@ -1093,11 +894,11 @@ public class PackagesPage extends UpdaterPage
}
}
- if (mUpdaterData != null) {
+ if (mLogic.mUpdaterData != null) {
try {
beginOperationPending();
- mUpdaterData.updateOrInstallAll_WithGUI(
+ mLogic.mUpdaterData.updateOrInstallAll_WithGUI(
archives,
mCheckFilterObsolete.getSelection() /* includeObsoletes */);
} finally {
@@ -1106,9 +907,9 @@ public class PackagesPage extends UpdaterPage
// Remove any pkg item matching anything we potentially installed
// then request the package list to be updated. This will prevent
// from having stale entries.
- synchronized(mPackages) {
+ synchronized(mLogic.mAllPkgItems) {
for (Archive a : archives) {
- for (Iterator<PkgItem> it = mPackages.iterator(); it.hasNext(); ) {
+ for (Iterator<PkgItem> it = mLogic.mAllPkgItems.iterator(); it.hasNext(); ) {
PkgItem pi = it.next();
if (pi.hasArchive(a)) {
it.remove();
@@ -1119,7 +920,7 @@ public class PackagesPage extends UpdaterPage
}
// The local package list has changed, make sure to refresh it
- mUpdaterData.getLocalSdkParser().clearPackages();
+ mLogic.mUpdaterData.getLocalSdkParser().clearPackages();
loadPackages();
}
}
@@ -1143,8 +944,8 @@ public class PackagesPage extends UpdaterPage
if (c instanceof PkgItem) {
PkgItem pi = (PkgItem) c;
PkgState state = pi.getState();
- if (state == PkgState.INSTALLED || state == PkgState.HAS_UPDATE) {
- Package p = pi.getPackage();
+ if (state == PkgState.INSTALLED) {
+ Package p = pi.getMainPackage();
Archive[] as = p.getArchives();
if (as.length == 1 && as[0] != null && as[0].isLocal()) {
@@ -1167,7 +968,7 @@ public class PackagesPage extends UpdaterPage
try {
beginOperationPending();
- mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
+ mLogic.mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
public void run(ITaskMonitor monitor) {
monitor.setProgressMax(archives.size() + 1);
for (Entry<Archive, PkgItem> entry : archives.entrySet()) {
@@ -1180,8 +981,8 @@ public class PackagesPage extends UpdaterPage
// Delete the actual package and its internal representation
a.deleteLocal();
- synchronized(mPackages) {
- mPackages.remove(entry.getValue());
+ synchronized(mLogic.mAllPkgItems) {
+ mLogic.mAllPkgItems.remove(entry.getValue());
}
monitor.incProgress(1);
@@ -1198,7 +999,7 @@ public class PackagesPage extends UpdaterPage
endOperationPending();
// The local package list has changed, make sure to refresh it
- mUpdaterData.getLocalSdkParser().clearPackages();
+ mLogic.mUpdaterData.getLocalSdkParser().clearPackages();
loadPackages();
}
}
@@ -1244,8 +1045,7 @@ public class PackagesPage extends UpdaterPage
if (element instanceof PkgItem) {
PkgItem pkg = (PkgItem) element;
- if (pkg.getState() == PkgState.INSTALLED ||
- pkg.getState() == PkgState.HAS_UPDATE) {
+ if (pkg.getState() == PkgState.INSTALLED) {
return Integer.toString(pkg.getRevision());
}
}
@@ -1257,19 +1057,16 @@ public class PackagesPage extends UpdaterPage
switch(pkg.getState()) {
case INSTALLED:
- return "Installed";
- case HAS_UPDATE:
- List<Package> updates = pkg.getUpdatePkgs();
- if (updates.size() == 1) {
- return String.format("Update available: rev. %1$s",
- updates.get(0).getRevision());
- } else {
- // This case should not happen in *our* typical release workflow.
- return "Multiple updates available";
+ Package update = pkg.getUpdatePkg();
+ if (update != null) {
+ return String.format(
+ "Update available: rev. %1$s",
+ update.getRevision());
}
+ return "Installed";
+
case NEW:
return "Not installed";
- // TODO display pkg.getRevision() in a tooltip
}
return pkg.getState().toString();
@@ -1279,7 +1076,7 @@ public class PackagesPage extends UpdaterPage
}
}
- return ""; //$NON-NLS-1$
+ return "";
}
private String getPkgItemname(PkgItem item) {
@@ -1306,7 +1103,7 @@ public class PackagesPage extends UpdaterPage
}
private PkgCategory findCategoryForItem(PkgItem item) {
- for (PkgCategory cat : mCategories) {
+ for (PkgCategory cat : mLogic.mCurrentCategories) {
for (PkgItem i : cat.getItems()) {
if (i == item) {
return cat;
@@ -1319,23 +1116,26 @@ public class PackagesPage extends UpdaterPage
@Override
public Image getImage(Object element) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ ImageFactory imgFactory = mLogic.mUpdaterData.getImageFactory();
if (imgFactory != null) {
if (mColumn == mColumnName) {
if (element instanceof PkgCategory) {
return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
} else if (element instanceof PkgItem) {
- return imgFactory.getImageForObject(((PkgItem) element).getPackage());
+ return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
}
return imgFactory.getImageForObject(element);
} else if (mColumn == mColumnStatus && element instanceof PkgItem) {
- switch(((PkgItem) element).getState()) {
+ PkgItem pi = (PkgItem) element;
+ switch(pi.getState()) {
case INSTALLED:
- return imgFactory.getImageByName(ICON_PKG_INSTALLED);
- case HAS_UPDATE:
- return imgFactory.getImageByName(ICON_PKG_UPDATE);
+ if (pi.hasUpdatePkg()) {
+ return imgFactory.getImageByName(ICON_PKG_UPDATE);
+ } else {
+ return imgFactory.getImageByName(ICON_PKG_INSTALLED);
+ }
case NEW:
return imgFactory.getImageByName(ICON_PKG_NEW);
}
@@ -1370,15 +1170,15 @@ public class PackagesPage extends UpdaterPage
return ((PkgCategory) parentElement).getItems().toArray();
} else if (parentElement instanceof PkgItem) {
- List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (mDisplayArchives) {
- // Display update packages as sub-items if there's more than one
- // or if the archive/details mode is activated.
- if (pkgs != null && (pkgs.size() > 1 || mDisplayArchives)) {
- return pkgs.toArray();
- }
+ Package pkg = ((PkgItem) parentElement).getUpdatePkg();
+
+ // Display update packages as sub-items if the details mode is activated.
+ if (pkg != null) {
+ return new Object[] { pkg };
+ }
- if (mDisplayArchives) {
return ((PkgItem) parentElement).getArchives();
}
@@ -1406,15 +1206,14 @@ public class PackagesPage extends UpdaterPage
return true;
} else if (parentElement instanceof PkgItem) {
- List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (mDisplayArchives) {
+ Package pkg = ((PkgItem) parentElement).getUpdatePkg();
- // Display update packages as sub-items if there's more than one
- // or if the archive/details mode is activated.
- if (pkgs != null && (pkgs.size() > 1 || mDisplayArchives)) {
- return !pkgs.isEmpty();
- }
+ // Display update packages as sub-items if the details mode is activated.
+ if (pkg != null) {
+ return true;
+ }
- if (mDisplayArchives) {
Archive[] archives = ((PkgItem) parentElement).getArchives();
return archives.length > 0;
}
@@ -1441,7 +1240,7 @@ public class PackagesPage extends UpdaterPage
}
}
- private static class PkgCategory {
+ static class PkgCategory {
private final int mKey;
private final Object mIconRef;
private final List<PkgItem> mItems = new ArrayList<PkgItem>();
@@ -1476,6 +1275,35 @@ public class PackagesPage extends UpdaterPage
public List<PkgItem> getItems() {
return mItems;
}
+
+ @Override
+ public String toString() {
+ return String.format("%s <key=%08x, label=%s, #items=%d>",
+ this.getClass().getSimpleName(),
+ mKey,
+ mLabel,
+ mItems.size());
+ }
+
+ /** {@link PkgCategory} are equal if their internal key is the same. */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mKey;
+ return result;
+ }
+
+ /** {@link PkgCategory} are equal if their internal key is the same. */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ PkgCategory other = (PkgCategory) obj;
+ if (mKey != other.mKey) return false;
+ return true;
+ }
}
private static class PkgApiCategory extends PkgCategory {
@@ -1510,10 +1338,13 @@ public class PackagesPage extends UpdaterPage
public String getApiLabel() {
int api = getKey();
- if (api > 0) {
+ if (api == KEY_TOOLS) {
+ return "TOOLS"; //$NON-NLS-1$ for internal use only
+ } else if (api == KEY_EXTRA) {
+ return "EXTRAS"; //$NON-NLS-1$ for internal use only
+ } else {
return String.format("API %1$d", getKey());
}
- return null;
}
@Override
@@ -1540,7 +1371,16 @@ public class PackagesPage extends UpdaterPage
@Override
public void setLabel(String label) {
- throw new UnsupportedOperationException("Use setPlatformName() instead."); //$NON-NLS-1$
+ throw new UnsupportedOperationException("Use setPlatformName() instead.");
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s <API=%s, label=%s, #items=%d>",
+ this.getClass().getSimpleName(),
+ getApiLabel(),
+ getLabel(),
+ getItems().size());
}
}
@@ -1563,6 +1403,296 @@ public class PackagesPage extends UpdaterPage
// nothing to be done for now.
}
+
+ /**
+ * Helper class that separate the logic of package management from the UI
+ * so that we can test it using head-less unit tests.
+ */
+ static abstract class PackagesPageLogic {
+ final PackageLoader mPackageLoader;
+ final UpdaterData mUpdaterData;
+
+ final List<PkgCategory> mApiCategories = new ArrayList<PkgCategory>();
+ final List<PkgCategory> mSourceCategories = new ArrayList<PkgCategory>();
+ List<PkgCategory> mCurrentCategories = mApiCategories;
+ /** Access to this list must be synchronized on {@link #mAllPkgItems}. */
+ final List<PkgItem> mAllPkgItems = new ArrayList<PkgItem>();
+
+ public PackagesPageLogic(UpdaterData updaterData) {
+ mUpdaterData = updaterData;
+ mPackageLoader = new PackageLoader(updaterData);
+ }
+
+ /**
+ * Recompute the tree by sorting all the packages by API.
+ * This does an update in-place of the mApiCategories list so that the table
+ * can preserve its state (checked / expanded / selected) properly.
+ */
+ void sortByApiLevel() {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+
+ mCurrentCategories = mApiCategories;
+
+ // We'll do an in-place update: first make a map of existing categories and
+ // whatever pkg items they contain. Then prepare the new categories we want
+ // which is all the existing categories + tools & extra (creating them the first time).
+ // We mark all the existing items as "unused" then remove from the unused set the
+ // items that we want to keep. At the end, whatever is left in the unused maps
+ // are obsolete items we remove from the tree.
+
+ // Keep a map of the initial state so that we can detect which items or categories are
+ // no longer being used, so that we can remove them at the end of the in-place update.
+
+ final Map<Integer, PkgApiCategory> categoryKeyMap = new HashMap<Integer, PkgApiCategory>();
+ final Set<Integer> unusedCategoryKey = new HashSet<Integer>();
+ final Set<Package> unusedPackages = new HashSet<Package>();
+
+ // Get existing categories and packages
+ for (PkgCategory cat : mApiCategories) {
+ if (cat instanceof PkgApiCategory) {
+ PkgApiCategory acat = (PkgApiCategory) cat;
+ categoryKeyMap.put(acat.getKey(), acat);
+ unusedCategoryKey.add(acat.getKey());
+
+ for (PkgItem pi : cat.getItems()) {
+ unusedPackages.add(pi.getMainPackage());
+ if (pi.hasUpdatePkg()) {
+ unusedPackages.add(pi.getUpdatePkg());
+ }
+ }
+ }
+ }
+
+ // Always add the tools & extras categories, even if empty (unlikely anyway)
+ if (!unusedCategoryKey.contains(PkgApiCategory.KEY_TOOLS)) {
+ PkgApiCategory acat = new PkgApiCategory(
+ PkgApiCategory.KEY_TOOLS,
+ null,
+ imgFactory.getImageByName(ICON_CAT_OTHER));
+ mApiCategories.add(acat);
+ categoryKeyMap.put(acat.getKey(), acat);
+ unusedCategoryKey.add(acat.getKey());
+ }
+
+ if (!unusedCategoryKey.contains(PkgApiCategory.KEY_EXTRA)) {
+ PkgApiCategory acat = new PkgApiCategory(
+ PkgApiCategory.KEY_EXTRA,
+ null,
+ imgFactory.getImageByName(ICON_CAT_OTHER));
+ mApiCategories.add(acat);
+ categoryKeyMap.put(acat.getKey(), acat);
+ unusedCategoryKey.add(acat.getKey());
+ }
+
+ // Go through the new package item list
+ synchronized (mAllPkgItems) {
+ for (PkgItem newItem : mAllPkgItems) {
+ // Is this a package we want to display? That may change depending on the
+ // display filter (obsolete, new/updates, etc.)
+ if (!keepItem(newItem)) {
+ continue;
+ }
+
+ // Get the category for this item.
+ int apiKey = newItem.getApi();
+
+ if (apiKey < 1) {
+ Package p = newItem.getMainPackage();
+ if (p instanceof ToolPackage || p instanceof PlatformToolPackage) {
+ apiKey = PkgApiCategory.KEY_TOOLS;
+ } else {
+ apiKey = PkgApiCategory.KEY_EXTRA;
+ }
+ }
+
+ PkgApiCategory cat = categoryKeyMap.get(apiKey);
+
+ if (cat == null) {
+ // This is a new category. Create it and add it to the map.
+
+ // We need a label for the category.
+ // If we have an API level, try to get the info from the SDK Manager.
+ // If we don't (e.g. when installing a new platform that isn't yet available
+ // locally in the SDK Manager), it's OK we'll try to find the first platform
+ // package available.
+ String platformName = null;
+ if (apiKey != -1) {
+ for (IAndroidTarget target : mUpdaterData.getSdkManager().getTargets()) {
+ if (target.isPlatform() && target.getVersion().getApiLevel() == apiKey) {
+ platformName = target.getVersionName();
+ break;
+ }
+ }
+ }
+
+ cat = new PkgApiCategory(
+ apiKey,
+ platformName,
+ imgFactory.getImageByName(ICON_CAT_PLATFORM));
+ mApiCategories.add(0, cat);
+ categoryKeyMap.put(cat.getKey(), cat);
+ } else {
+ // Remove the category key from the unused list.
+ unusedCategoryKey.remove(apiKey);
+ }
+
+ // Add the item to the category or merge as an update of an existing package
+ boolean found = false;
+ if (newItem.getState() == PkgState.NEW) {
+ for (PkgItem pi : cat.getItems()) {
+ Package p = newItem.getMainPackage();
+ if (pi.isSameItemAs(newItem) ||
+ pi.isSameMainPackageAs(p)) {
+ // It's the same item or
+ // it's not exactly the same item but the main package is the same.
+ unusedPackages.remove(pi.getMainPackage());
+ found = true;
+
+ } else if (pi.mergeUpdate(p)) {
+ // The new package is an update for the existing package.
+ unusedPackages.remove(pi.getMainPackage());
+ unusedPackages.remove(pi.getUpdatePkg());
+ found = true;
+ }
+ if (found) {
+ break;
+ }
+ }
+ } else {
+ // We do not try to merge installed packages. This prevents a bug in
+ // the edge case where a new update might be present in the package
+ // list before the installed item it would update. If we were trying
+ // to merge the installed item into the new package, the installed item
+ // would most likely be hidden because it would have a lesser revision.
+ // This case is not supposed to happen but if it does, we 'd better have
+ // a dup than a missing displayed package.
+
+ for (PkgItem pi : cat.getItems()) {
+ if (pi.isSameItemAs(newItem)) {
+ unusedPackages.remove(newItem.getMainPackage());
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ cat.getItems().add(newItem);
+ unusedPackages.remove(newItem.getMainPackage());
+ unusedPackages.remove(newItem.getUpdatePkg());
+ }
+
+ if (apiKey != -1 && cat.getPlatformName() == null) {
+ // Check whether we can get the actual platform version name (e.g. "1.5")
+ // from the first Platform package we find in this category.
+ Package p = newItem.getMainPackage();
+ if (p instanceof PlatformPackage) {
+ String platformName = ((PlatformPackage) p).getVersionName();
+ cat.setPlatformName(platformName);
+ }
+ }
+ }
+ }
+
+ // Now go through all the remaining categories used for the tree and clear unused items.
+ for (Iterator<PkgCategory> iterCat = mApiCategories.iterator(); iterCat.hasNext(); ) {
+ PkgCategory cat = iterCat.next();
+
+ // Remove any unused categories.
+ if (unusedCategoryKey.contains(cat.getKey())) {
+ iterCat.remove();
+ continue;
+ }
+
+ // Remove any unused items in the category.
+ for (Iterator<PkgItem> iterItem = cat.getItems().iterator(); iterItem.hasNext(); ) {
+ PkgItem item = iterItem.next();
+
+ if (unusedPackages.contains(item.getMainPackage())) {
+ iterItem.remove();
+ } else if (item.hasUpdatePkg() && unusedPackages.contains(item.getUpdatePkg())) {
+ item.removeUpdate();
+ }
+ }
+
+ // Sort the items
+ Collections.sort(cat.getItems());
+ }
+
+ // Sort the categories list.
+ Collections.sort(mApiCategories, new Comparator<PkgCategory>() {
+ public int compare(PkgCategory cat1, PkgCategory cat2) {
+ // We always want categories in order tools..platforms..extras.
+ // For platform, we compare in descending order (o2-o1).
+ return cat2.getKey() - cat1.getKey();
+ }
+ });
+ }
+
+ /**
+ * Recompute the tree by sorting all packages by source.
+ */
+ void sortBySource() {
+
+ mCurrentCategories = mSourceCategories;
+
+ Map<SdkSource, List<PkgItem>> sourceMap = new HashMap<SdkSource, List<PkgItem>>();
+
+ synchronized(mAllPkgItems) {
+ for (PkgItem item : mAllPkgItems) {
+ if (keepItem(item)) {
+ SdkSource source = item.getSource();
+ List<PkgItem> list = sourceMap.get(source);
+ if (list == null) {
+ list = new ArrayList<PkgItem>();
+ sourceMap.put(source, list);
+ }
+ list.add(item);
+ }
+ }
+ }
+
+ // Sort the sources so that we can create categories sorted the same way
+ // (the categories don't link to the sources, so we can't just sort the categories.)
+ Set<SdkSource> sources = new TreeSet<SdkSource>(new Comparator<SdkSource>() {
+ public int compare(SdkSource o1, SdkSource o2) {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null && o2 != null) {
+ return -1;
+ } else if (o1 != null && o2 == null) {
+ return 1;
+ }
+ assert o1 != null;
+ return o1.toString().compareTo(o2.toString());
+ }
+ });
+ sources.addAll(sourceMap.keySet());
+
+ for (SdkSource source : sources) {
+ Object key = source != null ? source : "Locally Installed Packages";
+ Object iconRef = source != null ? source :
+ mUpdaterData.getImageFactory().getImageByName(ICON_PKG_INSTALLED);
+
+ PkgCategory cat = new PkgCategory(
+ key.hashCode(),
+ key.toString(),
+ iconRef);
+
+ for (PkgItem item : sourceMap.get(source)) {
+ if (item.getSource() == source) {
+ cat.getItems().add(item);
+ }
+ }
+
+ mSourceCategories.add(cat);
+ }
+ }
+
+ abstract boolean keepItem(PkgItem item);
+ }
+
+
// --- End of hiding from SWT Designer ---
//$hide<<$
}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java
new file mode 100755
index 0000000..77b5992
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+import java.io.File;
+import java.util.Properties;
+
+class MockEmptyPackage extends Package {
+ private final String mTestHandle;
+
+ public MockEmptyPackage(String testHandle) {
+ super(
+ null /*source*/,
+ null /*props*/,
+ 0 /*revision*/,
+ null /*license*/,
+ null /*description*/,
+ null /*descUrl*/,
+ Os.ANY /*archiveOs*/,
+ Arch.ANY /*archiveArch*/,
+ null /*archiveOsPath*/
+ );
+ mTestHandle = testHandle;
+ }
+
+ public MockEmptyPackage(String testHandle, int revision) {
+ super(
+ null /*source*/,
+ null /*props*/,
+ revision,
+ null /*license*/,
+ null /*description*/,
+ null /*descUrl*/,
+ Os.ANY /*archiveOs*/,
+ Arch.ANY /*archiveArch*/,
+ null /*archiveOsPath*/
+ );
+ mTestHandle = testHandle;
+ }
+
+ @Override
+ protected Archive createLocalArchive(
+ Properties props,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new Archive(this, props, archiveOs, archiveArch, archiveOsPath) {
+ @Override
+ public String toString() {
+ return mTestHandle;
+ }
+ };
+ }
+
+ public Archive getLocalArchive() {
+ return getArchives()[0];
+ }
+
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ return null;
+ }
+
+ @Override
+ public String getListDescription() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
+ if (getRevision() > 0) {
+ sb.append(" rev=").append(getRevision());
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ return (pkg instanceof MockEmptyPackage) &&
+ mTestHandle.equals(((MockEmptyPackage) pkg).mTestHandle);
+ }
+
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
new file mode 100755
index 0000000..20cd48a
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.ArchiveInstaller;
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskFactory;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.MockEmptySdkManager;
+import com.android.sdklib.mock.MockLog;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+
+import org.eclipse.swt.graphics.Image;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A mock UpdaterData that simply records what would have been installed. */
+class MockUpdaterData extends UpdaterData {
+
+ public final static String SDK_PATH = "/tmp/SDK";
+
+ private final List<Archive> mInstalled = new ArrayList<Archive>();
+
+ public MockUpdaterData() {
+ super(SDK_PATH, new MockLog());
+
+ setTaskFactory(new MockTaskFactory());
+ setImageFactory(new NullImageFactory());
+ }
+
+ /** Gives access to the internal {@link #installArchives(List)}. */
+ public void _installArchives(List<ArchiveInfo> result) {
+ installArchives(result);
+ }
+
+ public Archive[] getInstalled() {
+ return mInstalled.toArray(new Archive[mInstalled.size()]);
+ }
+
+ @Override
+ protected void initSdk() {
+ setSdkManager(new MockEmptySdkManager(SDK_PATH));
+ }
+
+ @Override
+ public void reloadSdk() {
+ // bypass original implementation
+ }
+
+ /** Returns a mock installer that simply records what would have been installed. */
+ @Override
+ protected ArchiveInstaller createArchiveInstaler() {
+ return new ArchiveInstaller() {
+ @Override
+ public boolean install(
+ Archive archive,
+ String osSdkRoot,
+ boolean forceHttp,
+ SdkManager sdkManager,
+ ITaskMonitor monitor) {
+ mInstalled.add(archive);
+ return true;
+ }
+ };
+ }
+
+ //------------
+
+ private class MockTaskFactory implements ITaskFactory {
+ public void start(String title, ITask task) {
+ new MockTask(task);
+ }
+ }
+
+ //------------
+
+ private static class MockTask implements ITaskMonitor {
+ public MockTask(ITask task) {
+ task.run(this);
+ }
+
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ return this;
+ }
+
+ public boolean displayPrompt(String title, String message) {
+ return false;
+ }
+
+ public int getProgress() {
+ return 0;
+ }
+
+ public void incProgress(int delta) {
+ // ignore
+ }
+
+ public boolean isCancelRequested() {
+ return false;
+ }
+
+ public void setDescription(String format, Object... args) {
+ // ignore
+ }
+
+ public void setProgressMax(int max) {
+ // ignore
+ }
+
+ public void log(String format, Object... args) {
+ // ignore
+ }
+
+ public void logError(String format, Object... args) {
+ // ignore
+ }
+
+ public void logVerbose(String format, Object... args) {
+ // ignore
+ }
+ }
+
+ //------------
+
+ private static class NullImageFactory extends ImageFactory {
+ public NullImageFactory() {
+ // pass
+ super(null /*display*/);
+ }
+
+ @Override
+ public Image getImageByName(String imageName) {
+ return null;
+ }
+
+ @Override
+ public Image getImageForObject(Object object) {
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ // pass
+ }
+
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java
new file mode 100755
index 0000000..e0a28f6
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.internal.repository.MockAddonPackage;
+import com.android.sdklib.internal.repository.MockExtraPackage;
+import com.android.sdklib.internal.repository.MockPlatformPackage;
+import com.android.sdklib.internal.repository.MockPlatformToolPackage;
+import com.android.sdklib.internal.repository.MockToolPackage;
+import com.android.sdkuilib.internal.repository.PackageLoader.PkgItem;
+import com.android.sdkuilib.internal.repository.PackageLoader.PkgState;
+import com.android.sdkuilib.internal.repository.PackagesPage.PackagesPageLogic;
+import com.android.sdkuilib.internal.repository.PackagesPage.PkgCategory;
+
+import junit.framework.TestCase;
+
+public class PackagesPageLogicTest extends TestCase {
+
+ private PackagesPageLogic m;
+ private MockUpdaterData u;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ u = new MockUpdaterData();
+ m = new PackagesPageLogic(u) {
+ @Override
+ boolean keepItem(PkgItem item) {
+ return true;
+ }
+ };
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ // ----
+
+ public void testSortByApi_Empty() {
+ assertTrue(m.mAllPkgItems.isEmpty());
+ m.sortByApiLevel();
+ assertSame(m.mCurrentCategories, m.mApiCategories);
+ assertTrue(m.mApiCategories.isEmpty());
+ }
+
+ public void testSortByApi_AddPackages() {
+ assertTrue(m.mAllPkgItems.isEmpty());
+ m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage("1_new"), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage("2_installed"), PkgState.INSTALLED));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <NEW, pkg:MockEmptyPackage>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_Update1() {
+ assertTrue(m.mAllPkgItems.isEmpty());
+
+ // Typical case: user has a locally installed package in revision 1
+ // The display list after sort should show that instaled package.
+ m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage("type1", 1), PkgState.INSTALLED));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage rev=1>\n",
+ getTree(m));
+
+ // Then loading sources reveals an update in revision 4
+ // Edge case: another source reveals an update in revision 2.
+ // The display list after sort should show an update as available with rev 4
+ // and rev 2 should be ignored since we have a better one.
+ m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage("type1", 4), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage("type1", 2), PkgState.NEW));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <INSTALLED, pkg:MockEmptyPackage rev=1, updated by:MockEmptyPackage rev=4>\n",
+ getTree(m));
+ }
+
+ public void testSortByApi_CompleteUpdate() {
+ assertTrue(m.mAllPkgItems.isEmpty());
+
+ // Resulting categories are sorted by Tools, descending platform API and finally Extras.
+ // Addons are sorted by name within their API.
+ // Extras are sorted by vendor name.
+ // The order packages are added to the mAllPkgItems list is purposedly different from
+ // the final order we get.
+
+ // Typical case is to have these 2 tools, which should get sorted in their own category
+ m.mAllPkgItems.add(new PkgItem(new MockToolPackage(10, 3), PkgState.INSTALLED));
+ m.mAllPkgItems.add(new PkgItem(new MockPlatformToolPackage(3), PkgState.INSTALLED));
+
+ // Load a few extra packages
+ m.mAllPkgItems.add(new PkgItem(new MockExtraPackage("carrier", "custom_rom", 1, 0), PkgState.NEW));
+
+ // We call sortByApiLevel() multiple times to simulate the fact it works as an
+ // incremental diff. In real usage, it is called after each source is loaded so
+ // that we can progressively update the display.
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=1>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ m.mAllPkgItems.add(new PkgItem(new MockExtraPackage("android", "usb_driver", 4, 3), PkgState.INSTALLED));
+ m.mAllPkgItems.add(new PkgItem(new MockExtraPackage("android", "usb_driver", 5, 3), PkgState.NEW));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ // Platforms and addon are sorted in a category based on their API level
+ MockPlatformPackage p1;
+ MockPlatformPackage p2;
+ m.mAllPkgItems.add(new PkgItem(p1 = new MockPlatformPackage(1, 2, 3), PkgState.INSTALLED));
+ m.mAllPkgItems.add(new PkgItem(p2 = new MockPlatformPackage(2, 4, 3), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem( new MockPlatformPackage(3, 6, 3), PkgState.INSTALLED));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=API 3, label=Android android-3 (API 3), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "PkgApiCategory <API=API 2, label=Android android-2 (API 2), #items=1>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "PkgApiCategory <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon C",p2, 9), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon A", p1, 5), PkgState.INSTALLED));
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon A", p1, 6), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon B",p2, 7), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon B",p2, 8), PkgState.NEW)); // this update is ignored
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon B",p2, 9), PkgState.NEW)); // this updates new addon B in rev 7
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=API 3, label=Android android-3 (API 3), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "PkgApiCategory <API=API 2, label=Android android-2 (API 2), #items=3>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "PkgApiCategory <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+
+ // The only requirement is that updates MUST appear AFTER the packages they will update.
+ // The reverse order is not supported by the sorting algorithm and both will be shown.
+
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon D",p2, 15), PkgState.NEW));
+ m.mAllPkgItems.add(new PkgItem(new MockAddonPackage("addon D",p2, 14), PkgState.INSTALLED));
+
+ m.sortByApiLevel();
+
+ assertEquals(
+ "PkgApiCategory <API=TOOLS, label=Tools, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android SDK Tools, revision 10>\n" +
+ "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+ "PkgApiCategory <API=API 3, label=Android android-3 (API 3), #items=1>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-3, API 3, revision 6>\n" +
+ "PkgApiCategory <API=API 2, label=Android android-2 (API 2), #items=5>\n" +
+ "-- <NEW, pkg:SDK Platform Android android-2, API 2, revision 4>\n" +
+ "-- <NEW, pkg:addon B by vendor 2, Android API 2, revision 7, updated by:addon B by vendor 2, Android API 2, revision 9>\n" +
+ "-- <NEW, pkg:addon C by vendor 2, Android API 2, revision 9>\n" +
+ "-- <INSTALLED, pkg:addon D by vendor 2, Android API 2, revision 14>\n" +
+ "-- <NEW, pkg:addon D by vendor 2, Android API 2, revision 15>\n" +
+ "PkgApiCategory <API=API 1, label=Android android-1 (API 1), #items=2>\n" +
+ "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+ "-- <INSTALLED, pkg:addon A by vendor 1, Android API 1, revision 5, updated by:addon A by vendor 1, Android API 1, revision 6>\n" +
+ "PkgApiCategory <API=EXTRAS, label=Extras, #items=2>\n" +
+ "-- <INSTALLED, pkg:Android USB Driver package, revision 4, updated by:Android USB Driver package, revision 5>\n" +
+ "-- <NEW, pkg:Carrier Custom Rom package, revision 1>\n",
+ getTree(m));
+ }
+
+ // ----
+
+ public void testSortBySource_Empty() {
+ assertTrue(m.mAllPkgItems.isEmpty());
+ m.sortBySource();
+ assertSame(m.mCurrentCategories, m.mSourceCategories);
+ assertTrue(m.mApiCategories.isEmpty());
+ }
+
+
+ // ----
+
+ /**
+ * Simulates the display we would have in the Packages Tree.
+ * This always depends on mCurrentCategories like the tree does.
+ * The display format is something like:
+ * <pre>
+ * PkgCategory &lt;description&gt;
+ * -- &lt;PkgItem description&gt;
+ * </pre>
+ */
+ public String getTree(PackagesPageLogic l) {
+ StringBuilder sb = new StringBuilder();
+
+ for (PkgCategory cat : l.mCurrentCategories) {
+ sb.append(cat.toString()).append('\n');
+ for (PkgItem item : cat.getItems()) {
+ sb.append("-- ").append(item.toString()).append('\n');
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
index 9470f91..303c24f 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java
@@ -16,23 +16,10 @@
package com.android.sdkuilib.internal.repository;
-import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.Archive;
-import com.android.sdklib.internal.repository.ArchiveInstaller;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.sdklib.internal.repository.ITaskMonitor;
-import com.android.sdklib.internal.repository.MockEmptySdkManager;
-import com.android.sdklib.internal.repository.Package;
-import com.android.sdklib.internal.repository.Archive.Arch;
-import com.android.sdklib.internal.repository.Archive.Os;
-import com.android.sdklib.mock.MockLog;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
import junit.framework.TestCase;
@@ -104,164 +91,4 @@ public class UpdaterDataTest extends TestCase {
assertEquals("[a1, a2]", Arrays.toString(m.getInstalled()));
}
- // ---
-
-
- /** A mock UpdaterData that simply records what would have been installed. */
- private static class MockUpdaterData extends UpdaterData {
-
- private final List<Archive> mInstalled = new ArrayList<Archive>();
-
- public MockUpdaterData() {
- super("/tmp/SDK", new MockLog());
-
- setTaskFactory(new MockTaskFactory());
- }
-
- /** Gives access to the internal {@link #installArchives(List)}. */
- public void _installArchives(List<ArchiveInfo> result) {
- installArchives(result);
- }
-
- public Archive[] getInstalled() {
- return mInstalled.toArray(new Archive[mInstalled.size()]);
- }
-
- @Override
- protected void initSdk() {
- setSdkManager(new MockEmptySdkManager("/tmp/SDK"));
- }
-
- @Override
- public void reloadSdk() {
- // bypass original implementation
- }
-
- /** Returns a mock installer that simply records what would have been installed. */
- @Override
- protected ArchiveInstaller createArchiveInstaler() {
- return new ArchiveInstaller() {
- @Override
- public boolean install(
- Archive archive,
- String osSdkRoot,
- boolean forceHttp,
- SdkManager sdkManager,
- ITaskMonitor monitor) {
- mInstalled.add(archive);
- return true;
- }
- };
- }
- }
-
- private static class MockTaskFactory implements ITaskFactory {
- public void start(String title, ITask task) {
- new MockTask(task);
- }
- }
-
- private static class MockTask implements ITaskMonitor {
- public MockTask(ITask task) {
- task.run(this);
- }
-
- public ITaskMonitor createSubMonitor(int tickCount) {
- return this;
- }
-
- public boolean displayPrompt(String title, String message) {
- return false;
- }
-
- public int getProgress() {
- return 0;
- }
-
- public void incProgress(int delta) {
- // ignore
- }
-
- public boolean isCancelRequested() {
- return false;
- }
-
- public void setDescription(String format, Object... args) {
- // ignore
- }
-
- public void setProgressMax(int max) {
- // ignore
- }
-
- public void log(String format, Object... args) {
- // ignore
- }
-
- public void logError(String format, Object... args) {
- // ignore
- }
-
- public void logVerbose(String format, Object... args) {
- // ignore
- }
- }
-
- private static class MockEmptyPackage extends Package {
- private final String mTestHandle;
-
- public MockEmptyPackage(String testHandle) {
- super(
- null /*source*/,
- null /*props*/,
- 0 /*revision*/,
- null /*license*/,
- null /*description*/,
- null /*descUrl*/,
- Os.ANY /*archiveOs*/,
- Arch.ANY /*archiveArch*/,
- null /*archiveOsPath*/
- );
- mTestHandle = testHandle;
- }
-
- @Override
- protected Archive createLocalArchive(
- Properties props,
- Os archiveOs,
- Arch archiveArch,
- String archiveOsPath) {
- return new Archive(this, props, archiveOs, archiveArch, archiveOsPath) {
- @Override
- public String toString() {
- return mTestHandle;
- }
- };
- }
-
- public Archive getLocalArchive() {
- return getArchives()[0];
- }
-
- @Override
- public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
- return null;
- }
-
- @Override
- public String getListDescription() {
- return this.getClass().getSimpleName();
- }
-
- @Override
- public String getShortDescription() {
- return this.getClass().getSimpleName();
- }
-
- @Override
- public boolean sameItemAs(Package pkg) {
- return false;
- }
-
- }
}