aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2012-09-15 20:24:06 -0700
committerandroid code review <noreply-gerritcodereview@google.com>2012-09-15 20:24:07 -0700
commitf8d4d4c018973fdf2b89e1f507a497a0c4253584 (patch)
treef52bc01509b5d7997fb4bfd2fa4b8f330acb286c
parentbd2f7c995a5eb3f3330f95af654d63c6df2ed118 (diff)
parent8daea84228bdda8d714f2ab4dfc19a3c2f10271b (diff)
downloadsdk-f8d4d4c018973fdf2b89e1f507a497a0c4253584.zip
sdk-f8d4d4c018973fdf2b89e1f507a497a0c4253584.tar.gz
sdk-f8d4d4c018973fdf2b89e1f507a497a0c4253584.tar.bz2
Merge "Refactor PackagesPage to make it testable."
-rwxr-xr-xeclipse/scripts/create_all_symlinks.sh9
-rw-r--r--sdkmanager/libs/sdklib/Android.mk11
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java3
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java8
-rw-r--r--sdkmanager/libs/sdkuilib/.classpath5
-rw-r--r--sdkmanager/libs/sdkuilib/Android.mk14
-rw-r--r--sdkmanager/libs/sdkuilib/etc/manifest.txt2
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java8
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PackagesDiffLogic.java20
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgCategorySource.java6
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgContentProvider.java7
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPage.java598
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageIcons.java33
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageImpl.java568
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockDownloadCache.java234
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java55
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/MockPackagesPageImpl.java231
-rwxr-xr-xsdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/SdkManagerUpgradeTest.java203
18 files changed, 1510 insertions, 505 deletions
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index 89abd53..8bda0ff 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -105,6 +105,7 @@ set -e # fail early
LIBS=""
CP_FILES=""
+
### BASE ###
BASE_PLUGIN_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.base/libs"
@@ -122,6 +123,7 @@ BASE_PLUGIN_PREBUILTS="\
LIBS="$LIBS $BASE_PLUGIN_LIBS"
CP_FILES="$CP_FILES @:$BASE_PLUGIN_DEST $BASE_PLUGIN_LIBS $BASE_PLUGIN_PREBUILTS"
+
### ADT ###
ADT_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs"
@@ -136,6 +138,7 @@ ADT_PREBUILTS="\
LIBS="$LIBS $ADT_LIBS"
CP_FILES="$CP_FILES @:$ADT_DEST $ADT_LIBS $ADT_PREBUILTS"
+
### DDMS ###
DDMS_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.ddms/libs"
@@ -170,7 +173,6 @@ if [[ $PLATFORM != "windows-x86" ]]; then
fi
-
### HIERARCHYVIEWER ###
HV_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/libs"
@@ -188,6 +190,7 @@ TV_LIBS="traceview"
LIBS="$LIBS $TV_LIBS"
CP_FILES="$CP_FILES @:$TV_DEST $TV_LIBS"
+
### MONITOR ###
MONITOR_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.monitor/libs"
@@ -196,12 +199,14 @@ MONITOR_LIBS="sdkuilib"
LIBS="$LIBS $MONITOR_LIBS"
CP_FILES="$CP_FILES @:$MONITOR_DEST $MONITOR_LIBS"
+
### SDKMANAGER ###
-SDMAN_LIBS="swtmenubar"
+SDKMAN_LIBS="swtmenubar"
LIBS="$LIBS $SDKMAN_LIBS"
+
### GL DEBUGGER ###
if [[ $PLATFORM != "windows-x86" ]]; then
diff --git a/sdkmanager/libs/sdklib/Android.mk b/sdkmanager/libs/sdklib/Android.mk
index 7ff9135..30c4e04 100644
--- a/sdkmanager/libs/sdklib/Android.mk
+++ b/sdkmanager/libs/sdklib/Android.mk
@@ -26,16 +26,17 @@ LOCAL_JAR_MANIFEST := manifest.txt
# sdkmanager/sdklib/manifest.txt
# sdkmanager/app/etc/android.bat
LOCAL_JAVA_LIBRARIES := \
- layoutlib_api \
common \
- guava-tools \
+ commons-codec-1.4 \
commons-compress-1.0 \
+ commons-logging-1.1.1 \
+ dvlib \
+ guava-tools \
httpclient-4.1.1 \
httpcore-4.1 \
httpmime-4.1.1 \
- commons-logging-1.1.1 \
- commons-codec-1.4 \
- dvlib
+ mkidentity-prebuilt \
+ layoutlib_api
LOCAL_MODULE := sdklib
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
index 1902101..c89df5e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSources.java
@@ -420,7 +420,8 @@ public class SdkSources {
try {
runnable.run();
} catch (Throwable ignore) {
- assert ignore == null : "A SdkSource.ChangeListener failed with an exception.";
+ assert ignore == null :
+ "A SdkSource.ChangeListener failed with an exception: " + ignore.toString();
}
}
}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java
index 46d1db4..86a555a 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java
@@ -143,6 +143,14 @@ public class SdkManagerTestCase extends TestCase {
AndroidLocation.resetFolder();
File addonsDir = new File(sdkDir, SdkConstants.FD_ADDONS);
addonsDir.mkdir();
+
+ File toolsDir = new File(sdkDir, SdkConstants.OS_SDK_TOOLS_FOLDER);
+ toolsDir.mkdir();
+ new File(toolsDir, SdkConstants.androidCmdName()).createNewFile();
+ new File(toolsDir, SdkConstants.FN_EMULATOR).createNewFile();
+
+ // TODO makePlatformTools with at least a source props
+
File toolsLibEmuDir = new File(sdkDir, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator");
toolsLibEmuDir.mkdirs();
new File(toolsLibEmuDir, "snapshots.img").createNewFile();
diff --git a/sdkmanager/libs/sdkuilib/.classpath b/sdkmanager/libs/sdkuilib/.classpath
index 4008a80..90f452f 100644
--- a/sdkmanager/libs/sdkuilib/.classpath
+++ b/sdkmanager/libs/sdkuilib/.classpath
@@ -9,6 +9,11 @@
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.core.commands_3.6.0.I20100512-1500.jar"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.equinox.common_3.6.0.v20100503.jar"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/eclipse/org.eclipse.jface_3.6.2.M20110210-1200.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/commons-codec-1.4.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/commons-logging-1.1.1.jar"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpclient-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpcore-4.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-core-4.1-src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpmime-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
<classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/swt.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
diff --git a/sdkmanager/libs/sdkuilib/Android.mk b/sdkmanager/libs/sdkuilib/Android.mk
index f715d57..9406fdf 100644
--- a/sdkmanager/libs/sdkuilib/Android.mk
+++ b/sdkmanager/libs/sdkuilib/Android.mk
@@ -25,13 +25,19 @@ LOCAL_JAVA_RESOURCE_DIRS := src
# (Note: there is no manifest.txt for sdkuilib.)
LOCAL_JAVA_LIBRARIES := \
common \
+ commons-codec-1.4 \
+ commons-compress-1.0 \
+ commons-logging-1.1.1 \
+ httpclient-4.1.1 \
+ httpcore-4.1 \
+ httpmime-4.1.1 \
+ org.eclipse.jface_3.6.2.M20110210-1200 \
+ org.eclipse.equinox.common_3.6.0.v20100503 \
+ org.eclipse.core.commands_3.6.0.I20100512-1500 \
sdklib \
layoutlib_api \
- swtmenubar \
swt \
- org.eclipse.jface_3.6.2.M20110210-1200 \
- org.eclipse.equinox.common_3.6.0.v20100503 \
- org.eclipse.core.commands_3.6.0.I20100512-1500
+ swtmenubar
LOCAL_MODULE := sdkuilib
diff --git a/sdkmanager/libs/sdkuilib/etc/manifest.txt b/sdkmanager/libs/sdkuilib/etc/manifest.txt
index 1c93a5d..37def3a 100644
--- a/sdkmanager/libs/sdkuilib/etc/manifest.txt
+++ b/sdkmanager/libs/sdkuilib/etc/manifest.txt
@@ -1 +1 @@
-Class-Path: sdklib.jar layoutlib_api.jar common.jar swtmenubar.jar swt.jar org.eclipse.jface_3.6.2.M20110210-1200.jar org.eclipse.equinox.common_3.6.0.v20100503.jar org.eclipse.core.commands_3.6.0.I20100512-1500.jar
+Class-Path: sdklib.jar layoutlib_api.jar common.jar commons-compress-1.0.jar httpclient-4.1.1.jar httpcore-4.1.jar httpmime-4.1.1.jar commons-logging-1.1.1.jar commons-codec-1.4.jar swtmenubar.jar swt.jar org.eclipse.jface_3.6.2.M20110210-1200.jar org.eclipse.equinox.common_3.6.0.v20100503.jar org.eclipse.core.commands_3.6.0.I20100512-1500.jar
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index 87cf5f1..7fc1b1e 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -78,7 +78,11 @@ public class UpdaterData implements IUpdaterData {
private String mOsSdkRoot;
private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
+ /** Holds all sources. Do not use this directly.
+ * Instead use {@link #getSources()} so that unit tests can override this as needed. */
private final SdkSources mSources = new SdkSources();
+ /** Holds settings. Do not use this directly.
+ * Instead use {@link #getSettingsController()} so that unit tests can override this. */
private final SettingsController mSettingsController;
private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();
private final ILogger mSdkLog;
@@ -129,7 +133,7 @@ public class UpdaterData implements IUpdaterData {
public DownloadCache getDownloadCache() {
if (mDownloadCache == null) {
mDownloadCache = new DownloadCache(
- mSettingsController.getSettings().getUseDownloadCache() ?
+ getSettingsController().getSettings().getUseDownloadCache() ?
DownloadCache.Strategy.FRESH_CACHE :
DownloadCache.Strategy.DIRECT);
}
@@ -1044,7 +1048,7 @@ public class UpdaterData implements IUpdaterData {
getPackageLoader().loadRemoteAddonsList(monitor);
- SdkSource[] sources = mSources.getAllSources();
+ SdkSource[] sources = getSources().getAllSources();
monitor.setDescription("Refresh Sources");
monitor.setProgressMax(monitor.getProgress() + sources.length);
for (SdkSource source : sources) {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PackagesDiffLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PackagesDiffLogic.java
index 5353b74..f5a2ed3 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PackagesDiffLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PackagesDiffLogic.java
@@ -32,7 +32,7 @@ import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.util.SparseArray;
import com.android.sdkuilib.internal.repository.UpdaterData;
import com.android.sdkuilib.internal.repository.core.PkgItem.PkgState;
-import com.android.sdkuilib.internal.repository.ui.PackagesPage;
+import com.android.sdkuilib.internal.repository.ui.PackagesPageIcons;
import java.util.ArrayList;
import java.util.Collections;
@@ -688,9 +688,9 @@ public class PackagesDiffLogic {
// Always add the tools & extras categories, even if empty (unlikely anyway)
if (needTools) {
PkgCategoryApi acat = new PkgCategoryApi(
- PkgCategoryApi.KEY_TOOLS,
- null,
- mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_OTHER));
+ PkgCategoryApi.KEY_TOOLS,
+ null,
+ mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_OTHER));
synchronized (cats) {
cats.add(acat);
}
@@ -698,9 +698,9 @@ public class PackagesDiffLogic {
if (needExtras) {
PkgCategoryApi acat = new PkgCategoryApi(
- PkgCategoryApi.KEY_EXTRA,
- null,
- mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_OTHER));
+ PkgCategoryApi.KEY_EXTRA,
+ null,
+ mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_OTHER));
synchronized (cats) {
cats.add(acat);
}
@@ -733,9 +733,9 @@ public class PackagesDiffLogic {
}
cat = new PkgCategoryApi(
- key,
- platformName,
- mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_PLATFORM));
+ key,
+ platformName,
+ mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_PLATFORM));
return cat;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgCategorySource.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgCategorySource.java
index cdf6b59..b73288b 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgCategorySource.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgCategorySource.java
@@ -19,7 +19,7 @@ package com.android.sdkuilib.internal.repository.core;
import com.android.sdklib.internal.repository.sources.SdkRepoSource;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdkuilib.internal.repository.UpdaterData;
-import com.android.sdkuilib.internal.repository.ui.PackagesPage;
+import com.android.sdkuilib.internal.repository.ui.PackagesPageIcons;
public class PkgCategorySource extends PkgCategory {
@@ -42,8 +42,8 @@ public class PkgCategorySource extends PkgCategory {
source, // the source is the key and it can be null
source == UNKNOWN_SOURCE ? "Local Packages" : source.toString(),
source == UNKNOWN_SOURCE ?
- updaterData.getImageFactory().getImageByName(PackagesPage.ICON_PKG_INSTALLED) :
- source);
+ updaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_PKG_INSTALLED) :
+ source);
mSource = source;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgContentProvider.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgContentProvider.java
index 6f7589c..8adf428 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgContentProvider.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/core/PkgContentProvider.java
@@ -22,6 +22,7 @@ import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdkuilib.internal.repository.ui.PackagesPage;
+import org.eclipse.jface.viewers.IInputProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
@@ -33,10 +34,10 @@ import java.util.List;
*/
public class PkgContentProvider implements ITreeContentProvider {
- private final Viewer mViewer;
+ private final IInputProvider mViewer;
private boolean mDisplayArchives;
- public PkgContentProvider(Viewer viewer) {
+ public PkgContentProvider(IInputProvider viewer) {
mViewer = viewer;
}
@@ -139,7 +140,7 @@ public class PkgContentProvider implements ITreeContentProvider {
}
@Override
- public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// unused
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPage.java
index 566981d..025be46 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPage.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPage.java
@@ -16,20 +16,12 @@
package com.android.sdkuilib.internal.repository.ui;
-import com.android.SdkConstants;
-import com.android.sdklib.internal.repository.DownloadCache;
-import com.android.sdklib.internal.repository.DownloadCache.Strategy;
-import com.android.sdklib.internal.repository.IDescription;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdkuilib.internal.repository.UpdaterData;
-import com.android.sdkuilib.internal.repository.core.PackageLoader;
-import com.android.sdkuilib.internal.repository.core.PackageLoader.ISourceLoadedCallback;
-import com.android.sdkuilib.internal.repository.core.PackagesDiffLogic;
import com.android.sdkuilib.internal.repository.core.PkgCategory;
import com.android.sdkuilib.internal.repository.core.PkgCategoryApi;
import com.android.sdkuilib.internal.repository.core.PkgContentProvider;
@@ -50,7 +42,6 @@ import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.ITableFontProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreeViewerColumn;
@@ -65,7 +56,6 @@ import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
@@ -79,8 +69,6 @@ import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -92,16 +80,7 @@ import java.util.Map.Entry;
* remote available packages. This gives an overview of what is installed
* vs what is available and allows the user to update or install packages.
*/
-public class PackagesPage extends Composite implements ISdkChangeListener {
-
- public static final String ICON_CAT_OTHER = "pkgcat_other_16.png"; //$NON-NLS-1$
- public static final String ICON_CAT_PLATFORM = "pkgcat_16.png"; //$NON-NLS-1$
- private static final String ICON_SORT_BY_SOURCE = "source_icon16.png"; //$NON-NLS-1$
- private static final String ICON_SORT_BY_API = "platform_pkg_16.png"; //$NON-NLS-1$
- private static final String ICON_PKG_NEW = "pkg_new_16.png"; //$NON-NLS-1$
- private static final String ICON_PKG_INCOMPAT = "pkg_incompat_16.png"; //$NON-NLS-1$
- private static final String ICON_PKG_UPDATE = "pkg_update_16.png"; //$NON-NLS-1$
- public static final String ICON_PKG_INSTALLED = "pkg_installed_16.png"; //$NON-NLS-1$
+public final class PackagesPage extends Composite implements ISdkChangeListener {
enum MenuAction {
RELOAD (SWT.NONE, "Reload"),
@@ -133,13 +112,13 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>();
+ private final PackagesPageImpl mImpl;
private final SdkInvocationContext mContext;
- private final UpdaterData mUpdaterData;
- private final PackagesDiffLogic mDiffLogic;
private boolean mDisplayArchives = false;
private boolean mOperationPending;
+ private Composite mGroupPackages;
private Text mTextSdkOsPath;
private Button mCheckSortSource;
private Button mCheckSortApi;
@@ -148,17 +127,11 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
private Button mCheckFilterNew;
private Composite mGroupOptions;
private Composite mGroupSdk;
- private Group mGroupPackages;
private Button mButtonDelete;
private Button mButtonInstall;
- private Tree mTree;
- private CheckboxTreeViewer mTreeViewer;
- private TreeViewerColumn mColumnName;
- private TreeViewerColumn mColumnApi;
- private TreeViewerColumn mColumnRevision;
- private TreeViewerColumn mColumnStatus;
private Font mTreeFontItalic;
private TreeColumn mTreeColumnName;
+ private CheckboxTreeViewer mTreeViewer;
public PackagesPage(
Composite parent,
@@ -166,25 +139,45 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
UpdaterData updaterData,
SdkInvocationContext context) {
super(parent, swtStyle);
- mUpdaterData = updaterData;
- mContext = context;
+ mImpl = new PackagesPageImpl(updaterData) {
+ @Override
+ protected boolean isUiDisposed() {
+ return mGroupPackages == null || mGroupPackages.isDisposed();
+ };
+ @Override
+ protected void syncExec(Runnable runnable) {
+ if (!isUiDisposed()) {
+ mGroupPackages.getDisplay().syncExec(runnable);
+ }
+ };
+ @Override
+ protected void refreshViewerInput() {
+ PackagesPage.this.refreshViewerInput();
+ }
+
+ @Override
+ protected boolean isSortByApi() {
+ return PackagesPage.this.isSortByApi();
+ }
- mDiffLogic = new PackagesDiffLogic(updaterData);
+ @Override
+ protected Font getTreeFontItalic() {
+ return mTreeFontItalic;
+ }
+
+ @Override
+ protected void loadPackages(boolean useLocalCache, boolean overrideExisting) {
+ PackagesPage.this.loadPackages(useLocalCache, overrideExisting);
+ }
+ };
+ mContext = context;
createContents(this);
postCreate(); //$hide$
}
public void performFirstLoad() {
- // First a package loader is created that only checks
- // the local cache xml files. It populates the package
- // list based on what the client got last, essentially.
- loadPackages(true /*useLocalCache*/, false /*overrideExisting*/);
-
- // Next a regular package loader is created that will
- // respect the expiration and refresh parameters of the
- // download cache.
- loadPackages(false /*useLocalCache*/, true /*overrideExisting*/);
+ mImpl.performFirstLoad();
}
@SuppressWarnings("unused")
@@ -202,12 +195,39 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
GridDataBuilder.create(mTextSdkOsPath).hFill().vCenter().hGrab();
mTextSdkOsPath.setEnabled(false);
- mGroupPackages = new Group(parent, SWT.NONE);
+ Group groupPackages = new Group(parent, SWT.NONE);
+ mGroupPackages = groupPackages;
GridDataBuilder.create(mGroupPackages).fill().grab().hSpan(2);
- mGroupPackages.setText("Packages");
- GridLayoutBuilder.create(mGroupPackages).columns(1);
+ groupPackages.setText("Packages");
+ GridLayoutBuilder.create(groupPackages).columns(1);
+
+ mTreeViewer = new CheckboxTreeViewer(groupPackages, SWT.BORDER);
+ mImpl.setITreeViewer(new PackagesPageImpl.ICheckboxTreeViewer() {
+ @Override
+ public Object getInput() {
+ return mTreeViewer.getInput();
+ }
- mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER);
+ @Override
+ public void setInput(List<PkgCategory> cats) {
+ mTreeViewer.setInput(cats);
+ }
+
+ @Override
+ public void setContentProvider(PkgContentProvider pkgContentProvider) {
+ mTreeViewer.setContentProvider(pkgContentProvider);
+ }
+
+ @Override
+ public void refresh() {
+ mTreeViewer.refresh();
+ }
+
+ @Override
+ public Object[] getCheckedElements() {
+ return mTreeViewer.getCheckedElements();
+ }
+ });
mTreeViewer.addFilter(new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
@@ -229,39 +249,45 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
}
});
- mTree = mTreeViewer.getTree();
- mTree.setLinesVisible(true);
- mTree.setHeaderVisible(true);
- GridDataBuilder.create(mTree).fill().grab();
+ Tree tree = mTreeViewer.getTree();
+ tree.setLinesVisible(true);
+ tree.setHeaderVisible(true);
+ GridDataBuilder.create(tree).fill().grab();
// 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();
+ TreeViewerColumn columnName = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ mTreeColumnName = columnName.getColumn();
mTreeColumnName.setText("Name");
mTreeColumnName.setWidth(340);
- mColumnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE);
- TreeColumn treeColumn2 = mColumnApi.getColumn();
+ TreeViewerColumn columnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn2 = columnApi.getColumn();
treeColumn2.setText("API");
treeColumn2.setAlignment(SWT.CENTER);
treeColumn2.setWidth(50);
- mColumnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE);
- TreeColumn treeColumn3 = mColumnRevision.getColumn();
+ TreeViewerColumn columnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn3 = columnRevision.getColumn();
treeColumn3.setText("Rev.");
treeColumn3.setToolTipText("Revision currently installed");
treeColumn3.setAlignment(SWT.CENTER);
treeColumn3.setWidth(50);
- mColumnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE);
- TreeColumn treeColumn4 = mColumnStatus.getColumn();
+ TreeViewerColumn columnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn4 = columnStatus.getColumn();
treeColumn4.setText("Status");
treeColumn4.setAlignment(SWT.LEAD);
treeColumn4.setWidth(190);
- mGroupOptions = new Composite(mGroupPackages, SWT.NONE);
+ mImpl.setIColumns(
+ wrapColumn(columnName),
+ wrapColumn(columnApi),
+ wrapColumn(columnRevision),
+ wrapColumn(columnStatus));
+
+ mGroupOptions = new Composite(groupPackages, SWT.NONE);
GridDataBuilder.create(mGroupOptions).hFill().vCenter().hGrab();
GridLayoutBuilder.create(mGroupOptions).columns(6).noMargins();
@@ -392,9 +418,18 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
});
}
+ private PackagesPageImpl.ITreeViewerColumn wrapColumn(final TreeViewerColumn column) {
+ return new PackagesPageImpl.ITreeViewerColumn() {
+ @Override
+ public void setLabelProvider(ColumnLabelProvider labelProvider) {
+ column.setLabelProvider(labelProvider);
+ }
+ };
+ }
+
private Image getImage(String filename) {
- if (mUpdaterData != null) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (mImpl.mUpdaterData != null) {
+ ImageFactory imgFactory = mImpl.mUpdaterData.getImageFactory();
if (imgFactory != null) {
return imgFactory.getImageByName(filename);
}
@@ -418,19 +453,19 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
switch (action) {
case RELOAD:
- fullReload();
+ mImpl.fullReload();
break;
case SHOW_ADDON_SITES:
- AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData);
+ AddonSitesDialog d = new AddonSitesDialog(getShell(), mImpl.mUpdaterData);
if (d.open()) {
- loadPackages();
+ mImpl.loadPackages();
}
break;
case TOGGLE_SHOW_ARCHIVES:
mDisplayArchives = !mDisplayArchives;
// Force the viewer to be refreshed
- ((PkgContentProvider) mTreeViewer.getContentProvider()).setDisplayArchives(
- mDisplayArchives);
+ ((PkgContentProvider) mTreeViewer.getContentProvider()).
+ setDisplayArchives(mDisplayArchives);
mTreeViewer.setInput(null);
refreshViewerInput();
syncViewerSelection();
@@ -533,29 +568,23 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
}
private void postCreate() {
- if (mUpdaterData != null) {
- mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
+ mImpl.postCreate();
+
+ if (mImpl.mUpdaterData != null) {
+ mTextSdkOsPath.setText(mImpl.mUpdaterData.getOsSdkRoot());
}
- mTreeViewer.setContentProvider(new PkgContentProvider(mTreeViewer));
((PkgContentProvider) mTreeViewer.getContentProvider()).setDisplayArchives(
- mDisplayArchives);
- ColumnViewerToolTipSupport.enableFor(mTreeViewer, ToolTip.NO_RECREATE);
+ mDisplayArchives);
- mColumnApi.setLabelProvider(
- new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnApi)));
- mColumnName.setLabelProvider(
- new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnName)));
- mColumnStatus.setLabelProvider(
- new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnStatus)));
- mColumnRevision.setLabelProvider(
- new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnRevision)));
+ ColumnViewerToolTipSupport.enableFor(mTreeViewer, ToolTip.NO_RECREATE);
- FontData fontData = mTree.getFont().getFontData()[0];
+ Tree tree = mTreeViewer.getTree();
+ FontData fontData = tree.getFont().getFontData()[0];
fontData.setStyle(SWT.ITALIC);
- mTreeFontItalic = new Font(mTree.getDisplay(), fontData);
+ mTreeFontItalic = new Font(tree.getDisplay(), fontData);
- mTree.addDisposeListener(new DisposeListener() {
+ tree.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
mTreeFontItalic.dispose();
@@ -564,52 +593,8 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
});
}
- /**
- * Performs a full reload by removing all cached packages data, including the platforms
- * and addons from the sdkmanager instance. This will perform a full local parsing
- * as well as a full reload of the remote data (by fetching all sources again.)
- */
- private void fullReload() {
- // Clear all source information, forcing them to be refreshed.
- mUpdaterData.getSources().clearAllPackages();
- // Clear and reload all local data too.
- localReload();
- }
-
- /**
- * Performs a full reload of all the local package information, including the platforms
- * and addons from the sdkmanager instance. This will perform a full local parsing.
- * <p/>
- * This method does NOT force a new fetch of the remote sources.
- *
- * @see #fullReload()
- */
- private void localReload() {
- // Clear all source caches, otherwise loading will use the cached data
- mUpdaterData.getLocalSdkParser().clearPackages();
- mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
- loadPackages();
- }
-
- /**
- * Performs a "normal" reload of the package information, use the default download
- * cache and refreshing strategy as needed.
- */
- private void loadPackages() {
- loadPackages(false /*useLocalCache*/, false /*overrideExisting*/);
- }
-
- /**
- * Performs a reload of the package information.
- *
- * @param useLocalCache When true, the {@link PackageLoader} is switched to use
- * a specific {@link DownloadCache} using the {@link Strategy#ONLY_CACHE}, meaning
- * it will only use data from the local cache. It will not try to fetch or refresh
- * manifests. This is used once the very first time the sdk manager window opens
- * and is typically followed by a regular load with refresh.
- */
- private void loadPackages(final boolean useLocalCache, final boolean overrideExisting) {
- if (mUpdaterData == null) {
+ private void loadPackages(boolean useLocalCache, boolean overrideExisting) {
+ if (mImpl.mUpdaterData == null) {
return;
}
@@ -619,7 +604,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
// action done after loadPackages must check the UI hasn't been
// disposed yet. Otherwise hilarity ensues.
- final boolean displaySortByApi = isSortByApi();
+ boolean displaySortByApi = isSortByApi();
if (mTreeColumnName.isDisposed()) {
// If the UI got disposed, don't try to load anything since we won't be
@@ -627,76 +612,11 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
return;
}
- mTreeColumnName.setImage(getImage(displaySortByApi ? ICON_SORT_BY_API
- : ICON_SORT_BY_SOURCE));
-
- PackageLoader packageLoader = null;
- if (useLocalCache) {
- packageLoader =
- new PackageLoader(mUpdaterData, new DownloadCache(Strategy.ONLY_CACHE));
- } else {
- packageLoader = mUpdaterData.getPackageLoader();
- }
- assert packageLoader != null;
-
- mDiffLogic.updateStart();
- packageLoader.loadPackages(overrideExisting, new ISourceLoadedCallback() {
- @Override
- public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
- // This runs in a thread and must not access UI directly.
- final boolean changed = mDiffLogic.updateSourcePackages(
- displaySortByApi, source, newPackages);
+ mTreeColumnName.setImage(getImage(
+ displaySortByApi ? PackagesPageIcons.ICON_SORT_BY_API
+ : PackagesPageIcons.ICON_SORT_BY_SOURCE));
- if (!mGroupPackages.isDisposed()) {
- mGroupPackages.getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- if (changed ||
- mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
- refreshViewerInput();
- }
- }
- });
- }
-
- // Return true to tell the loader to continue with the next source.
- // Return false to stop the loader if any UI has been disposed, which can
- // happen if the user is trying to close the window during the load operation.
- return !mGroupPackages.isDisposed();
- }
-
- @Override
- public void onLoadCompleted() {
- // This runs in a thread and must not access UI directly.
- final boolean changed = mDiffLogic.updateEnd(displaySortByApi);
-
- if (!mGroupPackages.isDisposed()) {
- mGroupPackages.getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- if (changed ||
- mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
- refreshViewerInput();
- }
-
- if (!useLocalCache &&
- mDiffLogic.isFirstLoadComplete() &&
- !mGroupPackages.isDisposed()) {
- // At the end of the first load, if nothing is selected then
- // automatically select all new and update packages.
- Object[] checked = mTreeViewer.getCheckedElements();
- if (checked == null || checked.length == 0) {
- onSelectNewUpdates(
- false, //selectNew
- true, //selectUpdates,
- true); //selectTop
- }
- }
- }
- });
- }
- }
- });
+ mImpl.loadPackagesImpl(useLocalCache, overrideExisting);
}
private void refreshViewerInput() {
@@ -704,16 +624,9 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
// Since the official Android source gets loaded first, it makes the
// window look non-empty a lot sooner.
if (!mGroupPackages.isDisposed()) {
-
- List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
- if (mTreeViewer.getInput() != cats) {
- // set initial input
- mTreeViewer.setInput(cats);
- } else {
- // refresh existing, which preserves the expanded state, the selection
- // and the checked state.
- mTreeViewer.refresh();
- }
+ try {
+ mImpl.setViewerInput();
+ } catch (Exception ignore) {}
// set the initial expanded state
expandInitial(mTreeViewer.getInput());
@@ -787,11 +700,12 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
if (mTreeViewer != null && !mTreeViewer.getTree().isDisposed()) {
boolean enablePreviews =
- mUpdaterData.getSettingsController().getSettings().getEnablePreviews();
+ mImpl.mUpdaterData.getSettingsController().getSettings().getEnablePreviews();
mTreeViewer.setExpandedState(elem, true);
nextCategory: for (Object pkg :
- ((ITreeContentProvider) mTreeViewer.getContentProvider()).getChildren(elem)) {
+ ((ITreeContentProvider) mTreeViewer.getContentProvider()).
+ getChildren(elem)) {
if (pkg instanceof PkgCategory) {
PkgCategory cat = (PkgCategory) pkg;
@@ -845,7 +759,8 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
return;
}
- ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
+ ITreeContentProvider provider =
+ (ITreeContentProvider) mTreeViewer.getContentProvider();
Object[] children = provider.getElements(elem);
if (children == null) {
return;
@@ -874,7 +789,8 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
boolean checked,
boolean fixChildren,
boolean fixParent) {
- ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
+ ITreeContentProvider provider =
+ (ITreeContentProvider) mTreeViewer.getContentProvider();
// fix the item itself
if (checked != mTreeViewer.getChecked(elem)) {
@@ -958,11 +874,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
*/
private void onSelectNewUpdates(boolean selectNew, boolean selectUpdates, boolean selectTop) {
// This does not update the tree itself, syncViewerSelection does it below.
- mDiffLogic.checkNewUpdateItems(
- selectNew,
- selectUpdates,
- selectTop,
- SdkConstants.CURRENT_PLATFORM);
+ mImpl.onSelectNewUpdates(selectNew, selectUpdates, selectTop);
syncViewerSelection();
updateButtonsState();
}
@@ -972,7 +884,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
*/
private void onDeselectAll() {
// This does not update the tree itself, syncViewerSelection does it below.
- mDiffLogic.uncheckAllItems();
+ mImpl.onDeselectAll();
syncViewerSelection();
updateButtonsState();
}
@@ -983,8 +895,10 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
* This does not update the tree itself.
*/
private void copySelection(boolean fromSourceToApi) {
- List<PkgItem> fromItems = mDiffLogic.getAllPkgItems(!fromSourceToApi, fromSourceToApi);
- List<PkgItem> toItems = mDiffLogic.getAllPkgItems(fromSourceToApi, !fromSourceToApi);
+ List<PkgItem> fromItems =
+ mImpl.mDiffLogic.getAllPkgItems(!fromSourceToApi, fromSourceToApi);
+ List<PkgItem> toItems =
+ mImpl.mDiffLogic.getAllPkgItems(fromSourceToApi, !fromSourceToApi);
// deselect all targets
for (PkgItem item : toItems) {
@@ -1090,12 +1004,12 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
ArrayList<Archive> archives = new ArrayList<Archive>();
getArchivesForInstall(archives);
- if (mUpdaterData != null) {
+ if (mImpl.mUpdaterData != null) {
boolean needsRefresh = false;
try {
beginOperationPending();
- List<Archive> installed = mUpdaterData.updateOrInstallAll_WithGUI(
+ List<Archive> installed = mImpl.mUpdaterData.updateOrInstallAll_WithGUI(
archives,
mCheckFilterObsolete.getSelection() /* includeObsoletes */,
mContext == SdkInvocationContext.IDE ?
@@ -1107,7 +1021,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
if (needsRefresh) {
// The local package list has changed, make sure to refresh it
- localReload();
+ mImpl.localReload();
}
}
}
@@ -1219,7 +1133,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
try {
beginOperationPending();
- mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
+ mImpl.mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
@Override
public void run(ITaskMonitor monitor) {
monitor.setProgressMax(archives.size() + 1);
@@ -1245,7 +1159,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
endOperationPending();
// The local package list has changed, make sure to refresh it
- localReload();
+ mImpl.localReload();
}
}
}
@@ -1335,236 +1249,6 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
// ----------------------
- public class PkgCellLabelProvider extends ColumnLabelProvider implements ITableFontProvider {
-
- private final TreeViewerColumn mColumn;
-
- public PkgCellLabelProvider(TreeViewerColumn column) {
- super();
- mColumn = column;
- }
-
- @Override
- public String getText(Object element) {
-
- if (mColumn == mColumnName) {
- if (element instanceof PkgCategory) {
- return ((PkgCategory) element).getLabel();
- } else if (element instanceof PkgItem) {
- return getPkgItemName((PkgItem) element);
- } else if (element instanceof IDescription) {
- return ((IDescription) element).getShortDescription();
- }
-
- } else if (mColumn == mColumnApi) {
- int api = -1;
- if (element instanceof PkgItem) {
- api = ((PkgItem) element).getApi();
- }
- if (api >= 1) {
- return Integer.toString(api);
- }
-
- } else if (mColumn == mColumnRevision) {
- if (element instanceof PkgItem) {
- PkgItem pkg = (PkgItem) element;
- return pkg.getRevision().toShortString();
- }
-
- } else if (mColumn == mColumnStatus) {
- if (element instanceof PkgItem) {
- PkgItem pkg = (PkgItem) element;
-
- switch(pkg.getState()) {
- case INSTALLED:
- Package update = pkg.getUpdatePkg();
- if (update != null) {
- return String.format(
- "Update available: rev. %1$s",
- update.getRevision().toShortString());
- }
- return "Installed";
-
- case NEW:
- Package p = pkg.getMainPackage();
- if (p != null && p.hasCompatibleArchive()) {
- return "Not installed";
- } else {
- return String.format("Not compatible with %1$s",
- SdkConstants.currentPlatformName());
- }
- }
- return pkg.getState().toString();
-
- } else if (element instanceof Package) {
- // This is an update package.
- return "New revision " + ((Package) element).getRevision().toShortString();
- }
- }
-
- return ""; //$NON-NLS-1$
- }
-
- 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.
-
- PkgCategoryApi cat = (PkgCategoryApi) findCategoryForItem(item);
- String apiLabel = cat.getApiLabel();
- String platLabel = cat.getPlatformName();
-
- if (platLabel != null && name.endsWith(platLabel)) {
- return name.substring(0, name.length() - platLabel.length());
-
- } else if (apiLabel != null && name.endsWith(apiLabel)) {
- return name.substring(0, name.length() - apiLabel.length());
-
- } else if (platLabel != null && item.isObsolete() && name.indexOf(platLabel) > 0) {
- // For obsolete items, the format is "<base name> <platform name> (Obsolete)"
- // so in this case only accept removing a platform name that is not at
- // the end.
- name = name.replace(platLabel, ""); //$NON-NLS-1$
- }
- }
-
- // Collapse potential duplicated spacing
- name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
-
- return name;
- }
-
- private PkgCategory findCategoryForItem(PkgItem item) {
- List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
- for (PkgCategory cat : cats) {
- for (PkgItem i : cat.getItems()) {
- if (i == item) {
- return cat;
- }
- }
- }
-
- return null;
- }
-
- @Override
- public Image getImage(Object element) {
- ImageFactory imgFactory = mUpdaterData.getImageFactory();
-
- if (imgFactory != null) {
- if (mColumn == mColumnName) {
- if (element instanceof PkgCategory) {
- return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
- } else if (element instanceof PkgItem) {
- return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
- }
- return imgFactory.getImageForObject(element);
-
- } else if (mColumn == mColumnStatus && element instanceof PkgItem) {
- PkgItem pi = (PkgItem) element;
- switch(pi.getState()) {
- case INSTALLED:
- if (pi.hasUpdatePkg()) {
- return imgFactory.getImageByName(ICON_PKG_UPDATE);
- } else {
- return imgFactory.getImageByName(ICON_PKG_INSTALLED);
- }
- case NEW:
- Package p = pi.getMainPackage();
- if (p != null && p.hasCompatibleArchive()) {
- return imgFactory.getImageByName(ICON_PKG_NEW);
- } else {
- return imgFactory.getImageByName(ICON_PKG_INCOMPAT);
- }
- }
- }
- }
- return super.getImage(element);
- }
-
- // -- ITableFontProvider
-
- @Override
- public Font getFont(Object element, int columnIndex) {
- if (element instanceof PkgItem) {
- if (((PkgItem) element).getState() == PkgState.NEW) {
- return mTreeFontItalic;
- }
- } else if (element instanceof Package) {
- // update package
- return mTreeFontItalic;
- }
- return super.getFont(element);
- }
-
- // -- Tooltip support
-
- @Override
- public String getToolTipText(Object element) {
- PkgItem pi = element instanceof PkgItem ? (PkgItem) element : null;
- if (pi != null) {
- element = pi.getMainPackage();
- }
- if (element instanceof IDescription) {
- String s = getTooltipDescription((IDescription) element);
-
- if (pi != null && pi.hasUpdatePkg()) {
- s += "\n-----------------" + //$NON-NLS-1$
- "\nUpdate Available:\n" + //$NON-NLS-1$
- getTooltipDescription(pi.getUpdatePkg());
- }
-
- return s;
- }
- return super.getToolTipText(element);
- }
-
- private String getTooltipDescription(IDescription element) {
- String s = element.getLongDescription();
- if (element instanceof Package) {
- Package p = (Package) element;
-
- if (!p.isLocal()) {
- // For non-installed item, try to find a download size
- for (Archive a : p.getArchives()) {
- if (!a.isLocal() && a.isCompatible()) {
- s += '\n' + a.getSizeDescription();
- break;
- }
- }
- }
-
- // Display info about where this package comes/came from
- SdkSource src = p.getParentSource();
- if (src != null) {
- try {
- URL url = new URL(src.getUrl());
- String host = url.getHost();
- if (p.isLocal()) {
- s += String.format("\nInstalled from %1$s", host);
- } else {
- s += String.format("\nProvided by %1$s", host);
- }
- } catch (MalformedURLException ignore) {
- }
- }
- }
- return s;
- }
-
- @Override
- public Point getToolTipShift(Object object) {
- return new Point(15, 5);
- }
-
- @Override
- public int getToolTipDisplayDelayTime(Object object) {
- return 500;
- }
- }
// --- Implementation of ISdkChangeListener ---
@@ -1577,7 +1261,7 @@ public class PackagesPage extends Composite implements ISdkChangeListener {
public void onSdkReload() {
// The sdkmanager finished reloading its data. We must not call localReload() from here
// since we don't want to alter the sdkmanager's data that just finished loading.
- loadPackages();
+ mImpl.loadPackages();
}
@Override
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageIcons.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageIcons.java
new file mode 100755
index 0000000..4fe8fca
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageIcons.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository.ui;
+
+
+/**
+ * Icons used by {@link PackagesPage}.
+ */
+public class PackagesPageIcons {
+
+ public static final String ICON_CAT_OTHER = "pkgcat_other_16.png"; //$NON-NLS-1$
+ public static final String ICON_CAT_PLATFORM = "pkgcat_16.png"; //$NON-NLS-1$
+ public static final String ICON_SORT_BY_SOURCE = "source_icon16.png"; //$NON-NLS-1$
+ public static final String ICON_SORT_BY_API = "platform_pkg_16.png"; //$NON-NLS-1$
+ public static final String ICON_PKG_NEW = "pkg_new_16.png"; //$NON-NLS-1$
+ public static final String ICON_PKG_INCOMPAT = "pkg_incompat_16.png"; //$NON-NLS-1$
+ public static final String ICON_PKG_UPDATE = "pkg_update_16.png"; //$NON-NLS-1$
+ public static final String ICON_PKG_INSTALLED = "pkg_installed_16.png"; //$NON-NLS-1$
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageImpl.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageImpl.java
new file mode 100755
index 0000000..3ca0ee3
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/PackagesPageImpl.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository.ui;
+
+import com.android.SdkConstants;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
+import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdkuilib.internal.repository.UpdaterData;
+import com.android.sdkuilib.internal.repository.core.PackageLoader;
+import com.android.sdkuilib.internal.repository.core.PackageLoader.ISourceLoadedCallback;
+import com.android.sdkuilib.internal.repository.core.PackagesDiffLogic;
+import com.android.sdkuilib.internal.repository.core.PkgCategory;
+import com.android.sdkuilib.internal.repository.core.PkgCategoryApi;
+import com.android.sdkuilib.internal.repository.core.PkgContentProvider;
+import com.android.sdkuilib.internal.repository.core.PkgItem;
+import com.android.sdkuilib.internal.repository.core.PkgItem.PkgState;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IInputProvider;
+import org.eclipse.jface.viewers.ITableFontProvider;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * Base class for {@link PackagesPage} that holds most of the logic to display
+ * the tree/list of packages. This class holds most of the logic and {@link PackagesPage}
+ * holds most of the UI (creating the UI, dealing with menus and buttons and tree
+ * selection.) This makes it easier to test the functionality by mocking only a
+ * subset of the UI.
+ */
+abstract class PackagesPageImpl {
+
+ final UpdaterData mUpdaterData;
+ final PackagesDiffLogic mDiffLogic;
+
+ private ICheckboxTreeViewer mITreeViewer;
+ private ITreeViewerColumn mIColumnName;
+ private ITreeViewerColumn mIColumnApi;
+ private ITreeViewerColumn mIColumnRevision;
+ private ITreeViewerColumn mIColumnStatus;
+
+ PackagesPageImpl(UpdaterData updaterData) {
+ mUpdaterData = updaterData;
+ mDiffLogic = new PackagesDiffLogic(updaterData);
+ }
+
+ /**
+ * Utility method that derived classes can override to check whether the UI is disposed.
+ * When the UI is disposed, most operations that affect the UI will be bypassed.
+ * @return True if UI is not available and should not be touched.
+ */
+ abstract protected boolean isUiDisposed();
+
+ /**
+ * Utility method to execute a runnable on the main UI thread.
+ * Will do nothing if {@link #isUiDisposed()} returns false.
+ * @param runnable The runnable to execute on the main UI thread.
+ */
+ abstract protected void syncExec(Runnable runnable);
+
+ void performFirstLoad() {
+ // First a package loader is created that only checks
+ // the local cache xml files. It populates the package
+ // list based on what the client got last, essentially.
+ loadPackages(true /*useLocalCache*/, false /*overrideExisting*/);
+
+ // Next a regular package loader is created that will
+ // respect the expiration and refresh parameters of the
+ // download cache.
+ loadPackages(false /*useLocalCache*/, true /*overrideExisting*/);
+ }
+
+ public void setITreeViewer(ICheckboxTreeViewer iTreeViewer) {
+ mITreeViewer = iTreeViewer;
+ }
+
+ public void setIColumns(
+ ITreeViewerColumn columnName,
+ ITreeViewerColumn columnApi,
+ ITreeViewerColumn columnRevision,
+ ITreeViewerColumn columnStatus) {
+ mIColumnName = columnName;
+ mIColumnApi = columnApi;
+ mIColumnRevision = columnRevision;
+ mIColumnStatus = columnStatus;
+ }
+
+ void postCreate() {
+ // Caller needs to call setITreeViewer before this.
+ assert mITreeViewer != null;
+ // Caller needs to call setIColumns before this.
+ assert mIColumnApi != null;
+ assert mIColumnName != null;
+ assert mIColumnStatus != null;
+ assert mIColumnRevision != null;
+
+ mITreeViewer.setContentProvider(new PkgContentProvider(mITreeViewer));
+
+ mIColumnApi.setLabelProvider(
+ new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnApi)));
+ mIColumnName.setLabelProvider(
+ new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnName)));
+ mIColumnStatus.setLabelProvider(
+ new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnStatus)));
+ mIColumnRevision.setLabelProvider(
+ new PkgTreeColumnViewerLabelProvider(new PkgCellLabelProvider(mIColumnRevision)));
+ }
+
+ /**
+ * Performs a full reload by removing all cached packages data, including the platforms
+ * and addons from the sdkmanager instance. This will perform a full local parsing
+ * as well as a full reload of the remote data (by fetching all sources again.)
+ */
+ void fullReload() {
+ // Clear all source information, forcing them to be refreshed.
+ mUpdaterData.getSources().clearAllPackages();
+ // Clear and reload all local data too.
+ localReload();
+ }
+
+ /**
+ * Performs a full reload of all the local package information, including the platforms
+ * and addons from the sdkmanager instance. This will perform a full local parsing.
+ * <p/>
+ * This method does NOT force a new fetch of the remote sources.
+ *
+ * @see #fullReload()
+ */
+ void localReload() {
+ // Clear all source caches, otherwise loading will use the cached data
+ mUpdaterData.getLocalSdkParser().clearPackages();
+ mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
+ loadPackages();
+ }
+
+ /**
+ * Performs a "normal" reload of the package information, use the default download
+ * cache and refreshing strategy as needed.
+ */
+ void loadPackages() {
+ loadPackages(false /*useLocalCache*/, false /*overrideExisting*/);
+ }
+
+ /**
+ * Performs a reload of the package information.
+ *
+ * @param useLocalCache When true, the {@link PackageLoader} is switched to use
+ * a specific {@link DownloadCache} using the {@link Strategy#ONLY_CACHE}, meaning
+ * it will only use data from the local cache. It will not try to fetch or refresh
+ * manifests. This is used once the very first time the sdk manager window opens
+ * and is typically followed by a regular load with refresh.
+ */
+ abstract protected void loadPackages(boolean useLocalCache, boolean overrideExisting);
+
+ /**
+ * Actual implementation of {@link #loadPackages(boolean, boolean)}.
+ * Derived implementations must call this to do the actual work after setting up the UI.
+ */
+ void loadPackagesImpl(final boolean useLocalCache, final boolean overrideExisting) {
+ if (mUpdaterData == null) {
+ return;
+ }
+
+ final boolean displaySortByApi = isSortByApi();
+
+ PackageLoader packageLoader = getPackageLoader(useLocalCache);
+ assert packageLoader != null;
+
+ mDiffLogic.updateStart();
+ packageLoader.loadPackages(overrideExisting, new ISourceLoadedCallback() {
+ @Override
+ public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
+ // This runs in a thread and must not access UI directly.
+ final boolean changed = mDiffLogic.updateSourcePackages(
+ displaySortByApi, source, newPackages);
+
+ syncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (changed ||
+ mITreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
+ refreshViewerInput();
+ }
+ }
+ });
+
+ // Return true to tell the loader to continue with the next source.
+ // Return false to stop the loader if any UI has been disposed, which can
+ // happen if the user is trying to close the window during the load operation.
+ return !isUiDisposed();
+ }
+
+ @Override
+ public void onLoadCompleted() {
+ // This runs in a thread and must not access UI directly.
+ final boolean changed = mDiffLogic.updateEnd(displaySortByApi);
+
+ syncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (changed ||
+ mITreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
+ try {
+ refreshViewerInput();
+ } catch (Exception ignore) {}
+ }
+
+ if (!useLocalCache &&
+ mDiffLogic.isFirstLoadComplete() &&
+ !isUiDisposed()) {
+ // At the end of the first load, if nothing is selected then
+ // automatically select all new and update packages.
+ Object[] checked = mITreeViewer.getCheckedElements();
+ if (checked == null || checked.length == 0) {
+ onSelectNewUpdates(
+ false, //selectNew
+ true, //selectUpdates,
+ true); //selectTop
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Used by {@link #loadPackagesImpl(boolean, boolean)} to get the package
+ * loader for the first or second pass update. When starting the manager
+ * starts with a first pass that reads only from the local cache, with no
+ * extra network access. That's {@code useLocalCache} being true.
+ * <p/>
+ * Leter it does a second pass with {@code useLocalCache} set to false
+ * and actually uses the download cache specified in {@link UpdaterData}.
+ *
+ * This is extracted so that we can control this cache via unit tests.
+ */
+ protected PackageLoader getPackageLoader(boolean useLocalCache) {
+ if (useLocalCache) {
+ return new PackageLoader(mUpdaterData, new DownloadCache(Strategy.ONLY_CACHE));
+ } else {
+ return mUpdaterData.getPackageLoader();
+ }
+ }
+
+ /**
+ * Overridden by the UI to respond to a request to refresh the tree viewer
+ * when the input has changed.
+ * The implementation must call {@link #setViewerInput()} somehow and will
+ * also need to adjust the expand state of the tree items and/or update
+ * some buttons or other state.
+ */
+ abstract protected void refreshViewerInput();
+
+ /**
+ * Invoked from {@link #refreshViewerInput()} to actually either set the
+ * input of the tree viewer or refresh it if it's the <em>same</em> input
+ * object.
+ */
+ protected void setViewerInput() {
+ List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
+ if (mITreeViewer.getInput() != cats) {
+ // set initial input
+ mITreeViewer.setInput(cats);
+ } else {
+ // refresh existing, which preserves the expanded state, the selection
+ // and the checked state.
+ mITreeViewer.refresh();
+ }
+ }
+
+ /**
+ * Overridden by the UI to determine if the tree should display packages sorted
+ * by API (returns true) or by repository source (returns false.)
+ */
+ abstract protected boolean isSortByApi();
+
+ /**
+ * Checks all PkgItems that are either new or have updates or select top platform
+ * for initial run.
+ */
+ void onSelectNewUpdates(boolean selectNew, boolean selectUpdates, boolean selectTop) {
+ // This does not update the tree itself, syncViewerSelection does it in the caller.
+ mDiffLogic.checkNewUpdateItems(
+ selectNew,
+ selectUpdates,
+ selectTop,
+ SdkConstants.CURRENT_PLATFORM);
+ }
+
+ /**
+ * Deselect all checked PkgItems.
+ */
+ void onDeselectAll() {
+ // This does not update the tree itself, syncViewerSelection does it in the caller.
+ mDiffLogic.uncheckAllItems();
+ }
+
+ // ----------------------
+
+ abstract protected Font getTreeFontItalic();
+
+ class PkgCellLabelProvider extends ColumnLabelProvider implements ITableFontProvider {
+
+ private final ITreeViewerColumn mColumn;
+
+ public PkgCellLabelProvider(ITreeViewerColumn column) {
+ super();
+ mColumn = column;
+ }
+
+ @Override
+ public String getText(Object element) {
+
+ if (mColumn == mIColumnName) {
+ if (element instanceof PkgCategory) {
+ return ((PkgCategory) element).getLabel();
+ } else if (element instanceof PkgItem) {
+ return getPkgItemName((PkgItem) element);
+ } else if (element instanceof IDescription) {
+ return ((IDescription) element).getShortDescription();
+ }
+
+ } else if (mColumn == mIColumnApi) {
+ int api = -1;
+ if (element instanceof PkgItem) {
+ api = ((PkgItem) element).getApi();
+ }
+ if (api >= 1) {
+ return Integer.toString(api);
+ }
+
+ } else if (mColumn == mIColumnRevision) {
+ if (element instanceof PkgItem) {
+ PkgItem pkg = (PkgItem) element;
+ return pkg.getRevision().toShortString();
+ }
+
+ } else if (mColumn == mIColumnStatus) {
+ if (element instanceof PkgItem) {
+ PkgItem pkg = (PkgItem) element;
+
+ switch(pkg.getState()) {
+ case INSTALLED:
+ Package update = pkg.getUpdatePkg();
+ if (update != null) {
+ return String.format(
+ "Update available: rev. %1$s",
+ update.getRevision().toShortString());
+ }
+ return "Installed";
+
+ case NEW:
+ Package p = pkg.getMainPackage();
+ if (p != null && p.hasCompatibleArchive()) {
+ return "Not installed";
+ } else {
+ return String.format("Not compatible with %1$s",
+ SdkConstants.currentPlatformName());
+ }
+ }
+ return pkg.getState().toString();
+
+ } else if (element instanceof Package) {
+ // This is an update package.
+ return "New revision " + ((Package) element).getRevision().toShortString();
+ }
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ 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.
+
+ PkgCategoryApi cat = (PkgCategoryApi) findCategoryForItem(item);
+ String apiLabel = cat.getApiLabel();
+ String platLabel = cat.getPlatformName();
+
+ if (platLabel != null && name.endsWith(platLabel)) {
+ return name.substring(0, name.length() - platLabel.length());
+
+ } else if (apiLabel != null && name.endsWith(apiLabel)) {
+ return name.substring(0, name.length() - apiLabel.length());
+
+ } else if (platLabel != null && item.isObsolete() && name.indexOf(platLabel) > 0) {
+ // For obsolete items, the format is "<base name> <platform name> (Obsolete)"
+ // so in this case only accept removing a platform name that is not at
+ // the end.
+ name = name.replace(platLabel, ""); //$NON-NLS-1$
+ }
+ }
+
+ // Collapse potential duplicated spacing
+ name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return name;
+ }
+
+ private PkgCategory findCategoryForItem(PkgItem item) {
+ List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
+ for (PkgCategory cat : cats) {
+ for (PkgItem i : cat.getItems()) {
+ if (i == item) {
+ return cat;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+
+ if (imgFactory != null) {
+ if (mColumn == mIColumnName) {
+ if (element instanceof PkgCategory) {
+ return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
+ } else if (element instanceof PkgItem) {
+ return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
+ }
+ return imgFactory.getImageForObject(element);
+
+ } else if (mColumn == mIColumnStatus && element instanceof PkgItem) {
+ PkgItem pi = (PkgItem) element;
+ switch(pi.getState()) {
+ case INSTALLED:
+ if (pi.hasUpdatePkg()) {
+ return imgFactory.getImageByName(PackagesPageIcons.ICON_PKG_UPDATE);
+ } else {
+ return imgFactory.getImageByName(PackagesPageIcons.ICON_PKG_INSTALLED);
+ }
+ case NEW:
+ Package p = pi.getMainPackage();
+ if (p != null && p.hasCompatibleArchive()) {
+ return imgFactory.getImageByName(PackagesPageIcons.ICON_PKG_NEW);
+ } else {
+ return imgFactory.getImageByName(PackagesPageIcons.ICON_PKG_INCOMPAT);
+ }
+ }
+ }
+ }
+ return super.getImage(element);
+ }
+
+ // -- ITableFontProvider
+
+ @Override
+ public Font getFont(Object element, int columnIndex) {
+ if (element instanceof PkgItem) {
+ if (((PkgItem) element).getState() == PkgState.NEW) {
+ return getTreeFontItalic();
+ }
+ } else if (element instanceof Package) {
+ // update package
+ return getTreeFontItalic();
+ }
+ return super.getFont(element);
+ }
+
+ // -- Tooltip support
+
+ @Override
+ public String getToolTipText(Object element) {
+ PkgItem pi = element instanceof PkgItem ? (PkgItem) element : null;
+ if (pi != null) {
+ element = pi.getMainPackage();
+ }
+ if (element instanceof IDescription) {
+ String s = getTooltipDescription((IDescription) element);
+
+ if (pi != null && pi.hasUpdatePkg()) {
+ s += "\n-----------------" + //$NON-NLS-1$
+ "\nUpdate Available:\n" + //$NON-NLS-1$
+ getTooltipDescription(pi.getUpdatePkg());
+ }
+
+ return s;
+ }
+ return super.getToolTipText(element);
+ }
+
+ private String getTooltipDescription(IDescription element) {
+ String s = element.getLongDescription();
+ if (element instanceof Package) {
+ Package p = (Package) element;
+
+ if (!p.isLocal()) {
+ // For non-installed item, try to find a download size
+ for (Archive a : p.getArchives()) {
+ if (!a.isLocal() && a.isCompatible()) {
+ s += '\n' + a.getSizeDescription();
+ break;
+ }
+ }
+ }
+
+ // Display info about where this package comes/came from
+ SdkSource src = p.getParentSource();
+ if (src != null) {
+ try {
+ URL url = new URL(src.getUrl());
+ String host = url.getHost();
+ if (p.isLocal()) {
+ s += String.format("\nInstalled from %1$s", host);
+ } else {
+ s += String.format("\nProvided by %1$s", host);
+ }
+ } catch (MalformedURLException ignore) {
+ }
+ }
+ }
+ return s;
+ }
+
+ @Override
+ public Point getToolTipShift(Object object) {
+ return new Point(15, 5);
+ }
+
+ @Override
+ public int getToolTipDisplayDelayTime(Object object) {
+ return 500;
+ }
+ }
+
+ interface ICheckboxTreeViewer extends IInputProvider {
+ void setContentProvider(PkgContentProvider pkgContentProvider);
+ void refresh();
+ void setInput(List<PkgCategory> cats);
+ Object[] getCheckedElements();
+ }
+
+ interface ITreeViewerColumn {
+ void setLabelProvider(ColumnLabelProvider labelProvider);
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockDownloadCache.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockDownloadCache.java
new file mode 100755
index 0000000..4adb9e2
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockDownloadCache.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+import com.android.sdklib.internal.repository.CanceledByUserException;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.utils.Pair;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.message.BasicHttpResponse;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/** A mock UpdaterData that simply records what would have been installed. */
+public class MockDownloadCache extends DownloadCache {
+
+ private final File mCacheRoot;
+
+ /** Map url => payload bytes, http code response.
+ * If the payload pair is null, an exception such as FNF is thrown. */
+ private final Map<String, Payload> mDirectPayloads = new HashMap<String, Payload>();
+ /** Map url => payload bytes, http code response.
+ * If the payload pair is null, an exception such as FNF is thrown. */
+ private final Map<String, Payload> mCachedPayloads = new HashMap<String, Payload>();
+
+ private final Map<String, Integer> mDirectHits = new TreeMap<String, Integer>();
+ private final Map<String, Integer> mCachedHits = new TreeMap<String, Integer>();
+
+ private Strategy mOverrideStrategy;
+
+ public final static int THROW_FNF = -1;
+
+ /**
+ * Creates a download cache with a {@code DIRECT} strategy and
+ * no root {@code $HOME/.android} folder, which effectively disables the cache.
+ */
+ public MockDownloadCache() {
+ super(DownloadCache.Strategy.DIRECT);
+ mCacheRoot = null;
+ }
+
+ /**
+ * Creates a download with the given strategy and the given cache root.
+ */
+ public MockDownloadCache(DownloadCache.Strategy strategy, File cacheRoot) {
+ super(strategy);
+ mCacheRoot = cacheRoot;
+ }
+
+ @Override
+ protected File initCacheRoot() {
+ return mCacheRoot;
+ }
+
+ /**
+ * Override the {@link DownloadCache.Strategy} of the cache.
+ * This lets us set it temporarily to {@link DownloadCache.Strategy#ONLY_CACHE},
+ * which will force {@link #openCachedUrl(String, ITaskMonitor)} to throw an FNF,
+ * essentially simulating an empty cache at first.
+ * <p/>
+ * Setting it back to null reverts the behavior to its default.
+ */
+ public void overrideStrategy(DownloadCache.Strategy strategy) {
+ mOverrideStrategy = strategy;
+ }
+
+ /**
+ * Register a direct payload response.
+ *
+ * @param url The URL to match.
+ * @param httpCode The expected response code.
+ * Use {@link #THROW_FNF} to mean an FNF should be thrown (which is what the
+ * httpClient stack seems to return instead of {@link HttpStatus#SC_NOT_FOUND}.)
+ * @param content The payload to return.
+ * As a shortcut a null will be replaced by an empty byte array.
+ */
+ public void registerDirectPayload(String url, int httpCode, byte[] content) {
+ mDirectPayloads.put(url, new Payload(httpCode, content));
+ }
+
+ /**
+ * Register a cached payload response.
+ *
+ * @param url The URL to match.
+ * @param content The payload to return or null to throw a FNF.
+ */
+ public void registerCachedPayload(String url, byte[] content) {
+ mCachedPayloads.put(url,
+ new Payload(content == null ? THROW_FNF : HttpStatus.SC_OK, content));
+ }
+
+ public String[] getDirectHits() {
+ ArrayList<String> list = new ArrayList<String>();
+ synchronized (mDirectHits) {
+ for (Entry<String, Integer> entry : mDirectHits.entrySet()) {
+ list.add(String.format("<%1$s : %2$d>",
+ entry.getKey(), entry.getValue().intValue()));
+ }
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ public String[] getCachedHits() {
+ ArrayList<String> list = new ArrayList<String>();
+ synchronized (mCachedHits) {
+ for (Entry<String, Integer> entry : mCachedHits.entrySet()) {
+ list.add(String.format("<%1$s : %2$d>",
+ entry.getKey(), entry.getValue().intValue()));
+ }
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ public void clearDirectHits() {
+ synchronized (mDirectHits) {
+ mDirectHits.clear();
+ }
+ }
+
+ public void clearCachedHits() {
+ synchronized (mCachedHits) {
+ mCachedHits.clear();
+ }
+ }
+
+ /**
+ * Override openDirectUrl to return one of the registered payloads or throw a FNF exception.
+ * This totally ignores the cache's {@link DownloadCache.Strategy}.
+ */
+ @Override
+ public Pair<InputStream, HttpResponse> openDirectUrl(
+ String urlString,
+ Header[] headers,
+ ITaskMonitor monitor) throws IOException, CanceledByUserException {
+
+ synchronized (mDirectHits) {
+ Integer count = mDirectHits.get(urlString);
+ mDirectHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
+ }
+
+ Payload payload = mDirectPayloads.get(urlString);
+
+ if (payload == null || payload.mHttpCode == THROW_FNF) {
+ throw new FileNotFoundException(urlString);
+ }
+
+ byte[] content = payload.mContent;
+ if (content == null) {
+ content = new byte[0];
+ }
+
+ InputStream is = new ByteArrayInputStream(content);
+ HttpResponse hr = new BasicHttpResponse(
+ new ProtocolVersion("HTTP", 1, 1),
+ payload.mHttpCode,
+ "Http-Code-" + payload.mHttpCode);
+
+ return Pair.of(is, hr);
+ }
+
+ /**
+ * Override openCachedUrl to return one of the registered payloads or throw a FNF exception.
+ * This totally ignores the cache's {@link DownloadCache.Strategy}.
+ * It will however throw a FNF if {@link #overrideStrategy(Strategy)} is set to
+ * {@link DownloadCache.Strategy#ONLY_CACHE}.
+ */
+ @Override
+ public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
+ throws IOException, CanceledByUserException {
+
+ synchronized (mCachedHits) {
+ Integer count = mCachedHits.get(urlString);
+ mCachedHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
+ }
+
+ if (Strategy.ONLY_CACHE.equals(mOverrideStrategy)) {
+ // Override the cache to read only "local cached" data.
+ // In this first phase, we assume there's nothing cached.
+ // TODO register first-pass files later.
+ throw new FileNotFoundException(urlString);
+ }
+
+ Payload payload = mCachedPayloads.get(urlString);
+
+ if (payload == null || payload.mHttpCode != HttpStatus.SC_OK) {
+ throw new FileNotFoundException(urlString);
+ }
+
+ byte[] content = payload.mContent;
+ if (content == null) {
+ content = new byte[0];
+ }
+
+ return new ByteArrayInputStream(content);
+ }
+
+ private static class Payload {
+ final byte[] mContent;
+ final int mHttpCode;
+
+ Payload(int httpCode, byte[] content) {
+ mHttpCode = httpCode;
+ mContent = content;
+ }
+ }
+
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
index 45e3163..f19bfcc 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/MockUpdaterData.java
@@ -25,6 +25,8 @@ import com.android.sdklib.internal.repository.MockEmptySdkManager;
import com.android.sdklib.internal.repository.NullTaskMonitor;
import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
import com.android.sdklib.internal.repository.archives.ArchiveReplacement;
+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
+import com.android.sdklib.internal.repository.sources.SdkSources;
import com.android.sdklib.mock.MockLog;
import com.android.sdkuilib.internal.repository.SettingsController.Settings;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
@@ -33,7 +35,6 @@ import com.android.utils.NullLogger;
import org.eclipse.swt.graphics.Image;
-import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
@@ -45,8 +46,17 @@ public class MockUpdaterData extends UpdaterData {
private final List<ArchiveReplacement> mInstalled = new ArrayList<ArchiveReplacement>();
- private DownloadCache mMockDownloadCache;
+ private DownloadCache mMockDownloadCache = new MockDownloadCache();
+ private final SdkSources mMockSdkSources = new SdkSources() {
+ @Override
+ public void loadUserAddons(ILogger log) {
+ // This source does not load user addons.
+ removeAll(SdkSourceCategory.USER_ADDONS);
+ };
+ };
+
+ /** Creates a {@link MockUpdaterData} using a {@link MockEmptySdkManager}. */
public MockUpdaterData() {
super(SDK_PATH, new MockLog());
@@ -54,6 +64,14 @@ public class MockUpdaterData extends UpdaterData {
setImageFactory(new NullImageFactory());
}
+ /** Creates a {@link MockUpdaterData} using the given {@link SdkManager}. */
+ public MockUpdaterData(SdkManager sdkManager) {
+ super(sdkManager.getLocation(), new MockLog());
+ setSdkManager(sdkManager);
+ setTaskFactory(new MockTaskFactory());
+ setImageFactory(new NullImageFactory());
+ }
+
/** Gives access to the internal {@link #installArchives(List, int)}. */
public void _installArchives(List<ArchiveInfo> result) {
installArchives(result, 0/*flags*/);
@@ -75,9 +93,19 @@ public class MockUpdaterData extends UpdaterData {
return createSettingsController(getSdkLog());
}
+ /** Override original implementation to do nothing. */
@Override
public void reloadSdk() {
- // bypass original implementation
+ // nop
+ }
+
+ /**
+ * Override original implementation to return a mock SdkSources that
+ * does not load user add-ons from the local .android/repository.cfg file.
+ */
+ @Override
+ public SdkSources getSources() {
+ return mMockSdkSources;
}
/** Returns a mock installer that simply records what would have been installed. */
@@ -98,30 +126,23 @@ public class MockUpdaterData extends UpdaterData {
};
}
- /**
- * Lazily initializes and returns a mock download cache that doesn't use the
- * local disk and doesn't cache anything.
- */
+ /** Returns a mock download cache. */
@Override
public DownloadCache getDownloadCache() {
- if (mMockDownloadCache == null) {
- mMockDownloadCache = new DownloadCache(DownloadCache.Strategy.DIRECT) {
- @Override
- protected File initCacheRoot() {
- // returns null, preventing the cache from using the default
- // $HOME/.android folder; this effectively disables the cache.
- return null;
- }
- };
- }
return mMockDownloadCache;
}
+ /** Overrides the mock download cache. */
+ public void setMockDownloadCache(DownloadCache mockDownloadCache) {
+ mMockDownloadCache = mockDownloadCache;
+ }
+
public void overrideSetting(String key, boolean boolValue) {
SettingsController sc = getSettingsController();
assert sc instanceof MockSettingsController;
((MockSettingsController)sc).overrideSetting(key, boolValue);
}
+
//------------
public static SettingsController createSettingsController(ILogger sdkLog) {
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/MockPackagesPageImpl.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/MockPackagesPageImpl.java
new file mode 100755
index 0000000..9ab36c1
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/MockPackagesPageImpl.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository.ui;
+
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
+import com.android.sdklib.util.SparseIntArray;
+import com.android.sdkuilib.internal.repository.MockDownloadCache;
+import com.android.sdkuilib.internal.repository.UpdaterData;
+import com.android.sdkuilib.internal.repository.core.PackageLoader;
+import com.android.sdkuilib.internal.repository.core.PkgCategory;
+import com.android.sdkuilib.internal.repository.core.PkgContentProvider;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Font;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MockPackagesPageImpl extends PackagesPageImpl {
+
+ public MockPackagesPageImpl(UpdaterData updaterData) {
+ super(updaterData);
+ }
+
+ /** UI is never disposed in the unit test. */
+ @Override
+ protected boolean isUiDisposed() {
+ return false;
+ }
+
+ /** Sync exec always executes immediately in the unit test, no threading is used. */
+ @Override
+ protected void syncExec(Runnable runnable) {
+ runnable.run();
+ }
+
+ private MockTreeViewer mTreeViewer;
+
+ @Override
+ void postCreate() {
+ mTreeViewer = new MockTreeViewer();
+ setITreeViewer(mTreeViewer);
+
+ setIColumns(new MockTreeColumn(mTreeViewer), // columnName
+ new MockTreeColumn(mTreeViewer), // columnApi
+ new MockTreeColumn(mTreeViewer), // columnRevision
+ new MockTreeColumn(mTreeViewer)); // columnStatus
+
+ super.postCreate();
+ }
+
+ @Override
+ protected void refreshViewerInput() {
+ super.setViewerInput();
+ }
+
+ @Override
+ protected boolean isSortByApi() {
+ return true;
+ }
+
+ @Override
+ protected Font getTreeFontItalic() {
+ return null;
+ }
+
+ @Override
+ protected void loadPackages(boolean useLocalCache, boolean overrideExisting) {
+ super.loadPackagesImpl(useLocalCache, overrideExisting);
+ }
+
+ /**
+ * In this mock version, we use the default {@link PackageLoader} which will
+ * use the {@link DownloadCache} from the {@link UpdaterData}. This should be
+ * the mock download cache, in which case we change the strategy at run-time
+ * to set it to only-cache on the first manager update.
+ */
+ @Override
+ protected PackageLoader getPackageLoader(boolean useLocalCache) {
+ DownloadCache dc = mUpdaterData.getDownloadCache();
+ assert dc instanceof MockDownloadCache;
+ if (dc instanceof MockDownloadCache) {
+ ((MockDownloadCache) dc).overrideStrategy(useLocalCache ? Strategy.ONLY_CACHE : null);
+ }
+ return mUpdaterData.getPackageLoader();
+ }
+
+ /**
+ * Get a dump-out of the tree in a format suitable for unit testing.
+ */
+ public String getMockTreeDisplay() throws Exception {
+ return mTreeViewer.getTreeDisplay();
+ }
+
+ private static class MockTreeViewer implements PackagesPageImpl.ICheckboxTreeViewer {
+ private final SparseIntArray mWidths = new SparseIntArray();
+ private final List<MockTreeColumn> mColumns = new ArrayList<MockTreeColumn>();
+ private List<PkgCategory> mInput;
+ private PkgContentProvider mPkgContentProvider;
+ private String mLastRefresh;
+ private static final String SPACE = " ";
+
+ @Override
+ public void setInput(List<PkgCategory> input) {
+ mInput = input;
+ refresh();
+ }
+
+ @Override
+ public Object getInput() {
+ return mInput;
+ }
+
+ @Override
+ public void setContentProvider(PkgContentProvider pkgContentProvider) {
+ mPkgContentProvider = pkgContentProvider;
+ }
+
+ @Override
+ public void refresh() {
+ // Recompute the display of the tree
+ StringBuilder sb = new StringBuilder();
+ boolean widthChanged = false;
+
+ for (int render = 0; render < (widthChanged ? 2 : 1); render++) {
+ widthChanged = false;
+ sb.setLength(0);
+ for (Object cat : mPkgContentProvider.getElements(mInput)) {
+ if (cat == null) {
+ continue;
+ }
+
+ if (sb.length() > 0) {
+ sb.append('\n');
+ }
+
+ widthChanged |= rowAsString(cat, sb, 3);
+
+ Object[] children = mPkgContentProvider.getElements(cat);
+ if (children == null) {
+ continue;
+ }
+ for (Object child : children) {
+ sb.append("\n L_");
+ widthChanged |= rowAsString(child, sb, 0);
+ }
+ }
+ }
+
+ mLastRefresh = sb.toString();
+ }
+
+ boolean rowAsString(Object element, StringBuilder sb, int space) {
+ boolean widthChanged = false;
+ sb.append("[] ");
+ for (int col = 0; col < mColumns.size(); col++) {
+ if (col > 0) {
+ sb.append(" | ");
+ }
+ String t = mColumns.get(col).getLabelProvider().getText(element);
+ if (t == null) {
+ t = "(null)";
+ }
+ int len = t.length();
+ int w = mWidths.get(col);
+ if (len > w) {
+ widthChanged = true;
+ mWidths.put(col, len);
+ w = len;
+ }
+ String pad = len >= w ? "" : SPACE.substring(SPACE.length() - w + len);
+ if (col == 0 && space > 0) {
+ sb.append(SPACE.substring(SPACE.length() - space));
+ }
+ if (col >= 1 && col <= 2) {
+ sb.append(pad);
+ }
+ sb.append(t);
+ if (col == 0 || col > 2) {
+ sb.append(pad);
+ }
+ }
+ return widthChanged;
+ }
+
+ @Override
+ public Object[] getCheckedElements() {
+ return null;
+ }
+
+ public void addColumn(MockTreeColumn mockTreeColumn) {
+ mColumns.add(mockTreeColumn);
+ }
+
+ public String getTreeDisplay() {
+ return mLastRefresh;
+ }
+ }
+
+ private static class MockTreeColumn implements PackagesPageImpl.ITreeViewerColumn {
+ private ColumnLabelProvider mLabelProvider;
+
+ public MockTreeColumn(MockTreeViewer treeViewer) {
+ treeViewer.addColumn(this);
+ }
+
+ @Override
+ public void setLabelProvider(ColumnLabelProvider labelProvider) {
+ mLabelProvider = labelProvider;
+ }
+
+ public ColumnLabelProvider getLabelProvider() {
+ return mLabelProvider;
+ }
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/SdkManagerUpgradeTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/SdkManagerUpgradeTest.java
new file mode 100755
index 0000000..00d9684
--- /dev/null
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/ui/SdkManagerUpgradeTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository.ui;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.SdkManagerTestCase;
+import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdkuilib.internal.repository.MockDownloadCache;
+import com.android.sdkuilib.internal.repository.MockUpdaterData;
+
+import java.util.Arrays;
+
+public class SdkManagerUpgradeTest extends SdkManagerTestCase {
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Create a mock page and list the current SDK state
+ */
+ public void testPackagesPage1() throws Exception {
+ SdkManager sdkman = getSdkManager();
+
+ MockUpdaterData updaterData = new MockUpdaterData(sdkman);
+ MockDownloadCache cache = (MockDownloadCache) updaterData.getDownloadCache();
+ updaterData.setupDefaultSources();
+
+ MockPackagesPageImpl pageImpl = new MockPackagesPageImpl(updaterData);
+ pageImpl.postCreate();
+ pageImpl.performFirstLoad();
+
+ // We have no network access possible and no mock download cache items.
+ // The only thing visible in the display are the local packages as set by
+ // the fake locally-installed SDK.
+ String actual = pageImpl.getMockTreeDisplay();
+ assertEquals(
+ "[] Tools | | | \n" +
+ " L_[] Android SDK Tools | | 0 | Installed\n" +
+ "[] Android 0.0 (API 0) | | | \n" +
+ " L_[] SDK Platform | | 1 | Installed\n" +
+ " L_[] Sources for Android SDK | | 0 | Installed\n" +
+ "[] Extras | | | ",
+ actual);
+
+ assertEquals(
+ "[]", // there are no direct downloads till we try to install.
+ Arrays.toString(cache.getDirectHits()));
+ assertEquals(
+ "[<https://dl-ssl.google.com/android/repository/addons_list-1.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/addons_list-2.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-5.xml : 2>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-6.xml : 2>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-7.xml : 2>, " +
+ "<https://dl-ssl.google.com/android/repository/repository.xml : 2>]",
+ Arrays.toString(cache.getCachedHits()));
+
+
+ // Now prepare a tools update on the server and reload
+ setupToolsXml1(cache);
+ cache.clearDirectHits();
+ cache.clearCachedHits();
+ pageImpl.fullReload();
+
+ actual = pageImpl.getMockTreeDisplay();
+ assertEquals(
+ "[] Tools | | | \n" +
+ " L_[] Android SDK Tools | | 0 | Update available: rev. 20.0.3\n" +
+ " L_[] Android SDK Platform-tools | | 14 | Not installed \n" +
+ "[] Android 0.0 (API 0) | | | \n" +
+ " L_[] SDK Platform | | 1 | Installed \n" +
+ " L_[] Sources for Android SDK | | 0 | Installed \n" +
+ "[] Extras | | | ",
+ actual);
+
+ assertEquals(
+ "[]", // there are no direct downloads till we try to install.
+ Arrays.toString(cache.getDirectHits()));
+ assertEquals(
+ "[<https://dl-ssl.google.com/android/repository/repository-5.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-6.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-7.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository.xml : 1>]",
+ Arrays.toString(cache.getCachedHits()));
+
+
+ // We should get the same display if we restart the manager page from scratch
+ // (e.g. simulate a first load)
+
+ cache.clearDirectHits();
+ cache.clearCachedHits();
+ pageImpl = new MockPackagesPageImpl(updaterData);
+ pageImpl.postCreate();
+ pageImpl.performFirstLoad();
+
+ actual = pageImpl.getMockTreeDisplay();
+ assertEquals(
+ "[] Tools | | | \n" +
+ " L_[] Android SDK Tools | | 0 | Update available: rev. 20.0.3\n" +
+ " L_[] Android SDK Platform-tools | | 14 | Not installed \n" +
+ "[] Android 0.0 (API 0) | | | \n" +
+ " L_[] SDK Platform | | 1 | Installed \n" +
+ " L_[] Sources for Android SDK | | 0 | Installed \n" +
+ "[] Extras | | | ",
+ actual);
+
+ assertEquals(
+ "[]", // there are no direct downloads till we try to install.
+ Arrays.toString(cache.getDirectHits()));
+ assertEquals(
+ "[<https://dl-ssl.google.com/android/repository/repository-5.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-6.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository-7.xml : 1>, " +
+ "<https://dl-ssl.google.com/android/repository/repository.xml : 1>]",
+ Arrays.toString(cache.getCachedHits()));
+ }
+
+ private void setupToolsXml1(MockDownloadCache cache) throws Exception {
+ String repoXml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<sdk:sdk-repository xmlns:sdk=\"http://schemas.android.com/sdk/android/repository/7\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
+ "<sdk:license id=\"android-sdk-license\" type=\"text\">Blah blah blah.</sdk:license>\n" +
+ "\n" +
+ "<sdk:platform-tool>\n" +
+ " <sdk:revision>\n" +
+ " <sdk:major>14</sdk:major>\n" +
+ " </sdk:revision>\n" +
+ " <sdk:archives>\n" +
+ " <sdk:archive arch=\"any\" os=\"windows\">\n" +
+ " <sdk:size>11159472</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">6028258d8f2fba14d8b40c3cf507afa0289aaa13</sdk:checksum>\n" +
+ " <sdk:url>platform-tools_r14-windows.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " <sdk:archive arch=\"any\" os=\"linux\">\n" +
+ " <sdk:size>10985068</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">6e2bc329c9485eb383172cbc2cde8b0c0cd1843f</sdk:checksum>\n" +
+ " <sdk:url>platform-tools_r14-linux.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " <sdk:archive arch=\"any\" os=\"macosx\">\n" +
+ " <sdk:size>11342461</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">4a015090c6a209fc33972acdbc65745e0b3c08b9</sdk:checksum>\n" +
+ " <sdk:url>platform-tools_r14-macosx.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " </sdk:archives>\n" +
+ "</sdk:platform-tool>\n" +
+ "\n" +
+ "<sdk:tool>\n" +
+ " <sdk:revision>\n" +
+ " <sdk:major>20</sdk:major>\n" +
+ " <sdk:minor>0</sdk:minor>\n" +
+ " <sdk:micro>3</sdk:micro>\n" +
+ " </sdk:revision>\n" +
+ " <sdk:min-platform-tools-rev>\n" +
+ " <sdk:major>12</sdk:major>\n" +
+ " </sdk:min-platform-tools-rev>\n" +
+ " <sdk:archives>\n" +
+ " <sdk:archive arch=\"any\" os=\"windows\">\n" +
+ " <sdk:size>90272048</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">54fb94168e631e211910f88aa40c532205730dd4</sdk:checksum>\n" +
+ " <sdk:url>tools_r20.0.3-windows.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " <sdk:archive arch=\"any\" os=\"linux\">\n" +
+ " <sdk:size>82723559</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">09bc633b406ae81981e3a0db19426acbb01ef219</sdk:checksum>\n" +
+ " <sdk:url>tools_r20.0.3-linux.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " <sdk:archive arch=\"any\" os=\"macosx\">\n" +
+ " <sdk:size>58197071</sdk:size>\n" +
+ " <sdk:checksum type=\"sha1\">09cee5ff3226277a6f0c07dcd29cba4ffc2e1da4</sdk:checksum>\n" +
+ " <sdk:url>tools_r20.0.3-macosx.zip</sdk:url>\n" +
+ " </sdk:archive>\n" +
+ " </sdk:archives>\n" +
+ "</sdk:tool>\n" +
+ "\n" +
+ "</sdk:sdk-repository>\n";
+
+ String url = SdkRepoConstants.URL_GOOGLE_SDK_SITE +
+ String.format(SdkRepoConstants.URL_DEFAULT_FILENAME, SdkRepoConstants.NS_LATEST_VERSION);
+
+ cache.registerCachedPayload(url, repoXml.getBytes("UTF-8"));
+ }
+
+}