From: Raphael Moll Date: Thu, 21 Jul 2011 22:53:43 +0000 (-0700) Subject: SDK Manager2: revamp package diff logic. X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=d54047c70b383faeaf0d4591ba7f7009088c01e6;p=android-x86%2Fsdk.git SDK Manager2: revamp package diff logic. This revamps the diff algorithm used to merge the local and remote packages found during a repository load into the PkgItems displayed in the tree viewer. FYI all the Package and Archive sub-classes are getting an equals() and hashCode() methods to make them behave correctly when put it in a HashTable or Set. These are the auto-generated stuff from Eclipse. Change-Id: I1494a0ed44cd768eed252e3a81b9e74bf86d563c --- diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index af5c2374a..12645af2a 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -32,6 +32,7 @@ import org.w3c.dom.Node; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; import java.util.Properties; @@ -70,6 +71,44 @@ public class AddonPackage extends Package public String getDescription() { return mDescription; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode()); + result = prime * result + ((mName == null) ? 0 : mName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Lib)) { + return false; + } + Lib other = (Lib) obj; + if (mDescription == null) { + if (other.mDescription != null) { + return false; + } + } else if (!mDescription.equals(other.mDescription)) { + return false; + } + if (mName == null) { + if (other.mName != null) { + return false; + } + } else if (!mName.equals(other.mName)) { + return false; + } + return true; + } } private final Lib[] mLibs; @@ -389,4 +428,62 @@ public class AddonPackage extends Package return false; } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode()); + result = prime * result + Arrays.hashCode(mLibs); + result = prime * result + ((mName == null) ? 0 : mName.hashCode()); + result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode()); + result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof AddonPackage)) { + return false; + } + AddonPackage other = (AddonPackage) obj; + if (mLayoutlibVersion == null) { + if (other.mLayoutlibVersion != null) { + return false; + } + } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) { + return false; + } + if (!Arrays.equals(mLibs, other.mLibs)) { + return false; + } + if (mName == null) { + if (other.mName != null) { + return false; + } + } else if (!mName.equals(other.mName)) { + return false; + } + if (mVendor == null) { + if (other.mVendor != null) { + return false; + } + } else if (!mVendor.equals(other.mVendor)) { + return false; + } + if (mVersion == null) { + if (other.mVersion != null) { + return false; + } + } else if (!mVersion.equals(other.mVersion)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java index 4a2185102..fb28a9d16 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java @@ -384,4 +384,92 @@ public class Archive implements IDescription, Comparable { } return 0; } + + /** + * Note: An {@link Archive}'s hash code does NOT depend on the parent {@link Package} hash code. + *

+ * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mArch == null) ? 0 : mArch.hashCode()); + result = prime * result + ((mChecksum == null) ? 0 : mChecksum.hashCode()); + result = prime * result + ((mChecksumType == null) ? 0 : mChecksumType.hashCode()); + result = prime * result + (mIsLocal ? 1231 : 1237); + result = prime * result + ((mLocalOsPath == null) ? 0 : mLocalOsPath.hashCode()); + result = prime * result + ((mOs == null) ? 0 : mOs.hashCode()); + result = prime * result + (int) (mSize ^ (mSize >>> 32)); + result = prime * result + ((mUrl == null) ? 0 : mUrl.hashCode()); + return result; + } + + /** + * Note: An {@link Archive}'s equality does NOT depend on the parent {@link Package} equality. + *

+ * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Archive)) { + return false; + } + Archive other = (Archive) obj; + if (mArch == null) { + if (other.mArch != null) { + return false; + } + } else if (!mArch.equals(other.mArch)) { + return false; + } + if (mChecksum == null) { + if (other.mChecksum != null) { + return false; + } + } else if (!mChecksum.equals(other.mChecksum)) { + return false; + } + if (mChecksumType == null) { + if (other.mChecksumType != null) { + return false; + } + } else if (!mChecksumType.equals(other.mChecksumType)) { + return false; + } + if (mIsLocal != other.mIsLocal) { + return false; + } + if (mLocalOsPath == null) { + if (other.mLocalOsPath != null) { + return false; + } + } else if (!mLocalOsPath.equals(other.mLocalOsPath)) { + return false; + } + if (mOs == null) { + if (other.mOs != null) { + return false; + } + } else if (!mOs.equals(other.mOs)) { + return false; + } + if (mSize != other.mSize) { + return false; + } + if (mUrl == null) { + if (other.mUrl != null) { + return false; + } + } else if (!mUrl.equals(other.mUrl)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java index 5171454f4..b598f7d1f 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java @@ -253,4 +253,34 @@ public class DocPackage extends Package implements IPackageVersion { // not an upgrade but not incompatible either. return UpdateInfo.NOT_UPDATE; } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof DocPackage)) { + return false; + } + DocPackage other = (DocPackage) obj; + if (mVersion == null) { + if (other.mVersion != null) { + return false; + } + } else if (!mVersion.equals(other.mVersion)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java index ab074cedb..ac8dd09ec 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 @@ -29,6 +29,7 @@ import org.w3c.dom.Node; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; @@ -564,4 +565,50 @@ public class ExtraPackage extends MinToolsPackage return null; } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + mMinApiLevel; + result = prime * result + ((mPath == null) ? 0 : mPath.hashCode()); + result = prime * result + Arrays.hashCode(mProjectFiles); + result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof ExtraPackage)) { + return false; + } + ExtraPackage other = (ExtraPackage) obj; + if (mMinApiLevel != other.mMinApiLevel) { + return false; + } + if (mPath == null) { + if (other.mPath != null) { + return false; + } + } else if (!mPath.equals(other.mPath)) { + return false; + } + if (!Arrays.equals(mProjectFiles, other.mProjectFiles)) { + return false; + } + if (mVendor == null) { + if (other.mVendor != null) { + return false; + } + } else if (!mVendor.equals(other.mVendor)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java index 88b778d02..32d3ef3f6 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LayoutlibVersionMixin.java @@ -97,4 +97,34 @@ public class LayoutlibVersionMixin implements ILayoutlibVersion { public Pair getLayoutlibVersion() { return mLayoutlibVersion; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LayoutlibVersionMixin)) { + return false; + } + LayoutlibVersionMixin other = (LayoutlibVersionMixin) obj; + if (mLayoutlibVersion == null) { + if (other.mLayoutlibVersion != null) { + return false; + } + } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java index 661a73c10..56f62478c 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/MinToolsPackage.java @@ -98,4 +98,30 @@ public abstract class MinToolsPackage extends Package implements IMinToolsDepend props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision())); } } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + mMinToolsRevision; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof MinToolsPackage)) { + return false; + } + MinToolsPackage other = (MinToolsPackage) obj; + if (mMinToolsRevision != other.mMinToolsRevision) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java index a9c5996d6..fe083d1f3 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java @@ -28,6 +28,7 @@ import org.w3c.dom.Node; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; import java.util.Properties; @@ -450,6 +451,15 @@ public abstract class Package implements IDescription, Comparable { } /** + * A package is local (that is 'installed locally') if it contains a single + * archive that is local. If not local, it's a remote package, only available + * on a remote source for download and installation. + */ + public boolean isLocal() { + return mArchives.length == 1 && mArchives[0].isLocal(); + } + + /** * Computes a potential installation folder if an archive of this package were * to be installed right away in the given SDK root. *

@@ -651,4 +661,49 @@ public abstract class Package implements IDescription, Comparable { return sb.toString(); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(mArchives); + result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode()); + result = prime * result + mRevision; + result = prime * result + ((mSource == null) ? 0 : mSource.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Package)) { + return false; + } + Package other = (Package) obj; + if (!Arrays.equals(mArchives, other.mArchives)) { + return false; + } + if (mObsolete == null) { + if (other.mObsolete != null) { + return false; + } + } else if (!mObsolete.equals(other.mObsolete)) { + return false; + } + if (mRevision != other.mRevision) { + return false; + } + if (mSource == null) { + if (other.mSource != null) { + return false; + } + } else if (!mSource.equals(other.mSource)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java index 74b8cc767..6035ef5f8 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java @@ -261,4 +261,51 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion, return false; } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode()); + result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); + result = prime * result + ((mVersionName == null) ? 0 : mVersionName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof PlatformPackage)) { + return false; + } + PlatformPackage other = (PlatformPackage) obj; + if (mLayoutlibVersion == null) { + if (other.mLayoutlibVersion != null) { + return false; + } + } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) { + return false; + } + if (mVersion == null) { + if (other.mVersion != null) { + return false; + } + } else if (!mVersion.equals(other.mVersion)) { + return false; + } + if (mVersionName == null) { + if (other.mVersionName != null) { + return false; + } + } else if (!mVersionName.equals(other.mVersionName)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java index 69039ea08..bac12530e 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java @@ -352,4 +352,30 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency // get the return code from the process return process.waitFor(); } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + mMinPlatformToolsRevision; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof ToolPackage)) { + return false; + } + ToolPackage other = (ToolPackage) obj; + if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) { + return false; + } + return true; + } } 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 2df670bf0..925f08891 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 @@ -20,7 +20,6 @@ import com.android.sdklib.internal.repository.Archive; import com.android.sdklib.internal.repository.IPackageVersion; import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITaskMonitor; -import com.android.sdklib.internal.repository.LocalSdkParser; import com.android.sdklib.internal.repository.Package; import com.android.sdklib.internal.repository.SdkSource; import com.android.sdklib.internal.repository.Package.UpdateInfo; @@ -44,10 +43,9 @@ class PackageLoader { * Interface for the callback called by * {@link PackageLoader#loadPackages(ISourceLoadedCallback)}. *

- * After processing each source, the package loader calls {@link #onSourceLoaded(List)} - * with the list of package items found in that source. The client should process that - * list as it want, typically by accumulating the package items in a list of its own. - * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to + * After processing each source, the package loader calls {@link #onUpdateSource} + * with the list of packages found in that source. + * By returning true from {@link #onUpdateSource}, the client tells the loader to * continue and process the next source. By returning false, it tells to stop loading. *

* The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no @@ -57,20 +55,19 @@ class PackageLoader { public interface ISourceLoadedCallback { /** * After processing each source, the package loader calls this method with the - * list of package items found in that source. The client should process that - * list as it want, typically by accumulating the package items in a list of its own. - * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to - * continue and process the next source. By returning false, it tells to stop loading. + * list of packages found in that source. + * By returning true from {@link #onUpdateSource}, the client tells + * the loader to continue and process the next source. + * By returning false, it tells to stop loading. *

- * Important: This method is called from a sub-thread, so clients who try - * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)} - * or {@link Display#asyncExec(Runnable)}. + * Important: This method is called from a sub-thread, so clients which + * try to access any UI widgets must wrap their calls into + * {@link Display#syncExec(Runnable)} or {@link Display#asyncExec(Runnable)}. * - * @param pkgItems All the package items loaded from the last processed source. - * This is a copy and the client can hold to this list or modify it in any way. + * @param packages All the packages loaded from the source. Never null. * @return True if the load operation should continue, false if it should stop. */ - public boolean onSourceLoaded(List pkgItems); + public boolean onUpdateSource(SdkSource source, Package[] packages); /** * This method is guaranteed to be called at the end, no matter how the @@ -141,14 +138,15 @@ class PackageLoader { } // get local packages and offer them to the callback - List localPkgItems = loadLocalPackages(); - if (!localPkgItems.isEmpty()) { - if (!sourceLoadedCallback.onSourceLoaded(localPkgItems)) { - return; - } + Package[] localPkgs = mUpdaterData.getInstalledPackages(); + if (localPkgs == null) { + localPkgs = new Package[0]; + } + if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) { + return; } - final int[] numPackages = { localPkgItems.size() }; + final int[] numPackages = { localPkgs == null ? 0 : localPkgs.length }; // get remote packages final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); @@ -167,17 +165,11 @@ class PackageLoader { continue; } - List sourcePkgItems = new ArrayList(); - for(Package pkg : pkgs) { - PkgItem pi = new PkgItem(pkg, PkgState.NEW); - sourcePkgItems.add(pi); - } - - numPackages[0] += sourcePkgItems.size(); + numPackages[0] += pkgs.length; // Notify the callback a new source has finished loading. // If the callback requests so, stop right away. - if (!sourceLoadedCallback.onSourceLoaded(sourcePkgItems)) { + if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) { return; } } @@ -196,26 +188,6 @@ class PackageLoader { } /** - * Internal method that returns all installed packages from the {@link LocalSdkParser} - * associated with the {@link UpdaterData}. - *

- * Note that the {@link LocalSdkParser} maintains a cache, so callers need to clear - * it if they know they changed the local installation. - * - * @return A new list of {@link PkgItem}. May be empty but never null. - */ - private List loadLocalPackages() { - List pkgItems = new ArrayList(); - - for (Package pkg : mUpdaterData.getInstalledPackages()) { - PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED); - pkgItems.add(pi); - } - - return pkgItems; - } - - /** * Load packages, source by source using {@link #loadPackages(ISourceLoadedCallback)}, * and executes the given {@link IAutoInstallTask} on the current package list. * That is for each package known, the install task is queried to find if @@ -245,59 +217,44 @@ class PackageLoader { public void loadPackagesWithInstallTask(final IAutoInstallTask installTask) { loadPackages(new ISourceLoadedCallback() { - public boolean onSourceLoaded(List pkgItems) { - for (PkgItem item : pkgItems) { - Package acceptedPkg = null; - switch(item.getState()) { - case NEW: - if (installTask.acceptPackage(item.getMainPackage())) { - acceptedPkg = item.getMainPackage(); - } - if (item.hasUpdatePkg() && installTask.acceptPackage(item.getUpdatePkg())) { - acceptedPkg = item.getUpdatePkg(); - } - break; - case INSTALLED: - if (installTask.acceptPackage(item.getMainPackage())) { + public boolean onUpdateSource(SdkSource source, Package[] packages) { + for (Package pkg : packages) { + if (pkg.isLocal()) { + // This is a local (aka installed) package + if (installTask.acceptPackage(pkg)) { // If the caller is accepting an installed package, // return a success and give the package's install path - acceptedPkg = item.getMainPackage(); - Archive[] a = acceptedPkg.getArchives(); + Archive[] a = pkg.getArchives(); // an installed package should have one local compatible archive if (a.length == 1 && a[0].isCompatible()) { installTask.setResult( - acceptedPkg, + pkg, true /*success*/, new File(a[0].getLocalOsPath())); - - // return false to tell loadPackages() that we don't - // need to continue processing any more sources. - return false; } + // return false to tell loadPackages() that we don't + // need to continue processing any more sources. + return false; } - } - if (acceptedPkg != null) { - // Try to install this package if it has one compatible archive. - Archive archiveToInstall = null; + } else { + // This is a remote package + if (installTask.acceptPackage(pkg)) { + // The caller is accepting this remote package. Let's try to install it. - for (Archive a2 : acceptedPkg.getArchives()) { - if (a2.isCompatible()) { - archiveToInstall = a2; - break; + for (Archive archive : pkg.getArchives()) { + if (archive.isCompatible()) { + installArchive(archive); + break; + } } + // return false to tell loadPackages() that we don't + // need to continue processing any more sources. + return false; } - - if (archiveToInstall != null) { - installArchive(archiveToInstall); - } - - // return false to tell loadPackages() that we don't - // need to continue processing any more sources. - return false; } - } + // Tell loadPackages() to process the next source. return true; } @@ -342,11 +299,10 @@ class PackageLoader { // The local package list has changed, make sure to refresh it mUpdaterData.getLocalSdkParser().clearPackages(); - final List localPkgItems = loadLocalPackages(); + final Package[] localPkgs = mUpdaterData.getInstalledPackages(); // Try to locate the installed package in the new package list - for (PkgItem localItem : localPkgItems) { - Package localPkg = localItem.getMainPackage(); + for (Package localPkg : localPkgs) { if (localPkg.canBeUpdatedBy(packageToInstall) == UpdateInfo.NOT_UPDATE) { Archive[] localArchive = localPkg.getArchives(); if (localArchive.length == 1 && localArchive[0].isCompatible()) { @@ -419,7 +375,7 @@ class PackageLoader { } public boolean hasUpdatePkg() { - return mState == PkgState.INSTALLED && mUpdatePkg != null; + return mUpdatePkg != null; } public String getName() { 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 36dcd49a3..fd5f3c90a 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 @@ -21,16 +21,19 @@ import com.android.annotations.VisibleForTesting.Visibility; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.internal.repository.Archive; import com.android.sdklib.internal.repository.IDescription; +import com.android.sdklib.internal.repository.IPackageVersion; import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.Package; import com.android.sdklib.internal.repository.PlatformPackage; import com.android.sdklib.internal.repository.PlatformToolPackage; +import com.android.sdklib.internal.repository.SdkRepoSource; import com.android.sdklib.internal.repository.SdkSource; import com.android.sdklib.internal.repository.ToolPackage; import com.android.sdkuilib.internal.repository.PackageLoader.ISourceLoadedCallback; import com.android.sdkuilib.internal.repository.PackageLoader.PkgItem; import com.android.sdkuilib.internal.repository.PackageLoader.PkgState; +import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic.UpdateOp; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.repository.ISdkChangeListener; import com.android.sdkuilib.ui.GridDataBuilder; @@ -68,6 +71,7 @@ import org.eclipse.swt.widgets.TreeColumn; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -125,7 +129,7 @@ public class PackagesPage extends UpdaterPage private final Map mMenuActions = new HashMap(); - private final PackagesPageLogic mLogic; + private final PackagesDiffLogic mDiffLogic; private boolean mDisplayArchives = false; private Text mTextSdkOsPath; @@ -152,19 +156,14 @@ public class PackagesPage extends UpdaterPage public PackagesPage(Composite parent, int swtStyle, UpdaterData updaterData) { super(parent, swtStyle); - mLogic = new PackagesPageLogic(updaterData) { - @Override - boolean keepItem(PkgItem item) { - return PackagesPage.this.keepItem(item); - } - }; + mDiffLogic = new PackagesDiffLogic(updaterData); createContents(this); postCreate(); //$hide$ } public void onPageSelected() { - if (mLogic.mAllPkgItems.isEmpty()) { + if (mDiffLogic.mCurrentCategories == null || mDiffLogic.mCurrentCategories.isEmpty()) { // Initialize the package list the first time the page is shown. loadPackages(); } @@ -202,7 +201,7 @@ public class PackagesPage extends UpdaterPage mTree.setHeaderVisible(true); GridDataBuilder.create(mTree).fill().grab(); - // column name icon is set in sortPackages() depending on the current filter type + // column name icon is set when loading depending on the current filter type // (e.g. API level or source) mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE); mTreeColumnName = mColumnName.getColumn(); @@ -244,7 +243,7 @@ public class PackagesPage extends UpdaterPage mCheckFilterNew.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - sortPackages(true /*updateButtons*/); + loadPackages(); } }); mCheckFilterNew.setSelection(true); @@ -254,7 +253,7 @@ public class PackagesPage extends UpdaterPage mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - sortPackages(true /*updateButtons*/); + loadPackages(); } }); mCheckFilterInstalled.setSelection(true); @@ -266,7 +265,7 @@ public class PackagesPage extends UpdaterPage mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - sortPackages(true /*updateButtons*/); + loadPackages(); } }); mCheckFilterObsolete.setSelection(false); @@ -304,9 +303,9 @@ public class PackagesPage extends UpdaterPage mCheckSortApi.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - sortPackages(true /*updateButtons*/); + loadPackages(); // Reset the expanded state when changing sort algorithm - expandInitial(mLogic.mCurrentCategories); + expandInitial(mDiffLogic.mCurrentCategories); } }); mCheckSortApi.setText("API level"); @@ -318,9 +317,9 @@ public class PackagesPage extends UpdaterPage mCheckSortSource.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - sortPackages(true /*updateButtons*/); + loadPackages(); // Reset the expanded state when changing sort algorithm - expandInitial(mLogic.mCurrentCategories); + expandInitial(mDiffLogic.mCurrentCategories); } }); @@ -351,8 +350,8 @@ public class PackagesPage extends UpdaterPage } private Image getImage(String filename) { - if (mLogic.mUpdaterData != null) { - ImageFactory imgFactory = mLogic.mUpdaterData.getImageFactory(); + if (mDiffLogic.mUpdaterData != null) { + ImageFactory imgFactory = mDiffLogic.mUpdaterData.getImageFactory(); if (imgFactory != null) { return imgFactory.getImageByName(filename); } @@ -379,14 +378,14 @@ public class PackagesPage extends UpdaterPage loadPackages(); break; case SHOW_ADDON_SITES: - AddonSitesDialog d = new AddonSitesDialog(getShell(), mLogic.mUpdaterData); + AddonSitesDialog d = new AddonSitesDialog(getShell(), mDiffLogic.mUpdaterData); if (d.open()) { loadPackages(); } break; case TOGGLE_SHOW_ARCHIVES: mDisplayArchives = !mDisplayArchives; - sortPackages(true /*updateButtons*/); + loadPackages(); break; case TOGGLE_SHOW_INSTALLED_PKG: button = mCheckFilterInstalled; @@ -484,8 +483,8 @@ public class PackagesPage extends UpdaterPage } private void postCreate() { - if (mLogic.mUpdaterData != null) { - mTextSdkOsPath.setText(mLogic.mUpdaterData.getOsSdkRoot()); + if (mDiffLogic.mUpdaterData != null) { + mTextSdkOsPath.setText(mDiffLogic.mUpdaterData.getOsSdkRoot()); } mTreeViewer.setContentProvider(new PkgContentProvider()); @@ -512,55 +511,25 @@ public class PackagesPage extends UpdaterPage } private void loadPackages() { - if (mLogic.mUpdaterData == null) { + if (mDiffLogic.mUpdaterData == null) { return; } - final boolean firstLoad = mLogic.mAllPkgItems.isEmpty(); - // 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. - mLogic.mPackageLoader.loadPackages(new ISourceLoadedCallback() { - public boolean onSourceLoaded(List newPkgItems) { - boolean somethingNew = false; - - synchronized(mLogic.mAllPkgItems) { - nextNewItem: for (PkgItem newItem : newPkgItems) { - for (PkgItem existingItem : mLogic.mAllPkgItems) { - if (existingItem.isSameItemAs(newItem)) { - // This isn't a new package, we already have it. - continue nextNewItem; - } - } - mLogic.mAllPkgItems.add(newItem); - somethingNew = true; - } - } + final boolean useSortByApi = isSortByApi(); + final UpdateOp op = mDiffLogic.updateStart(useSortByApi); + mDiffLogic.mPackageLoader.loadPackages(new ISourceLoadedCallback() { + boolean needsRefresh = mDiffLogic.isSortByApi() == useSortByApi; - if (somethingNew) { - // Dynamically update the table while we load after each source. - // Since the official Android source gets loaded first, it makes the - // window look non-empty a lot sooner. - if (!mGroupPackages.isDisposed()) { - mGroupPackages.getDisplay().syncExec(new Runnable() { - public void run() { - sortPackages(true /* updateButtons */); - - if (!mGroupPackages.isDisposed()) { - if (firstLoad) { - // set the initial expanded state - expandInitial(mLogic.mCurrentCategories); - } - updateButtonsState(); - updateMenuCheckmarks(); - } - } - }); - } + public boolean onUpdateSource(SdkSource source, Package[] newPackages) { + if (mDiffLogic.updateSourcePackages(op, source, newPackages) || needsRefresh) { + refreshViewerSync(); + needsRefresh = false; } // Return true to tell the loader to continue with the next source. @@ -570,73 +539,45 @@ public class PackagesPage extends UpdaterPage } public void onLoadCompleted() { - if (firstLoad && !mGroupPackages.isDisposed()) { - updateButtonsState(); - updateMenuCheckmarks(); + if (mDiffLogic.updateEnd(op) || needsRefresh) { + refreshViewerSync(); + needsRefresh = false; } } }); } - private void sortPackages(boolean updateButtons) { - if (isSortByApi()) { - sortByApiLevel(); - } else { - sortBySource(); - } - if (updateButtons) { - updateButtonsState(); - updateMenuCheckmarks(); - } - } - - private boolean isSortByApi() { - return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection(); - } - - /** - * 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. - */ - private void sortByApiLevel() { + private void refreshViewerSync() { + // Dynamically update the table while we load after each source. + // Since the official Android source gets loaded first, it makes the + // window look non-empty a lot sooner. + if (!mGroupPackages.isDisposed()) { + mGroupPackages.getDisplay().syncExec(new Runnable() { + public void run() { + if (!mGroupPackages.isDisposed()) { - if (!mTreeColumnName.isDisposed()) { - mTreeColumnName.setImage(getImage(ICON_SORT_BY_API)); - } + if (mTreeViewer.getInput() != mDiffLogic.mCurrentCategories) { + // set initial input + mTreeViewer.setInput(mDiffLogic.mCurrentCategories); + } else { + // refresh existing, which preserves the expanded state, the selection + // and the checked state. + mTreeViewer.refresh(); + } - mLogic.sortByApiLevel(); + // set the initial expanded state + expandInitial(mDiffLogic.mCurrentCategories); - if (mTreeViewer.getInput() != mLogic.mCurrentCategories) { - // set initial input - mTreeViewer.setInput(mLogic.mCurrentCategories); - } else { - // refresh existing, which preserves the expanded state, the selection - // and the checked state. - mTreeViewer.refresh(); + updateButtonsState(); + updateMenuCheckmarks(); + } + } + }); } } - /** - * Recompute the tree by sorting all packages by source. - */ - private void sortBySource() { - - if (!mTreeColumnName.isDisposed()) { - mTreeColumnName.setImage(getImage(ICON_SORT_BY_SOURCE)); - } - - mLogic.sortBySource(); - - // We don't support in-place incremental updates so the table gets reset - // each time we load when sorted by source. - 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(); - } + private boolean isSortByApi() { + return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection(); } /** @@ -820,8 +761,8 @@ public class PackagesPage extends UpdaterPage private void onSelectNewUpdates() { ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider(); - synchronized(mLogic.mAllPkgItems) { - for (PkgCategory cat : mLogic.mCurrentCategories) { + synchronized(mDiffLogic.mCurrentCategories) { + for (PkgCategory cat : mDiffLogic.mCurrentCategories) { boolean selected = false; for (PkgItem item : cat.getItems()) { if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) { @@ -895,34 +836,18 @@ public class PackagesPage extends UpdaterPage } } - if (mLogic.mUpdaterData != null) { + if (mDiffLogic.mUpdaterData != null) { try { beginOperationPending(); - mLogic.mUpdaterData.updateOrInstallAll_WithGUI( + mDiffLogic.mUpdaterData.updateOrInstallAll_WithGUI( archives, mCheckFilterObsolete.getSelection() /* includeObsoletes */); } finally { endOperationPending(); - // 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(mLogic.mAllPkgItems) { - for (Archive a : archives) { - for (Iterator it = mLogic.mAllPkgItems.iterator(); - it.hasNext(); ) { - PkgItem pi = it.next(); - if (pi.hasArchive(a)) { - it.remove(); - break; - } - } - } - } - // The local package list has changed, make sure to refresh it - mLogic.mUpdaterData.getLocalSdkParser().clearPackages(); + mDiffLogic.mUpdaterData.getLocalSdkParser().clearPackages(); loadPackages(); } } @@ -970,7 +895,7 @@ public class PackagesPage extends UpdaterPage try { beginOperationPending(); - mLogic.mUpdaterData.getTaskFactory().start("Delete Package", new ITask() { + mDiffLogic.mUpdaterData.getTaskFactory().start("Delete Package", new ITask() { public void run(ITaskMonitor monitor) { monitor.setProgressMax(archives.size() + 1); for (Entry entry : archives.entrySet()) { @@ -980,13 +905,9 @@ public class PackagesPage extends UpdaterPage a.getParentPackage().getShortDescription(), a.getLocalOsPath()); - // Delete the actual package and its internal representation + // Delete the actual package a.deleteLocal(); - synchronized(mLogic.mAllPkgItems) { - mLogic.mAllPkgItems.remove(entry.getValue()); - } - monitor.incProgress(1); if (monitor.isCancelRequested()) { break; @@ -1001,7 +922,7 @@ public class PackagesPage extends UpdaterPage endOperationPending(); // The local package list has changed, make sure to refresh it - mLogic.mUpdaterData.getLocalSdkParser().clearPackages(); + mDiffLogic.mUpdaterData.getLocalSdkParser().clearPackages(); loadPackages(); } } @@ -1027,7 +948,7 @@ public class PackagesPage extends UpdaterPage if (element instanceof PkgCategory) { return ((PkgCategory) element).getLabel(); } else if (element instanceof PkgItem) { - return getPkgItemname((PkgItem) element); + return getPkgItemName((PkgItem) element); } else if (element instanceof IDescription) { return ((IDescription) element).getShortDescription(); } @@ -1081,13 +1002,14 @@ public class PackagesPage extends UpdaterPage return ""; } - private String getPkgItemname(PkgItem item) { + private String getPkgItemName(PkgItem item) { String name = item.getName().trim(); if (isSortByApi()) { // When sorting by API, the package name might contains the API number // or the platform name at the end. If we find it, cut it out since it's // redundant. + // TODO deal with obsolete packages PkgApiCategory cat = (PkgApiCategory) findCategoryForItem(item); String apiLabel = cat.getApiLabel(); @@ -1105,7 +1027,7 @@ public class PackagesPage extends UpdaterPage } private PkgCategory findCategoryForItem(PkgItem item) { - for (PkgCategory cat : mLogic.mCurrentCategories) { + for (PkgCategory cat : mDiffLogic.mCurrentCategories) { for (PkgItem i : cat.getItems()) { if (i == item) { return cat; @@ -1118,7 +1040,7 @@ public class PackagesPage extends UpdaterPage @Override public Image getImage(Object element) { - ImageFactory imgFactory = mLogic.mUpdaterData.getImageFactory(); + ImageFactory imgFactory = mDiffLogic.mUpdaterData.getImageFactory(); if (imgFactory != null) { if (mColumn == mColumnName) { @@ -1248,6 +1170,8 @@ public class PackagesPage extends UpdaterPage private final Object mIconRef; private final List mItems = new ArrayList(); private String mLabel; + /** Transient flag used during incremental updates. */ + private boolean mUnused; public PkgCategory(Object key, String label, Object iconRef) { mKey = key; @@ -1275,6 +1199,14 @@ public class PackagesPage extends UpdaterPage return mItems; } + public void setUnused(boolean unused) { + mUnused = unused; + } + + public boolean isUnused() { + return mUnused; + } + @Override public String toString() { return String.format("%s ", @@ -1389,9 +1321,10 @@ public class PackagesPage extends UpdaterPage /** * A special {@link SdkSource} object that represents the locally installed - * items, or more exactly a lack of remote source. Value is {@code null}. + * items, or more exactly a lack of remote source. */ - public final static SdkSource UNKNOWN_SOURCE = null; + public final static SdkSource UNKNOWN_SOURCE = + new SdkRepoSource("http://no.source", "Local Packages"); private final SdkSource mSource; public PkgSourceCategory(SdkSource source, UpdaterData updaterData) { @@ -1408,7 +1341,7 @@ public class PackagesPage extends UpdaterPage public String toString() { return String.format("%s ", this.getClass().getSimpleName(), - mSource == UNKNOWN_SOURCE ? "Local" : mSource.toString(), + mSource.toString(), getItems().size()); } @@ -1437,477 +1370,516 @@ public class PackagesPage extends UpdaterPage /** - * Helper class that separate the logic of package management from the UI + * Helper class that separates the logic of package management from the UI * so that we can test it using head-less unit tests. */ - static abstract class PackagesPageLogic { + static class PackagesDiffLogic { final PackageLoader mPackageLoader; final UpdaterData mUpdaterData; final List mApiCategories = new ArrayList(); final List mSourceCategories = new ArrayList(); List mCurrentCategories = mApiCategories; - /** Access to this list must be synchronized on {@link #mAllPkgItems}. */ - final List mAllPkgItems = new ArrayList(); - public PackagesPageLogic(UpdaterData updaterData) { + public PackagesDiffLogic(UpdaterData updaterData) { mUpdaterData = updaterData; mPackageLoader = new PackageLoader(updaterData); } /** - * Private interface used by {@link PackagesPageLogic#sort(ISortOperation)}. - * The sort() method only focuses on the incremental update part of the sort. - * The operation interface tells what to sort, how to extract keys from items - * to bucket them in categeories, how to create categories, what categories are - * by default and finally how to sort these categories. + * An update operation, customized to either sort by API or sort by source. */ - private interface ISortOperation { - /** The list of categories to update in-place. */ - List getCategories(); - - /** Create all the default categories (e.g. local installed packages, tools. etc.) */ - void addDefaultCategories( - List currentCategories, - Map categoryKeyMap, - Set unusedCategoryKey, - ImageFactory imgFactory); - - /** Extracts the category key from a given item. */ - public Object getCategoryKey(PkgItem item); - - /** Creates a new category object for the given key. */ - PkgCategory createCategory(Object catKey, ImageFactory imgFactory); - - /** - * Process a new item and merge it into the existing categories, - * return true if the item was already in the category items list. - * - * @return True if the {@code newItem} was found in the category's item list. - * When returning true, the method should remove the package(s) from - * {@code unusedPackages}. - * If the method return false, the caller will add {@code newItem} to the - * category's items list. - */ - boolean mergeNewItem( - PkgItem newItem, - PkgCategory cat, - List cats, - Set unusedPackages); - - /** - * Post process items/categories after an item from mAllPkgItems as been filtered. - * Used by the API sort to see if we can infer the category name from a platform - * package when we don't have this info in the package manager. - */ - void postProcessItem(Object catKey, PkgCategory category, PkgItem item); - - /** Final step, sort the list of categories. */ - void sortCategoryList(List categoryList); + abstract class UpdateOp { + public final Set mVisitedSources = new HashSet(); + + /** Retrieve the category key for the given package, either local or remote. */ + public abstract Object getCategoryKey(Package pkg); + /** Modified {@code currentCategories} to add default categories. */ + public abstract void addDefaultCategories(List currentCategories); + /** Creates the category for the given key and returns it. */ + public abstract PkgCategory createCategory(Object catKey); + /** Sorts the category list (but not the items within the categories.) */ + public abstract void sortCategoryList(List categoryList); + /** Called after items of a given category have changed. Used to sort the + * items and/or adjust the category name. */ + public abstract void postCategoryItemsChanged(List categoryList); + /** Add the new package or merge it as an update or does nothing if this package + * is already part of the category items. + * Returns true if the category item list has changed. */ + public abstract boolean mergeNewPackage(Package newPackage, PkgCategory cat); + } + + public boolean isSortByApi() { + return mCurrentCategories == mApiCategories; + } + + public UpdateOp updateStart(boolean sortByApi) { + mCurrentCategories = sortByApi ? mApiCategories : mSourceCategories; + + UpdateOp info = sortByApi ? (new UpdateOpApi()) : (new UpdateOpSource()); + + // Note that default categories are created after the unused ones so that + // the callback can decide whether they should be marked as unused or not. + for (PkgCategory cat : mCurrentCategories) { + cat.setUnused(true); + } + + info.addDefaultCategories(mCurrentCategories); + + return info; } - /** - * Recompute the tree by sorting all the {@link PkgItem}s into the category buckets. - * This does an update in-place of the mCurrentCategories list so that the table - * can preserve its state (checked / expanded / selected) properly. - *

- * Since this is shared between both the per-API and the per-source sort, care must - * be taken to either not change the displayed PkgItem or make the change compatible - * with both displays. Otherwise it looks odd when the displayed items change - * when changing the sorting mode in the UI. - * FIXME currently there's an issue about that wrt items that have available updates. - * - * @param op The actual details of the sort, which allows us to - * reuse the same method for both sorting by API or by SdkSource. - * @see ISortOperation - */ - private void sort(ISortOperation op) { - ImageFactory imgFactory = mUpdaterData.getImageFactory(); - - List cats = mCurrentCategories = op.getCategories(); - - // 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 categoryKeyMap = new HashMap(); - final Set unusedCategoryKey = new HashSet(); - final Set unusedPackages = new HashSet(); - - // Get existing categories and packages - for (PkgCategory cat : cats) { - categoryKeyMap.put(cat.getKey(), cat); - unusedCategoryKey.add(cat.getKey()); - - for (PkgItem pi : cat.getItems()) { - unusedPackages.add(pi.getMainPackage()); - if (pi.hasUpdatePkg()) { - unusedPackages.add(pi.getUpdatePkg()); + public boolean updateSourcePackages(UpdateOp op, SdkSource source, Package[] newPackages) { + if (newPackages.length > 0) { + op.mVisitedSources.add(source); + } + if (source == null) { + return processLocals(op, newPackages); + } else { + return processSource(op, source, newPackages); + } + } + + public boolean updateEnd(UpdateOp op) { + boolean hasChanged = false; + + // Remove unused categories + for (Iterator catIt = mCurrentCategories.iterator(); catIt.hasNext(); ) { + PkgCategory cat = catIt.next(); + if (cat.isUnused()) { + catIt.remove(); + hasChanged = true; + continue; + } + + // Remove all items which source we have not been visited. They are obsolete. + for (Iterator itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) { + PkgItem item = itemIt.next(); + if (!op.mVisitedSources.contains(item.getSource())) { + itemIt.remove(); + hasChanged = true; } } } + return hasChanged; + } - op.addDefaultCategories(cats, categoryKeyMap, unusedCategoryKey, imgFactory); + /** Process all local packages. Returns true if something changed. + * @param op */ + private boolean processLocals(UpdateOp op, Package[] packages) { + boolean hasChanged = false; + Set newPackages = new HashSet(Arrays.asList(packages)); + Set unusedPackages = new HashSet(newPackages); - // 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; - } + assert newPackages.size() == packages.length; - Object catKey = op.getCategoryKey(newItem); - PkgCategory cat = categoryKeyMap.get(catKey); + // Upgrade 'new' items to 'installed' for any local package we already know about + for (PkgCategory cat : mCurrentCategories) { + List items = cat.getItems(); + for (int i = 0; i < items.size(); i++) { + PkgItem item = items.get(i); - if (cat == null) { - // This is a new category. Create it and add it to the list. - cat = op.createCategory(catKey, imgFactory); - // It should not matter where we add to the list since we'll sort - // the categories at the end. - cats.add(cat); - categoryKeyMap.put(cat.getKey(), cat); - } else { - // Remove the category key from the unused list. - unusedCategoryKey.remove(catKey); + if (item.hasUpdatePkg() && newPackages.contains(item.getUpdatePkg())) { + // This item has an update package that is now installed. + PkgItem installed = new PkgItem(item.getUpdatePkg(), PkgState.INSTALLED); + unusedPackages.remove(item.getUpdatePkg()); + item.removeUpdate(); + items.add(installed); + cat.setUnused(false); + hasChanged = true; } - // Check whether the item is already present or merge it if it's an update - boolean found = op.mergeNewItem(newItem, cat, cats, unusedPackages); - - if (!found) { - cat.getItems().add(newItem); - unusedPackages.remove(newItem.getMainPackage()); - unusedPackages.remove(newItem.getUpdatePkg()); + if (newPackages.contains(item.getMainPackage())) { + unusedPackages.remove(item.getMainPackage()); + if (item.getState() == PkgState.NEW) { + // This item has a main package that is now installed. + item.setState(PkgState.INSTALLED); + cat.setUnused(false); + hasChanged = true; + } } + } + } - op.postProcessItem(catKey, cat, newItem); + // Downgrade 'installed' items to 'new' if their package isn't listed anymore + for (PkgCategory cat : mCurrentCategories) { + for (PkgItem item : cat.getItems()) { + if (item.getState() == PkgState.INSTALLED && + !newPackages.contains(item.getMainPackage())) { + item.setState(PkgState.NEW); + hasChanged = true; + } } } - // Now go through all the remaining categories used for the tree and clear unused items. - for (Iterator iterCat = cats.iterator(); iterCat.hasNext(); ) { - PkgCategory cat = iterCat.next(); + // Create new 'installed' items for any local package we haven't processed yet + for (Package newPackage : unusedPackages) { + Object catKey = op.getCategoryKey(newPackage); + PkgCategory cat = findCurrentCategory(mCurrentCategories, catKey); - // Remove any unused categories. - if (unusedCategoryKey.contains(cat.getKey())) { - iterCat.remove(); - continue; + if (cat == null) { + // This is a new category. Create it and add it to the list. + cat = op.createCategory(catKey); + mCurrentCategories.add(cat); + op.sortCategoryList(mCurrentCategories); } - // Remove any unused items in the category. - for (Iterator iterItem = cat.getItems().iterator(); iterItem.hasNext(); ) { - PkgItem item = iterItem.next(); + cat.getItems().add(new PkgItem(newPackage, PkgState.INSTALLED)); + cat.setUnused(false); + hasChanged = true; + } - if (unusedPackages.contains(item.getMainPackage())) { - iterItem.remove(); - } else if (item.hasUpdatePkg() && - unusedPackages.contains(item.getUpdatePkg())) { - item.removeUpdate(); + if (hasChanged) { + op.postCategoryItemsChanged(mCurrentCategories); + } + + return hasChanged; + } + + /** Process all remote packages. Returns true if something changed. + * @param op */ + private boolean processSource(UpdateOp op, SdkSource source, Package[] packages) { + boolean hasChanged = false; + // Note: unusedPackages must respect the original packages order. It can't be a set. + List unusedPackages = new ArrayList(Arrays.asList(packages)); + Set newPackages = new HashSet(unusedPackages); + + assert newPackages.size() == packages.length; + + // Remove any items or updates that are no longer in the source's packages + for (PkgCategory cat : mCurrentCategories) { + List items = cat.getItems(); + for (int i = 0; i < items.size(); i++) { + PkgItem item = items.get(i); + SdkSource itemSource = item.getSource(); + + // Only process items matching the current source + if (!(itemSource == source || (source != null && source.equals(itemSource)))) { + continue; + } + // Installed items have been dealt with the local source, + // so only change new items here + if (item.getState() == PkgState.NEW && + !newPackages.contains(item.getMainPackage())) { + // This package is no longer part of the source. + items.remove(i--); + hasChanged = true; + continue; + } + + cat.setUnused(false); + unusedPackages.remove(item.getMainPackage()); + + if (item.hasUpdatePkg()) { + if (newPackages.contains(item.getUpdatePkg())) { + unusedPackages.remove(item.getUpdatePkg()); + } else { + // This update is no longer part of the source + item.removeUpdate(); + hasChanged = true; + } } } + } - // Sort the items - Collections.sort(cat.getItems()); + // Add any new unknown packages + for (Package newPackage : unusedPackages) { + Object catKey = op.getCategoryKey(newPackage); + PkgCategory cat = findCurrentCategory(mCurrentCategories, catKey); + + if (cat == null) { + // This is a new category. Create it and add it to the list. + cat = op.createCategory(catKey); + mCurrentCategories.add(cat); + op.sortCategoryList(mCurrentCategories); + } + + // Add the new package or merge it as an update + hasChanged |= op.mergeNewPackage(newPackage, cat); } - op.sortCategoryList(cats); + if (hasChanged) { + op.postCategoryItemsChanged(mCurrentCategories); + } + + return hasChanged; } + private PkgCategory findCurrentCategory( + List currentCategories, + Object categoryKey) { + for (PkgCategory cat : currentCategories) { + if (cat.getKey().equals(categoryKey)) { + return cat; + } + } + return null; + } /** - * Recompute the tree by sorting all the packages by API. + * {@link UpdateOp} describing the Sort-by-API operation. */ - void sortByApiLevel() { - sort(new ISortOperation() { - public List getCategories() { - return mApiCategories; - } + private class UpdateOpApi extends UpdateOp { + @Override + public Object getCategoryKey(Package pkg) { + // Sort by API - public void addDefaultCategories( - List currentCategories, - Map categoryKeyMap, - Set unusedCategoryKey, - ImageFactory imgFactory) { - // 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)); - currentCategories.add(acat); - categoryKeyMap.put(acat.getKey(), acat); - unusedCategoryKey.add(acat.getKey()); - } + if (pkg instanceof IPackageVersion) { + return ((IPackageVersion) pkg).getVersion().getApiLevel(); + + } else if (pkg instanceof ToolPackage || pkg instanceof PlatformToolPackage) { + return PkgApiCategory.KEY_TOOLS; - if (!unusedCategoryKey.contains(PkgApiCategory.KEY_EXTRA)) { - PkgApiCategory acat = new PkgApiCategory( - PkgApiCategory.KEY_EXTRA, - null, - imgFactory.getImageByName(ICON_CAT_OTHER)); - currentCategories.add(acat); - categoryKeyMap.put(acat.getKey(), acat); - unusedCategoryKey.add(acat.getKey()); + } else { + return PkgApiCategory.KEY_EXTRA; + } + } + + @Override + public void addDefaultCategories(List currentCategories) { + boolean needTools = true; + boolean needExtras = true; + + for (PkgCategory cat : currentCategories) { + if (cat.getKey().equals(PkgApiCategory.KEY_TOOLS)) { + // Mark them as no unused to prevent their removal in updateEnd(). + cat.setUnused(false); + needTools = false; + } else if (cat.getKey().equals(PkgApiCategory.KEY_EXTRA)) { + cat.setUnused(false); + needExtras = false; } } - public Object getCategoryKey(PkgItem item) { - // Get the category for this item. - int apiKey = item.getApi(); + // Always add the tools & extras categories, even if empty (unlikely anyway) + if (needTools) { + PkgApiCategory acat = new PkgApiCategory( + PkgApiCategory.KEY_TOOLS, + null, + mUpdaterData.getImageFactory().getImageByName(ICON_CAT_OTHER)); + currentCategories.add(acat); + } - if (apiKey < 1) { - Package p = item.getMainPackage(); - if (p instanceof ToolPackage || p instanceof PlatformToolPackage) { - apiKey = PkgApiCategory.KEY_TOOLS; - } else { - apiKey = PkgApiCategory.KEY_EXTRA; - } - } - return apiKey; + if (needExtras) { + PkgApiCategory acat = new PkgApiCategory( + PkgApiCategory.KEY_EXTRA, + null, + mUpdaterData.getImageFactory().getImageByName(ICON_CAT_OTHER)); + currentCategories.add(acat); } + } - public PkgCategory createCategory( - Object catKey, - ImageFactory imgFactory) { - - PkgCategory cat = null; - - assert catKey instanceof Integer; - int apiKey = ((Integer) catKey).intValue(); - - // 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 && apiKey != PkgApiCategory.KEY_TOOLS) { - for (IAndroidTarget target : - mUpdaterData.getSdkManager().getTargets()) { - if (target.isPlatform() && - target.getVersion().getApiLevel() == apiKey) { - platformName = target.getVersionName(); - break; - } + @Override + public PkgCategory createCategory(Object catKey) { + // Create API category. + PkgCategory cat = null; + + assert catKey instanceof Integer; + int apiKey = ((Integer) catKey).intValue(); + + // 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 && apiKey != PkgApiCategory.KEY_TOOLS) { + 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)); + cat = new PkgApiCategory( + apiKey, + platformName, + mUpdaterData.getImageFactory().getImageByName(ICON_CAT_PLATFORM)); - return cat; - } + return cat; + } - public boolean mergeNewItem( - PkgItem newItem, - PkgCategory cat, - List cats, - Set unusedPackages) { - - // Behavior for a merge when sorting by API: - // - New items can only be merged with their own category. - // - Normally we expect installed items to be processed first (before new - // item which will update them), by design (since the local list is always - // processed first.) If for any reason this isn't the case, we'll show a - // duplicate right now. That means if we're processing an installed item, - // we won't try to merge it. - - 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()); - return true; - } else if (newItem.getState() == PkgState.NEW && pi.mergeUpdate(p)) { - // The new package is an update for the existing package. - unusedPackages.remove(pi.getMainPackage()); - unusedPackages.remove(pi.getUpdatePkg()); - return true; - } + @Override + public boolean mergeNewPackage(Package newPackage, PkgCategory cat) { + // First check if the new package could be an update + // to an existing package + for (PkgItem item : cat.getItems()) { + if (item.isSameMainPackageAs(newPackage)) { + // Seems like this isn't really a new item after all. + cat.setUnused(false); + // Return false since we're not changing anything. + return false; + } else if (item.mergeUpdate(newPackage)) { + // The new package is an update for the existing package + // and has been merged in the PkgItem as such. + cat.setUnused(false); + // Return true to indicate we changed something. + return true; } - return false; } - public void postProcessItem( - Object catKey, - PkgCategory category, - PkgItem item) { - assert catKey instanceof Integer; - int apiKey = ((Integer) catKey).intValue(); + // This is truly a new item. + cat.getItems().add(new PkgItem(newPackage, PkgState.NEW)); + cat.setUnused(false); + return true; // something has changed + } - assert category instanceof PkgApiCategory; - PkgApiCategory cat = (PkgApiCategory) category; + @Override + public void sortCategoryList(List categoryList) { + // Sort the categories list. + // We always want categories in order tools..platforms..extras. + // For platform, we compare in descending order (o2-o1). + // This order is achieved by having the category keys ordered as + // needed for the sort to just do what we expect. + + Collections.sort(categoryList, new Comparator() { + public int compare(PkgCategory cat1, PkgCategory cat2) { + assert cat1 instanceof PkgApiCategory; + assert cat2 instanceof PkgApiCategory; + int api1 = ((Integer) cat1.getKey()).intValue(); + int api2 = ((Integer) cat2.getKey()).intValue(); + return api2 - api1; + } + }); + } - if (apiKey != -1 && cat.getPlatformName() == null) { + @Override + public void postCategoryItemsChanged(List categoryList) { + // Sort the items + for (PkgCategory cat : mCurrentCategories) { + Collections.sort(cat.getItems()); + + // When sorting by API, we can't always get the platform name + // from the package manager. In this case at the very end we + // look for a potential platform package we can use to extract + // the platform version name (e.g. '1.5') from the first suitable + // platform package we can find. + + assert cat instanceof PkgApiCategory; + PkgApiCategory pac = (PkgApiCategory) cat; + if (pac.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.getMainPackage(); - if (p instanceof PlatformPackage) { - String platformName = ((PlatformPackage) p).getVersionName(); - cat.setPlatformName(platformName); + + for (PkgItem item : cat.getItems()) { + Package p = item.getMainPackage(); + if (p instanceof PlatformPackage) { + String platformName = ((PlatformPackage) p).getVersionName(); + if (platformName != null) { + pac.setPlatformName(platformName); + break; + } + } } } } - public void sortCategoryList(List categoryList) { - // Sort the categories list. - // We always want categories in order tools..platforms..extras. - // For platform, we compare in descending order (o2-o1). - // This order is achieved by having the category keys ordered as - // needed for the sort to just do what we expect. - - Collections.sort(categoryList, new Comparator() { - public int compare(PkgCategory cat1, PkgCategory cat2) { - assert cat1 instanceof PkgApiCategory; - assert cat2 instanceof PkgApiCategory; - int api1 = ((Integer) cat1.getKey()).intValue(); - int api2 = ((Integer) cat2.getKey()).intValue(); - return api2 - api1; - } - }); - } - }); + } } /** - * Recompute the tree by sorting all packages by source. - * - * Behavior for a merge when sorting by source: - * - Items are grouped under their source even if installed. - * The 'local' source is only for installed items with no source. + * {@link UpdateOp} describing the Sort-by-Source operation. */ - void sortBySource() { - sort(new ISortOperation() { - public List getCategories() { - return mSourceCategories; + private class UpdateOpSource extends UpdateOp { + @Override + public Object getCategoryKey(Package pkg) { + // Sort by source + SdkSource source = pkg.getParentSource(); + if (source == null) { + return PkgSourceCategory.UNKNOWN_SOURCE; } + return source; + } - public void addDefaultCategories( - List currentCategories, - Map categoryKeyMap, - Set unusedCategoryKey, - ImageFactory imgFactory) { - - // Always add the local categories, even if empty (unlikely anyway) - if (!unusedCategoryKey.contains(PkgSourceCategory.UNKNOWN_SOURCE)) { - PkgSourceCategory cat = new PkgSourceCategory( - PkgSourceCategory.UNKNOWN_SOURCE, - mUpdaterData); - currentCategories.add(cat); - categoryKeyMap.put(cat.getKey(), cat); - unusedCategoryKey.add(cat.getKey()); + @Override + public void addDefaultCategories(List currentCategories) { + for (PkgCategory cat : currentCategories) { + if (cat.getKey().equals(PkgSourceCategory.UNKNOWN_SOURCE)) { + // Already present. + return; } } - public Object getCategoryKey(PkgItem item) { - return item.getSource(); - } - - public PkgCategory createCategory( - Object catKey, - ImageFactory imgFactory) { + // Always add the local categories, even if empty (unlikely anyway) + PkgSourceCategory cat = new PkgSourceCategory( + PkgSourceCategory.UNKNOWN_SOURCE, + mUpdaterData); + // Mark it as unused so that it can be cleared in updateEnd() if not used. + cat.setUnused(true); + currentCategories.add(cat); + } - assert catKey instanceof SdkSource; + @Override + public PkgCategory createCategory(Object catKey) { + assert catKey instanceof SdkSource; + PkgCategory cat = new PkgSourceCategory((SdkSource) catKey, mUpdaterData); + return cat; - PkgCategory cat = new PkgSourceCategory( - (SdkSource) catKey, - mUpdaterData); + } - return cat; + @Override + public boolean mergeNewPackage(Package newPackage, PkgCategory cat) { + // First check if the new package could be an update + // to an existing package + for (PkgItem item : cat.getItems()) { + if (item.isSameMainPackageAs(newPackage)) { + // Seems like this isn't really a new item after all. + cat.setUnused(false); + // Return false since we're not changing anything. + return false; + } else if (item.mergeUpdate(newPackage)) { + // The new package is an update for the existing package + // and has been merged in the PkgItem as such. + cat.setUnused(false); + // Return true to indicate we changed something. + return true; + } } - public boolean mergeNewItem( - PkgItem newItem, - PkgCategory cat, - List cats, - Set unusedPackages) { - - for (PkgItem pi : cat.getItems()) { - Package p = newItem.getMainPackage(); - if (pi.isSameItemAs(newItem)) { - // It's the same item, keep it. - unusedPackages.remove(pi.getMainPackage()); - return true; - } else if (pi.isSameMainPackageAs(p)) { - // It's not exactly the same item but the main package is the same. - // This happens when trying to merge an item which state has changed - // or its update list has changed. - - if (newItem.getState() == PkgState.INSTALLED && - pi.getState() == PkgState.NEW) { - // In source-list mode, installed items 'hide' new items. - // In this case, return false so that the caller add the newItem - // to the category. - return false; - } + // This is truly a new item. + cat.getItems().add(new PkgItem(newPackage, PkgState.NEW)); + cat.setUnused(false); + return true; // something has changed + } - unusedPackages.remove(pi.getMainPackage()); - return true; - } else if (newItem.getState() == PkgState.NEW && pi.mergeUpdate(p)) { - // The new package is an update for the existing package. - unusedPackages.remove(pi.getMainPackage()); - unusedPackages.remove(pi.getUpdatePkg()); - return true; + @Override + public void sortCategoryList(List categoryList) { + // Sort the sources in ascending source name order, + // with the local packages always first. + + Collections.sort(categoryList, new Comparator() { + public int compare(PkgCategory cat1, PkgCategory cat2) { + assert cat1 instanceof PkgSourceCategory; + assert cat2 instanceof PkgSourceCategory; + + SdkSource src1 = ((PkgSourceCategory) cat1).getSource(); + SdkSource src2 = ((PkgSourceCategory) cat2).getSource(); + + if (src1 == src2) { + return 0; + } else if (src1 == PkgSourceCategory.UNKNOWN_SOURCE) { + return -1; + } else if (src2 == PkgSourceCategory.UNKNOWN_SOURCE) { + return 1; } + assert src1 != null; // true because LOCAL_SOURCE==null + assert src2 != null; + return src1.toString().compareTo(src2.toString()); } + }); + } - return false; - } - - public void postProcessItem( - Object catKey, - PkgCategory category, - PkgItem item) { - // pass - } - - public void sortCategoryList(List categoryList) { - - // Sort the sources in ascending source name order, - // with the local packages always first. - - Collections.sort(categoryList, new Comparator() { - public int compare(PkgCategory cat1, PkgCategory cat2) { - assert cat1 instanceof PkgSourceCategory; - assert cat2 instanceof PkgSourceCategory; - - SdkSource src1 = ((PkgSourceCategory) cat1).getSource(); - SdkSource src2 = ((PkgSourceCategory) cat2).getSource(); - - if (src1 == src2) { - return 0; - } else if (src1 == PkgSourceCategory.UNKNOWN_SOURCE) { - return -1; - } else if (src2 == PkgSourceCategory.UNKNOWN_SOURCE) { - return 1; - } - assert src1 != null; // true because LOCAL_SOURCE==null - assert src2 != null; - return src1.toString().compareTo(src2.toString()); - } - }); + @Override + public void postCategoryItemsChanged(List categoryList) { + // Sort the items + for (PkgCategory cat : mCurrentCategories) { + Collections.sort(cat.getItems()); } - }); + } } - - /** - * Used by {@link #sort(ISortOperation)} to determine if a given item from - * the input {@link #mAllPkgItems} should be displayed or not. This is what - * allows us to filter items in our out of the tree displayed depending on - * user flags, without actually reloading anything. - */ - abstract boolean keepItem(PkgItem item); } 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 index 8fb4f1c01..964c30a37 100755 --- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java +++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockEmptyPackage.java @@ -138,4 +138,33 @@ class MockEmptyPackage extends Package { mTestHandle.equals(((MockEmptyPackage) pkg).mTestHandle); } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mTestHandle == null) ? 0 : mTestHandle.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof MockEmptyPackage)) { + return false; + } + MockEmptyPackage other = (MockEmptyPackage) obj; + if (mTestHandle == null) { + if (other.mTestHandle != null) { + return false; + } + } else if (!mTestHandle.equals(other.mTestHandle)) { + return false; + } + return true; + } } diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java new file mode 100755 index 000000000..3be5d4f5e --- /dev/null +++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesDiffLogicTest.java @@ -0,0 +1,789 @@ +/* + * 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.sdklib.internal.repository.Package; +import com.android.sdklib.internal.repository.SdkRepoSource; +import com.android.sdklib.internal.repository.SdkSource; +import com.android.sdkuilib.internal.repository.PackageLoader.PkgItem; +import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic; +import com.android.sdkuilib.internal.repository.PackagesPage.PkgCategory; +import com.android.sdkuilib.internal.repository.PackagesPage.PackagesDiffLogic.UpdateOp; + +import junit.framework.TestCase; + +public class PackagesDiffLogicTest extends TestCase { + + private PackagesDiffLogic m; + private MockUpdaterData u; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + u = new MockUpdaterData(); + m = new PackagesDiffLogic(u); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + // ---- + // + // Test Details Note: the way load is implemented in PackageLoader, the + // loader processes each source and then for each source the packages are added + // to a list and the sorting algorithm is called with that list. Thus for + // one load, many calls to the sortByX/Y happen, with the list progressively + // being populated. + // However when the user switches sorting algorithm, the package list is not + // reloaded and is processed at once. + + public void testSortByApi_Empty() { + UpdateOp op = m.updateStart(true /*sortByApi*/); + assertFalse(m.updateSourcePackages(op, null /*locals*/, new Package[0])); + assertFalse(m.updateEnd(op)); + + assertSame(m.mCurrentCategories, m.mApiCategories); + + // We also keep these 2 categories even if they contain nothing + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n", + getTree(m)); + } + + public void testSortByApi_AddSamePackage() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + UpdateOp op = m.updateStart(true /*sortByApi*/); + // First insert local packages + assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "some pkg", 1) + })); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // Insert the next source + // Same package as the one installed, so we don't display it + assertFalse(m.updateSourcePackages(op, src1, new Package[] { + new MockEmptyPackage(src1, "some pkg", 1) + })); + + assertFalse(m.updateEnd(op)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_AddOtherPackage() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + UpdateOp op = m.updateStart(true /*sortByApi*/); + // First insert local packages + assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "some pkg", 1) + })); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // Insert the next source + // Not the same package as the one installed, so we'll display it + assertTrue(m.updateSourcePackages(op, src1, new Package[] { + new MockEmptyPackage(src1, "other pkg", 1) + })); + + assertFalse(m.updateEnd(op)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_Update1() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + // Typical case: user has a locally installed package in revision 1 + // The display list after sort should show that installed package. + UpdateOp op = m.updateStart(true /*sortByApi*/); + // First insert local packages + assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + assertTrue(m.updateSourcePackages(op, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 4), + new MockEmptyPackage(src1, "type1", 2) + })); + + assertFalse(m.updateEnd(op)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_Reload() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + // First load reveals a package local package and its update + UpdateOp op1 = m.updateStart(true /*sortByApi*/); + // First insert local packages + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2) + })); + + assertFalse(m.updateEnd(op1)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // Now simulate a reload that clears the package list and create similar + // objects but not the same references. The only difference is that updateXyz + // returns false since they don't change anything. + + UpdateOp op2 = m.updateStart(true /*sortByApi*/); + // First insert local packages + assertFalse(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + assertFalse(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2) + })); + + assertFalse(m.updateEnd(op2)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_InstallPackage() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + // First load reveals a new package + UpdateOp op1 = m.updateStart(true /*sortByApi*/); + // No local packages at first + assertFalse(m.updateSourcePackages(op1, null /*locals*/, new Package[0])); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + + assertFalse(m.updateEnd(op1)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // Install it. + UpdateOp op2 = m.updateStart(true /*sortByApi*/); + // local packages + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + assertFalse(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + + assertFalse(m.updateEnd(op2)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // Load reveals an update + UpdateOp op3 = m.updateStart(true /*sortByApi*/); + // local packages + assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + assertTrue(m.updateSourcePackages(op3, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2) + })); + + assertFalse(m.updateEnd(op3)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_DeletePackage() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + // We have an installed package + UpdateOp op2 = m.updateStart(true /*sortByApi*/); + // local packages + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + assertTrue(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2) + })); + + assertFalse(m.updateEnd(op2)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + + // User now deletes the installed package. + UpdateOp op1 = m.updateStart(true /*sortByApi*/); + // No local packages + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[0])); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1) + })); + + assertFalse(m.updateEnd(op1)); + + assertEquals( + "PkgApiCategory \n" + + "PkgApiCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortByApi_CompleteUpdate() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url1", "repo1"); + SdkSource src2 = new SdkRepoSource("http://repo.com/url2", "repo2"); + + // 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. + + // First update has the typical tools and a couple extras + UpdateOp op1 = m.updateStart(true /*sortByApi*/); + + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + })); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + })); + assertFalse(m.updateEnd(op1)); + + assertEquals( + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n", + getTree(m)); + + // Next update adds platforms and addon, sorted in a category based on their API level + UpdateOp op2 = m.updateStart(true /*sortByApi*/); + MockPlatformPackage p1; + MockPlatformPackage p2; + + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + // second update + p1 = new MockPlatformPackage(src1, 1, 2, 3), // API 1 + new MockPlatformPackage(src1, 3, 6, 3), + new MockAddonPackage(src2, "addon A", p1, 5), + })); + assertTrue(m.updateSourcePackages(op2, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + // second update + p2 = new MockPlatformPackage(src1, 2, 4, 3), // API 2 + })); + assertTrue(m.updateSourcePackages(op2, src2, new Package[] { + new MockAddonPackage(src2, "addon C", p2, 9), + new MockAddonPackage(src2, "addon A", p1, 6), + new MockAddonPackage(src2, "addon B", p2, 7), + // the rev 8 update will be ignored since there's a rev 9 coming after + new MockAddonPackage(src2, "addon B", p2, 8), + new MockAddonPackage(src2, "addon B", p2, 9), + })); + assertFalse(m.updateEnd(op2)); + + assertEquals( + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n", + getTree(m)); + + // Reloading the same thing should have no impact except for the update methods + // returning false when they don't change the current list. + UpdateOp op3 = m.updateStart(true /*sortByApi*/); + + assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + // second update + p1 = new MockPlatformPackage(src1, 1, 2, 3), + new MockPlatformPackage(src1, 3, 6, 3), + new MockAddonPackage(src2, "addon A", p1, 5), + })); + assertFalse(m.updateSourcePackages(op3, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + // second update + p2 = new MockPlatformPackage(src1, 2, 4, 3), + })); + assertTrue(m.updateSourcePackages(op3, src2, new Package[] { + new MockAddonPackage(src2, "addon C", p2, 9), + new MockAddonPackage(src2, "addon A", p1, 6), + new MockAddonPackage(src2, "addon B", p2, 7), + // the rev 8 update will be ignored since there's a rev 9 coming after + // however as a side effect it makes the update method return true as it + // incorporated the update. + new MockAddonPackage(src2, "addon B", p2, 8), + new MockAddonPackage(src2, "addon B", p2, 9), + })); + assertFalse(m.updateEnd(op3)); + + assertEquals( + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n" + + "PkgApiCategory \n" + + "-- \n" + + "-- \n", + getTree(m)); + } + + // ---- + + public void testSortBySource_Empty() { + UpdateOp op = m.updateStart(false /*sortByApi*/); + assertFalse(m.updateSourcePackages(op, null /*locals*/, new Package[0])); + // UpdateEnd returns true since it removed the synthetic "unknown source" category + assertTrue(m.updateEnd(op)); + + assertSame(m.mCurrentCategories, m.mSourceCategories); + assertTrue(m.mApiCategories.isEmpty()); + + assertEquals( + "", + getTree(m)); + } + + public void testSortBySource_AddPackages() { + // Since we're sorting by source, items are grouped under their source + // even if installed. The 'local' source is only for installed items for + // which we don't know the source. + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + UpdateOp op = m.updateStart(false /*sortByApi*/); + assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "known source", 2), + new MockEmptyPackage(null, "unknown source", 3), + })); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n" + + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + assertTrue(m.updateSourcePackages(op, src1, new Package[] { + new MockEmptyPackage(src1, "new", 1), + })); + + assertFalse(m.updateEnd(op)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n" + + "PkgSourceCategory \n" + + "-- \n" + + "-- \n", + getTree(m)); + } + + public void testSortBySource_Update1() { + + // Typical case: user has a locally installed package in revision 1 + // The display list after sort should show that instaled package. + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + UpdateOp op = m.updateStart(false /*sortByApi*/); + assertTrue(m.updateSourcePackages(op, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + + assertEquals( + "PkgSourceCategory \n" + + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + // Edge case: the source reveals an update in revision 2. It is ignored since + // we already have a package in rev 4. + + assertTrue(m.updateSourcePackages(op, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 4), + new MockEmptyPackage(src1, "type1", 2), + })); + + assertTrue(m.updateEnd(op)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortBySource_Reload() { + + // First load reveals a package local package and its update + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + UpdateOp op1 = m.updateStart(false /*sortByApi*/); + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2), + })); + assertTrue(m.updateEnd(op1)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + // Now simulate a reload that clears the package list and creates similar + // objects but not the same references. Update methods return false since + // they don't change anything. + UpdateOp op2 = m.updateStart(false /*sortByApi*/); + assertFalse(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertFalse(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2), + })); + assertTrue(m.updateEnd(op2)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortBySource_InstallPackage() { + + // First load reveals a new package + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + UpdateOp op1 = m.updateStart(false /*sortByApi*/); + // no local package + assertFalse(m.updateSourcePackages(op1, null /*locals*/, new Package[0])); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateEnd(op1)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + + // Install it. The display only shows the installed one, 'hiding' the remote package + UpdateOp op2 = m.updateStart(false /*sortByApi*/); + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertFalse(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateEnd(op2)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + // Now we have an update + UpdateOp op3 = m.updateStart(false /*sortByApi*/); + assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateSourcePackages(op3, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 2), + })); + assertTrue(m.updateEnd(op3)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortBySource_DeletePackage() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); + + // Start with an installed package and its matching remote package + UpdateOp op2 = m.updateStart(false /*sortByApi*/); + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertFalse(m.updateSourcePackages(op2, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateEnd(op2)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + + // User now deletes the installed package. + UpdateOp op1 = m.updateStart(false /*sortByApi*/); + // no local package + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[0])); + assertFalse(m.updateSourcePackages(op1, src1, new Package[] { + new MockEmptyPackage(src1, "type1", 1), + })); + assertTrue(m.updateEnd(op1)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n", + getTree(m)); + } + + public void testSortBySource_CompleteUpdate() { + SdkSource src1 = new SdkRepoSource("http://repo.com/url1", "repo1"); + SdkSource src2 = new SdkRepoSource("http://repo.com/url2", "repo2"); + + // First update has the typical tools and a couple extras + UpdateOp op1 = m.updateStart(false /*sortByApi*/); + + assertTrue(m.updateSourcePackages(op1, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + })); + assertTrue(m.updateSourcePackages(op1, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + })); + assertTrue(m.updateEnd(op1)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n", + getTree(m)); + + // Next update adds platforms and addon, sorted in a category based on their API level + UpdateOp op2 = m.updateStart(false /*sortByApi*/); + MockPlatformPackage p1; + MockPlatformPackage p2; + + assertTrue(m.updateSourcePackages(op2, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + // second update + p1 = new MockPlatformPackage(src1, 1, 2, 3), // API 1 + new MockPlatformPackage(src1, 3, 6, 3), // API 3 + new MockAddonPackage(src2, "addon A", p1, 5), + })); + assertTrue(m.updateSourcePackages(op2, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + // second update + p2 = new MockPlatformPackage(src1, 2, 4, 3), // API 2 + })); + assertTrue(m.updateSourcePackages(op2, src2, new Package[] { + new MockAddonPackage(src2, "addon C", p2, 9), + new MockAddonPackage(src2, "addon A", p1, 6), + new MockAddonPackage(src2, "addon B", p2, 7), + // the rev 8 update will be ignored since there's a rev 9 coming after + new MockAddonPackage(src2, "addon B", p2, 8), + new MockAddonPackage(src2, "addon B", p2, 9), + })); + assertTrue(m.updateEnd(op2)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "PkgSourceCategory \n" + + "-- \n" + + "-- \n" + + "-- \n", + getTree(m)); + + // Reloading the same thing should have no impact except for the update methods + // returning false when they don't change the current list. + UpdateOp op3 = m.updateStart(false /*sortByApi*/); + + assertFalse(m.updateSourcePackages(op3, null /*locals*/, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "android", "usb_driver", 4, 3), + // second update + p1 = new MockPlatformPackage(src1, 1, 2, 3), + new MockPlatformPackage(src1, 3, 6, 3), + new MockAddonPackage(src2, "addon A", p1, 5), + })); + assertFalse(m.updateSourcePackages(op3, src1, new Package[] { + new MockToolPackage(src1, 10, 3), + new MockPlatformToolPackage(src1, 3), + new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), + new MockExtraPackage(src1, "android", "usb_driver", 5, 3), + // second update + p2 = new MockPlatformPackage(src1, 2, 4, 3), + })); + assertTrue(m.updateSourcePackages(op3, src2, new Package[] { + new MockAddonPackage(src2, "addon C", p2, 9), + new MockAddonPackage(src2, "addon A", p1, 6), + new MockAddonPackage(src2, "addon B", p2, 7), + // the rev 8 update will be ignored since there's a rev 9 coming after + // however as a side effect it makes the update method return true as it + // incorporated the update. + new MockAddonPackage(src2, "addon B", p2, 8), + new MockAddonPackage(src2, "addon B", p2, 9), + })); + assertTrue(m.updateEnd(op3)); + + assertEquals( + "PkgSourceCategory \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "-- \n" + + "PkgSourceCategory \n" + + "-- \n" + + "-- \n" + + "-- \n", + getTree(m)); + } + + // ---- + + /** + * 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: + *
+     *   PkgCategory <description>
+     *   -- <PkgItem description>
+     * 
+ */ + public String getTree(PackagesDiffLogic 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/PackagesPageLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java deleted file mode 100755 index d3f30aca3..000000000 --- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/PackagesPageLogicTest.java +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.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.sdklib.internal.repository.SdkRepoSource; -import com.android.sdklib.internal.repository.SdkSource; -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(); - } - - // ---- - // - // Test Details Note: the way load is implemented in PackageLoader, the - // loader processes each source and then for each source the packages are added - // to a list and the sorting algorithm is called with that list. Thus for - // one load, many calls to the sortByX/Y happen, with the list progressively - // being populated. - // However when the user switches sorting algorithm, the package list is not - // reloaded and is processed at once. - - public void testSortByApi_Empty() { - assertTrue(m.mAllPkgItems.isEmpty()); - m.sortByApiLevel(); - assertSame(m.mCurrentCategories, m.mApiCategories); - assertTrue(m.mApiCategories.isEmpty()); - } - - public void testSortByApi_SamePackage() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "some pkg", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - // Same package as the one installed, so we don't display it - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "some pkg", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_AddPackages() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "that pkg", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "this pkg", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_Update1() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // 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(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \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(src1, "type1", 4), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_Reload() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // First load reveals a package local package and its update - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - // Now simulate a reload that clears the package list and create similar - // objects but not the same references. - m.mAllPkgItems.clear(); - assertTrue(m.mAllPkgItems.isEmpty()); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_InstallAfterNew() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // We expect updates to appear AFTER the packages the installed items will update. - // (This is pretty much guaranteed since local packages are processed first.) - // The reverse order is not supported by the sorting algorithm and both will be shown. - - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_InstallPackage() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // First load reveals a new package - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - // Install it. Load reveals a package local package and its update - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - // Now we have an update - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_DeletePackage() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // We have an installed package - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortByApiLevel(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - // User now deletes the installed package. - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortByApi_CompleteUpdate() { - assertTrue(m.mAllPkgItems.isEmpty()); - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - // 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)); - // We'll typically see installed items twice, first as installed then as new packages - // coming from the source that delivered them. The new ones should be ignored. - m.mAllPkgItems.add(new PkgItem(new MockToolPackage(10, 3), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockPlatformToolPackage(3), PkgState.NEW)); - - // Load a few extra packages - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src1, "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 \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n", - getTree(m)); - - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src1, "android", "usb_driver", 4, 3), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src1, "android", "usb_driver", 5, 3), PkgState.NEW)); - - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \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(src1, 1, 2, 3), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem(p2 = new MockPlatformPackage(src1, 2, 4, 3), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( new MockPlatformPackage(src1, 3, 6, 3), PkgState.INSTALLED)); - - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon C", p2, 9), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon A", p1, 5), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon A", p1, 6), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon B", p2, 7), PkgState.NEW)); - // the rev 8 update will be ignored since there's a rev 9 coming after - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon B", p2, 8), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockAddonPackage(src1, "addon B", p2, 9), PkgState.NEW)); - - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - - // Now simulate a change of sorting algorithm: sort by source then by API again. - - m.sortBySource(); - m.sortByApiLevel(); - - assertEquals( - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n" + - "PkgApiCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); -} - - // ---- - - public void testSortBySource_Empty() { - assertTrue(m.mAllPkgItems.isEmpty()); - m.sortBySource(); - assertSame(m.mCurrentCategories, m.mSourceCategories); - assertTrue(m.mApiCategories.isEmpty()); - } - - - public void testSortBySource_AddPackages() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // Since we're sorting by source, items are grouped under their source - // even if installed. The 'local' source is only for installed items for - // which we don't know the source. - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - - m.mAllPkgItems.add(new PkgItem( - new MockEmptyPackage(src1, "new", 1), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( - new MockEmptyPackage(src1, "known source", 2), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem( - new MockEmptyPackage(null, "unknown source", 3), PkgState.INSTALLED)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_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. - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // Edge case: the source reveals an update in revision 2. It is ignored since - // we already have a package in rev 4. - - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 4), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_Reload() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // First load reveals a package local package and its update - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // Now simulate a reload that clears the package list and create similar - // objects but not the same references. - m.mAllPkgItems.clear(); - assertTrue(m.mAllPkgItems.isEmpty()); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_InstallAfterNew() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // We expect updates to appear AFTER the packages the installed items will update. - // (This is pretty much guaranteed since local packages are processed first.) - // The reverse order is not supported by the sorting algorithm and both will be shown. - - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_InstallPackage() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // First load reveals a new package - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // Install it. The display only shows the installed one, 'hiding' the remote package - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortBySource(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // Now we have an update - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortBySource(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 2), PkgState.NEW)); - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_DeletePackage() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // Start with an installed package and its matching remote package - SdkSource src1 = new SdkRepoSource("http://repo.com/url", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.INSTALLED)); - m.sortBySource(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // User now deletes the installed package. - m.mAllPkgItems.clear(); - m.mAllPkgItems.add(new PkgItem(new MockEmptyPackage(src1, "type1", 1), PkgState.NEW)); - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - } - - public void testSortBySource_CompleteUpdate() { - assertTrue(m.mAllPkgItems.isEmpty()); - - // Typical case is to have these 2 tools - SdkSource src1 = new SdkRepoSource("http://repo.com/url1", "repo1"); - m.mAllPkgItems.add(new PkgItem(new MockToolPackage(src1, 10, 3), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem(new MockPlatformToolPackage(src1, 3), PkgState.INSTALLED)); - - // Load a few extra packages - m.mAllPkgItems.add( - new PkgItem(new MockExtraPackage(src1, "carrier", "custom_rom", 1, 0), PkgState.NEW)); - - // We call sortBySource() 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.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n", - getTree(m)); - - // Source 2 only provides the addon, which is already installed so the source - // should be empty. - SdkSource src2 = new SdkRepoSource("http://repo.com/url2", "repo2"); - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src2, "android", "usb_driver", 4, 3), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src2, "android", "usb_driver", 4, 3), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // When an update is available, it is still merged with the installed item - m.mAllPkgItems.add(new PkgItem( - new MockExtraPackage(src2, "android", "usb_driver", 6, 4), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" , - getTree(m)); - - - // Now add a few Platforms - - SdkSource src3 = new SdkRepoSource("http://repo.com/url3", "repo3"); - MockPlatformPackage p1; - MockPlatformPackage p2; - m.mAllPkgItems.add(new PkgItem( - p1 = new MockPlatformPackage(src2, 1, 2, 3), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem( - p2 = new MockPlatformPackage(src3, 2, 4, 3), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( - new MockPlatformPackage(src2, 3, 6, 3), PkgState.INSTALLED)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n"+ - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n", - getTree(m)); - - // Add a bunch of add-ons and sort them. - // Note that for source 4, the order is BCA since we order first by decreasing API - // and then by increasing add-on name. - SdkSource src4 = new SdkRepoSource("http://repo.com/url4", "repo4"); - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon C", p2, 9), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon A", p1, 5), PkgState.INSTALLED)); - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon A", p1, 6), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon B", p2, 7), PkgState.NEW)); - // the rev 8 update will be ignored since there's a rev 9 coming after - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon B", p2, 8), PkgState.NEW)); - m.mAllPkgItems.add(new PkgItem( - new MockAddonPackage(src4, "addon B", p2, 9), PkgState.NEW)); - - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n"+ - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n", - getTree(m)); - - // Now simulate a change of sorting algorithm: sort by source then by API again. - m.sortByApiLevel(); - m.sortBySource(); - - assertEquals( - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n"+ - "-- \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" + - "PkgSourceCategory \n" + - "-- \n" + - "-- \n" + - "-- \n", - getTree(m)); - } - - // ---- - - /** - * 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: - *
-     *   PkgCategory <description>
-     *   -- <PkgItem description>
-     * 
- */ - 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(); - } -}