aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--anttasks/src/com/android/ant/ManifestMergerTask.java26
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java40
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceCollectorDialog.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceOptionsDialog.java23
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceOptions.java6
-rwxr-xr-xeclipse/scripts/create_all_symlinks.sh9
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ICallback.java37
-rw-r--r--manifmerger/src/com/android/manifmerger/Main.java2
-rwxr-xr-xmanifmerger/src/com/android/manifmerger/ManifestMerger.java92
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java12
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml28
-rwxr-xr-xmanifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml16
-rw-r--r--sdkmanager/libs/sdklib/Android.mk11
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java6
-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
-rwxr-xr-xsdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java17
-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
34 files changed, 1812 insertions, 583 deletions
diff --git a/anttasks/src/com/android/ant/ManifestMergerTask.java b/anttasks/src/com/android/ant/ManifestMergerTask.java
index 597e1e9..00939dc 100644
--- a/anttasks/src/com/android/ant/ManifestMergerTask.java
+++ b/anttasks/src/com/android/ant/ManifestMergerTask.java
@@ -16,8 +16,11 @@
package com.android.ant;
+import com.android.manifmerger.ICallback;
import com.android.manifmerger.ManifestMerger;
import com.android.manifmerger.MergerLog;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
import com.android.sdklib.io.FileOp;
import com.android.utils.StdLogger;
@@ -124,8 +127,27 @@ public class ManifestMergerTask extends SingleDependencyTask {
} else {
System.out.println(String.format("Merging manifests from project and %d libraries.",
libraries.size()));
- ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(
- new StdLogger(StdLogger.Level.VERBOSE)));
+ ManifestMerger merger = new ManifestMerger(
+ MergerLog.wrapSdkLog(new StdLogger(StdLogger.Level.VERBOSE)),
+ new ICallback() {
+ SdkManager mManager;
+ @Override
+ public int queryCodenameApiLevel(String codename) {
+ if (mManager == null) {
+ File sdkDir = TaskHelper.getSdkLocation(getProject());
+ mManager = SdkManager.createManager(sdkDir.getPath(),
+ new StdLogger(StdLogger.Level.VERBOSE));
+ }
+ if (mManager != null) {
+ IAndroidTarget t = mManager.getTargetFromHashString(
+ IAndroidTarget.PLATFORM_HASH_PREFIX + codename);
+ if (t != null) {
+ return t.getVersion().getApiLevel();
+ }
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
if (merger.process(
new File(mOutManifest),
appManifestFile,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index d4c8525..9ad2e32 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -37,6 +37,7 @@ import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErro
import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
@@ -828,28 +829,29 @@ public class PreCompilerBuilder extends BaseBuilder {
// TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create
// and maintain error markers.
- ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(new ILogger() {
-
- @Override
- public void warning(@NonNull String warningFormat, Object... args) {
- AdtPlugin.printToConsole(getProject(), String.format(warningFormat, args));
- }
+ ManifestMerger merger = new ManifestMerger(
+ MergerLog.wrapSdkLog(new ILogger() {
+ @Override
+ public void warning(@NonNull String warningFormat, Object... args) {
+ AdtPlugin.printToConsole(getProject(), String.format(warningFormat, args));
+ }
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- AdtPlugin.printToConsole(getProject(), String.format(msgFormat, args));
- }
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ AdtPlugin.printToConsole(getProject(), String.format(msgFormat, args));
+ }
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- info(msgFormat, args);
- }
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ info(msgFormat, args);
+ }
- @Override
- public void error(Throwable t, String errorFormat, Object... args) {
- errors.add(String.format(errorFormat, args));
- }
- }));
+ @Override
+ public void error(Throwable t, String errorFormat, Object... args) {
+ errors.add(String.format(errorFormat, args));
+ }
+ }),
+ new AdtManifestMergeCallback());
File[] libManifests = new File[libProjects.size()];
int libIndex = 0;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java
new file mode 100755
index 0000000..265552b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtManifestMergeCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.sdk;
+
+import com.android.manifmerger.ICallback;
+import com.android.manifmerger.ManifestMerger;
+import com.android.sdklib.IAndroidTarget;
+
+/**
+ * A {@link ManifestMerger} {@link ICallback} that returns the
+ * proper API level for known API codenames.
+ */
+public class AdtManifestMergeCallback implements ICallback {
+ @Override
+ public int queryCodenameApiLevel(String codename) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ IAndroidTarget t = sdk.getTargetFromHashString(
+ IAndroidTarget.PLATFORM_HASH_PREFIX + codename);
+ if (t != null) {
+ return t.getVersion().getApiLevel();
+ }
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index 36e09f9..cb45522 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -15,8 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.wizards.templates;
-import static com.android.SdkConstants.FD_NATIVE_LIBS;
-import static com.android.SdkConstants.*;
+import static com.android.SdkConstants.DOT_AIDL;
import static com.android.SdkConstants.DOT_FTL;
import static com.android.SdkConstants.DOT_JAVA;
import static com.android.SdkConstants.DOT_RS;
@@ -24,13 +23,13 @@ import static com.android.SdkConstants.DOT_SVG;
import static com.android.SdkConstants.DOT_TXT;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.FD_NATIVE_LIBS;
import static com.android.ide.eclipse.adt.internal.wizards.templates.InstallDependencyPage.SUPPORT_LIBRARY_NAME;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateManager.getTemplateRootFolder;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
@@ -39,6 +38,7 @@ import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback;
import com.android.manifmerger.ManifestMerger;
import com.android.manifmerger.MergerLog;
import com.android.resources.ResourceFolderType;
@@ -792,9 +792,12 @@ class TemplateHandler {
private boolean mergeManifest(Document currentManifest, Document fragment) {
// TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create
// and maintain error markers.
- ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(AdtPlugin.getDefault()));
- return currentManifest != null && fragment != null
- && merger.process(currentManifest, fragment);
+ ManifestMerger merger = new ManifestMerger(
+ MergerLog.wrapSdkLog(AdtPlugin.getDefault()),
+ new AdtManifestMergeCallback());
+ return currentManifest != null &&
+ fragment != null &&
+ merger.process(currentManifest, fragment);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
index 9acff50..5e97305 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
@@ -126,7 +126,8 @@ public class CollectTraceAction implements IWorkbenchWindowActionDelegate {
try {
if (!SYSTEM_APP.equals(traceOptions.appToTrace)) {
- startActivity(device, traceOptions.appToTrace, traceOptions.activityToTrace);
+ startActivity(device, traceOptions.appToTrace, traceOptions.activityToTrace,
+ traceOptions.isActivityNameFullyQualified);
}
} catch (Exception e) {
MessageDialog.openError(shell, "Setup GL Trace",
@@ -272,19 +273,24 @@ public class CollectTraceAction implements IWorkbenchWindowActionDelegate {
}
}
- private void startActivity(IDevice device, String appPackage, String activity)
+ private void startActivity(IDevice device, String appPackage, String activity,
+ boolean isActivityNameFullyQualified)
throws TimeoutException, AdbCommandRejectedException,
ShellCommandUnresponsiveException, IOException, InterruptedException {
killApp(device, appPackage); // kill app if it is already running
waitUntilAppKilled(device, appPackage, KILL_TIMEOUT);
- String activityPath = appPackage;
+ StringBuilder activityPath = new StringBuilder(appPackage);
if (!activity.isEmpty()) {
- activityPath = String.format("%s/.%s", appPackage, activity); //$NON-NLS-1$
+ activityPath.append('/');
+ if (!isActivityNameFullyQualified) {
+ activityPath.append('.');
+ }
+ activityPath.append(activity);
}
String startAppCmd = String.format(
"am start --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
- activityPath);
+ activityPath.toString());
Semaphore launchCompletionSempahore = new Semaphore(0);
StartActivityOutputReceiver receiver = new StartActivityOutputReceiver(
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceCollectorDialog.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceCollectorDialog.java
index 4dfcafa..56dc8e9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceCollectorDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceCollectorDialog.java
@@ -35,11 +35,13 @@ import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import java.io.IOException;
+import java.text.DecimalFormat;
/** Dialog displayed while the trace is being streamed from device to host. */
public class GLTraceCollectorDialog extends TitleAreaDialog {
private static final String TITLE = "OpenGL ES Trace";
private static final String DEFAULT_MESSAGE = "Trace collection in progress.";
+ private static final DecimalFormat SIZE_FORMATTER = new DecimalFormat("#.##"); //$NON-NLS-1$
private TraceOptions mTraceOptions;
private final TraceFileWriter mTraceFileWriter;
@@ -194,7 +196,7 @@ public class GLTraceCollectorDialog extends TitleAreaDialog {
double fileSize = mTraceFileWriter.getCurrentFileSize();
fileSize /= (1024 * 1024); // convert to size in MB
- final String frameSize = String.format("%.2g MB", fileSize); //$NON-NLS-1$
+ final String frameSize = SIZE_FORMATTER.format(fileSize) + " MB";
Display.getDefault().syncExec(new Runnable() {
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceOptionsDialog.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceOptionsDialog.java
index 1ddb6d8..764ba98 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceOptionsDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTraceOptionsDialog.java
@@ -64,6 +64,7 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
private Combo mDeviceCombo;
private Text mAppPackageToTraceText;
private Text mActivityToTraceText;
+ private Button mIsActivityFullyQualifiedButton;
private Text mTraceFilePathText;
private String mSelectedDevice = "";
@@ -74,6 +75,7 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
private static boolean sCollectFbOnEglSwap = true;
private static boolean sCollectFbOnGlDraw = false;
private static boolean sCollectTextureData = false;
+ private static boolean sIsActivityFullyQualified = false;
private IDevice[] mDevices;
public GLTraceOptionsDialog(Shell parentShell) {
@@ -95,12 +97,20 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
mDevices = AndroidDebugBridge.getBridge().getDevices();
createDeviceDropdown(c, mDevices);
+ createSeparator(c);
+
createLabel(c, "Application Package:");
createAppToTraceText(c, "e.g. com.example.package");
createLabel(c, "Activity to launch:");
createActivityToTraceText(c, "Leave blank to launch default activity");
+ createLabel(c, "");
+ createIsFullyQualifedActivityButton(c,
+ "Activity name is fully qualified, do not prefix with package name");
+
+ createSeparator(c);
+
createLabel(c, "Data Collection Options:");
createCaptureImageOptions(c);
@@ -245,6 +255,15 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
return mActivityToTraceText;
}
+ private Button createIsFullyQualifedActivityButton(Composite parent, String message) {
+ mIsActivityFullyQualifiedButton = new Button(parent, SWT.CHECK);
+ mIsActivityFullyQualifiedButton.setText(message);
+ mIsActivityFullyQualifiedButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mIsActivityFullyQualifiedButton.setSelection(sIsActivityFullyQualified);
+
+ return mIsActivityFullyQualifiedButton;
+ }
+
private void validateAndSetMessage() {
DialogStatus status = validateDialog();
mOkButton.setEnabled(status.valid);
@@ -324,6 +343,7 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
if (mActivityToTrace.startsWith(".")) { //$NON-NLS-1$
mActivityToTrace = mActivityToTrace.substring(1);
}
+ sIsActivityFullyQualified = mIsActivityFullyQualifiedButton.getSelection();
mTraceFilePath = mTraceFilePathText.getText().trim();
mSelectedDevice = mDeviceCombo.getText();
@@ -355,6 +375,7 @@ public class GLTraceOptionsDialog extends TitleAreaDialog {
public TraceOptions getTraceOptions() {
return new TraceOptions(mSelectedDevice, mAppPackageToTrace, mActivityToTrace,
- mTraceFilePath, sCollectFbOnEglSwap, sCollectFbOnGlDraw, sCollectTextureData);
+ sIsActivityFullyQualified, mTraceFilePath, sCollectFbOnEglSwap,
+ sCollectFbOnGlDraw, sCollectTextureData);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceOptions.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceOptions.java
index e7ad17e..ac9fb6b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceOptions.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceOptions.java
@@ -26,6 +26,8 @@ public class TraceOptions {
/** Activity to trace. */
public final String activityToTrace;
+ public final boolean isActivityNameFullyQualified;
+
/** Path where the trace file should be saved. */
public final String traceDestination;
@@ -38,11 +40,13 @@ public class TraceOptions {
/** Flag indicating whether texture data should be captured on glTexImage*() */
public final boolean collectTextureData;
- public TraceOptions(String device, String appPackage, String activity, String destinationPath,
+ public TraceOptions(String device, String appPackage, String activity,
+ boolean isActivityNameFullyQualified, String destinationPath,
boolean collectFbOnEglSwap, boolean collectFbOnGlDraw, boolean collectTextureData) {
this.device = device;
this.appToTrace = appPackage;
this.activityToTrace = activity;
+ this.isActivityNameFullyQualified = isActivityNameFullyQualified;
this.traceDestination = destinationPath;
this.collectFbOnEglSwap = collectFbOnEglSwap;
this.collectFbOnGlDraw = collectFbOnGlDraw;
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/manifmerger/src/com/android/manifmerger/ICallback.java b/manifmerger/src/com/android/manifmerger/ICallback.java
new file mode 100755
index 0000000..26ae40d
--- /dev/null
+++ b/manifmerger/src/com/android/manifmerger/ICallback.java
@@ -0,0 +1,37 @@
+/*
+ * 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.manifmerger;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Callback used by the ManifestMerger to query the caller.
+ */
+public interface ICallback {
+
+ public static final int UNKNOWN_CODENAME = 0;
+
+ /**
+ * Queries the caller to find the API level for a given provisional API codename,
+ * as used in the <uses-sdk> {@code minSdkVersion} field.
+ *
+ * @param codename A non-null codename string.
+ * @return The integer API > 0 for the given codename, or {@link #UNKNOWN_CODENAME}.
+ */
+ public int queryCodenameApiLevel(@NonNull String codename);
+
+}
diff --git a/manifmerger/src/com/android/manifmerger/Main.java b/manifmerger/src/com/android/manifmerger/Main.java
index 78da1a3..c48033f 100644
--- a/manifmerger/src/com/android/manifmerger/Main.java
+++ b/manifmerger/src/com/android/manifmerger/Main.java
@@ -57,7 +57,7 @@ public class Main {
// Create a new ManifestMerger and call its process method.
// It will take care of validating its own arguments.
- ManifestMerger mm = new ManifestMerger(MergerLog.wrapSdkLog(mSdkLog));
+ ManifestMerger mm = new ManifestMerger(MergerLog.wrapSdkLog(mSdkLog), null);
String[] libPaths = mArgvParser.getParamLibs();
File[] libFiles = new File[libPaths.length];
diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
index c5153e9..2d5d3ec 100755
--- a/manifmerger/src/com/android/manifmerger/ManifestMerger.java
+++ b/manifmerger/src/com/android/manifmerger/ManifestMerger.java
@@ -36,7 +36,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
@@ -45,7 +45,7 @@ import javax.xml.xpath.XPathExpressionException;
/**
* Merges a library manifest into a main application manifest.
* <p/>
- * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog)} then
+ * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then
* call {@link ManifestMerger#process(File, File, File[])}.
* <p/>
* <pre> Merge operations:
@@ -69,6 +69,7 @@ import javax.xml.xpath.XPathExpressionException;
* => Add. OK if already defined.
* E- uses-sdk:
* {@code @minSdkVersion}: error if dest&lt;lib. Never automatically change dest minsdk.
+ * Codenames are accepted if we can resolve their API level.
* {@code @targetSdkVersion}: warning if dest&lt;lib.
* Never automatically change dest targetsdk.
* {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
@@ -117,7 +118,9 @@ import javax.xml.xpath.XPathExpressionException;
public class ManifestMerger {
/** Logger object. Never null. */
- private IMergerLog mLog;
+ private final IMergerLog mLog;
+ /** An optional callback that the merger can use to query the calling SDK. */
+ private final ICallback mCallback;
private XPath mXPath;
private Document mMainDoc;
@@ -125,8 +128,15 @@ public class ManifestMerger {
private String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX;
private int destMinSdk;
- public ManifestMerger(IMergerLog log) {
+ /**
+ * Creates a new {@link ManifestMerger}.
+ *
+ * @param log A non-null merger log to capture all warnings, errors and their location.
+ * @param callback An optional callback that the merger can use to query the calling SDK.
+ */
+ public ManifestMerger(@NonNull IMergerLog log, @Nullable ICallback callback) {
mLog = log;
+ mCallback = callback;
}
/**
@@ -726,11 +736,12 @@ public class ManifestMerger {
}
/**
- * Checks (but does not merge) uses-sdk attribues using the following rules:
+ * Checks (but does not merge) uses-sdk attributes using the following rules:
* <pre>
* - {@code @minSdkVersion}: error if dest&lt;lib. Never automatically change dest minsdk.
* - {@code @targetSdkVersion}: warning if dest&lt;lib. Never automatically change destination.
* - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * - The API level can be a codename if we have a callback that can convert it to an integer.
* </pre>
* @param libDoc The library document to merge from. Must not be null.
* @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
@@ -742,8 +753,8 @@ public class ManifestMerger {
Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
- AtomicReference<Object> destValue = new AtomicReference<Object>(1); // String or Integer
- AtomicReference<Object> srcValue = new AtomicReference<Object>(1);
+ AtomicInteger destValue = new AtomicInteger(1);
+ AtomicInteger srcValue = new AtomicInteger(1);
AtomicBoolean destImplied = new AtomicBoolean(true);
AtomicBoolean srcImplied = new AtomicBoolean(true);
@@ -756,17 +767,16 @@ public class ManifestMerger {
destValue, srcValue,
destImplied, srcImplied);
- if (result && destValue.get() instanceof Integer && srcValue.get() instanceof Integer) {
+ if (result) {
// Make it an error for an application to use a library with a greater
// minSdkVersion. This means the library code may crash unexpectedly.
// TODO it would be nice to be able to work around this in case the
// user think s/he knows what s/he's doing.
// We could define a simple XML comment flag: <!-- @NoMinSdkVersionMergeError -->
- destMinSdk = (Integer) destValue.get();
+ destMinSdk = destValue.get();
- int srcMinSdk = (Integer) srcValue.get();
- if (destMinSdk < srcMinSdk) {
+ if (destMinSdk < srcValue.get()) {
mLog.conflict(Severity.ERROR,
xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
@@ -795,14 +805,13 @@ public class ManifestMerger {
destImplied, srcImplied);
result &= result2;
- if (result2 && destValue.get() instanceof Integer && srcValue.get() instanceof Integer) {
+ if (result2) {
// Make it a warning for an application to use a library with a greater
// targetSdkVersion.
- int destTargetSdk = destImplied.get() ? destMinSdk : (Integer) destValue.get();
+ int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get();
- int srcMinSdk = (Integer) srcValue.get();
- if (destTargetSdk < srcMinSdk) {
+ if (destTargetSdk < srcValue.get()) {
mLog.conflict(Severity.WARNING,
xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
@@ -833,13 +842,14 @@ public class ManifestMerger {
Element destUsesSdk,
Element srcUsesSdk,
String attr,
- AtomicReference<Object> destValue,
- AtomicReference<Object> srcValue,
+ AtomicInteger destValue,
+ AtomicInteger srcValue,
AtomicBoolean destImplied,
AtomicBoolean srcImplied) {
String s = destUsesSdk == null ? "" //$NON-NLS-1$
: destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
+ boolean result = true;
assert s != null;
s = s.trim();
try {
@@ -848,9 +858,27 @@ public class ManifestMerger {
destImplied.set(false);
}
} catch (NumberFormatException e) {
- // Versions can contain codenames such as "JellyBean"
- destValue.set(s);
- destImplied.set(false);
+ boolean error = true;
+ if (mCallback != null) {
+ // Versions can contain codenames such as "JellyBean".
+ // We'll accept it only if have a callback that can give us the API level for it.
+ int apiLevel = mCallback.queryCodenameApiLevel(s);
+ if (apiLevel > ICallback.UNKNOWN_CODENAME) {
+ destValue.set(apiLevel);
+ destImplied.set(false);
+ error = false;
+ }
+ }
+ if (error) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
+ "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
+ attr,
+ s);
+ result = false;
+ }
}
s = srcUsesSdk == null ? "" //$NON-NLS-1$
@@ -863,12 +891,28 @@ public class ManifestMerger {
srcImplied.set(false);
}
} catch (NumberFormatException e) {
- // Versions can contain codenames such as "JellyBean"
- destValue.set(s);
- destImplied.set(false);
+ boolean error = true;
+ if (mCallback != null) {
+ // Versions can contain codenames such as "JellyBean".
+ // We'll accept it only if have a callback that can give us the API level for it.
+ int apiLevel = mCallback.queryCodenameApiLevel(s);
+ if (apiLevel > ICallback.UNKNOWN_CODENAME) {
+ srcValue.set(apiLevel);
+ srcImplied.set(false);
+ error = false;
+ }
+ }
+ if (error) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
+ "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
+ attr,
+ s);
+ result = false;
+ }
}
- return true;
+ return result;
}
diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java
index 13302e7..8e854f9 100755
--- a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java
+++ b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTestCase.java
@@ -377,7 +377,17 @@ abstract class ManifestMergerTestCase extends TestCase {
void processTestFiles(TestFiles testFiles) throws Exception {
MockLog log = new MockLog();
IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
- ManifestMerger merger = new ManifestMerger(mergerLog);
+ ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(String codename) {
+ if ("ApiCodename1".equals(codename)) {
+ return 1;
+ } else if ("ApiCodename10".equals(codename)) {
+ return 10;
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
boolean processOK = merger.process(testFiles.getActualResult(),
testFiles.getMain(),
testFiles.getLibs());
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
index 6984d26..b38aad3 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
@@ -81,6 +81,26 @@
</manifest>
+@lib7_parsingError
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Parsing errors -->
+ <uses-sdk android:minSdkVersion="InvalidMinSdk" android:targetSdkVersion="InvalidTargetSdk" />
+
+</manifest>
+
+
+@lib8_parsingCodename
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Test code names -->
+ <uses-sdk android:minSdkVersion="ApiCodename1" android:targetSdkVersion="ApiCodename10" />
+
+</manifest>
+
+
@result
<manifest
@@ -105,6 +125,8 @@ E [ManifestMergerTest0_main.xml:4, ManifestMergerTest2_lib2.xml:3] Main manifest
Note: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1.
E [ManifestMergerTest0_main.xml:4, ManifestMergerTest3_lib3.xml:3] Main manifest has <uses-sdk android:minSdkVersion='1'> but library uses minSdkVersion='11'
Note: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1.
-E [ManifestMergerTest4_lib4_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='abcd'>: must be an integer number.
-E [ManifestMergerTest5_lib5_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='123456789123456789'>: must be an integer number.
-E [ManifestMergerTest6_lib6_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='0xFFFFFFFFFFFFFFFF'>: must be an integer number.
+E [ManifestMergerTest4_lib4_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='abcd'>: must be an integer number or codename.
+E [ManifestMergerTest5_lib5_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='123456789123456789'>: must be an integer number or codename.
+E [ManifestMergerTest6_lib6_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='0xFFFFFFFFFFFFFFFF'>: must be an integer number or codename.
+E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse <uses-sdk minSdkVersion='InvalidMinSdk'>: must be an integer number or codename.
+E [ManifestMergerTest7_lib7_parsingError.xml:4] Failed to parse <uses-sdk targetSdkVersion='InvalidTargetSdk'>: must be an integer number or codename.
diff --git a/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
index 303855b..2fdbaaa 100755
--- a/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
+++ b/manifmerger/tests/src/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
@@ -1,6 +1,6 @@
-
+#
# Test uses-sdk: there's a warning if the main manifest defines a targetSdkVersion that
-# .
+# is smaller than what the libraries target.
#
@fails
@@ -13,10 +13,10 @@
android:versionCode="100"
android:versionName="1.0.0">
- <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <!-- This app requires cupcake and targets at least 10. -->
<uses-sdk
android:minSdkVersion="3"
- android:targetSdkVersion="4"
+ android:targetSdkVersion="ApiCodename10"
/>
<application />
@@ -28,7 +28,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <!-- This lib requires cupcake and targets 11 which is > 10 so it's a warning. -->
<uses-sdk
android:minSdkVersion="3"
android:targetSdkVersion="11"
@@ -57,10 +57,10 @@
android:versionCode="100"
android:versionName="1.0.0">
- <!-- This app requires cupcake and targets honeycomb's yummy Holo theme. -->
+ <!-- This app requires cupcake and targets at least 10. -->
<uses-sdk
android:minSdkVersion="3"
- android:targetSdkVersion="4"
+ android:targetSdkVersion="ApiCodename10"
/>
<application />
@@ -70,4 +70,4 @@
@errors
-W [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has <uses-sdk android:targetSdkVersion='4'> but library uses targetSdkVersion='11'
+W [ManifestMergerTest0_main.xml:4, ManifestMergerTest1_lib1.xml:4] Main manifest has <uses-sdk android:targetSdkVersion='10'> but library uses targetSdkVersion='11'
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/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
index 596e837..18577cf 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
@@ -25,6 +25,12 @@ import java.util.Map;
*/
public interface IAndroidTarget extends Comparable<IAndroidTarget> {
+ /**
+ * Prefix used to build hash strings for platform targets
+ * @see SdkManager#getTargetFromHashString(String)
+ */
+ public static final String PLATFORM_HASH_PREFIX = "android-";
+
/** OS Path to the "android.jar" file. */
public final static int ANDROID_JAR = 1;
/** OS Path to the "framework.aidl" file. */
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/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java
index 645d2bb..4989ec3 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java
@@ -36,7 +36,8 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
// a legacy armeabi system image (this is not a separate system image package)
assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
+ "[Android SDK Tools, revision 0, " +
+ "SDK Platform Android 0.0, API 0, revision 1, " +
"Sources for Android SDK, API 0, revision 0]",
Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
@@ -63,7 +64,7 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
monitor)));
assertEquals(
- "[]",
+ "[Android SDK Tools, revision 0]",
Arrays.toString(parser.parseSdk(sdkman.getLocation(),
sdkman,
LocalSdkParser.PARSE_TOOLS,
@@ -82,7 +83,8 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
t = sdkman.getTargets()[0];
assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
+ "[Android SDK Tools, revision 0, " +
+ "SDK Platform Android 0.0, API 0, revision 1, " +
"Sources for Android SDK, API 0, revision 0]",
Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
@@ -98,7 +100,8 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
sdkman.reloadSdk(getLog());
assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
+ "[Android SDK Tools, revision 0, " +
+ "SDK Platform Android 0.0, API 0, revision 1, " +
"ARM EABI v7a System Image, Android API 0, revision 0, " +
"ARM EABI System Image, Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0]",
@@ -112,7 +115,8 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_INTEL_ATOM));
assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
+ "[Android SDK Tools, revision 0, " +
+ "SDK Platform Android 0.0, API 0, revision 1, " +
"ARM EABI v7a System Image, Android API 0, revision 0, " +
"ARM EABI System Image, Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0, " +
@@ -120,7 +124,8 @@ public class LocalSdkParserTest extends SdkManagerTestCase {
Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
assertEquals(
- "[SDK Platform Android 0.0, API 0, revision 1, " +
+ "[Android SDK Tools, revision 0, " +
+ "SDK Platform Android 0.0, API 0, revision 1, " +
"ARM EABI v7a System Image, Android API 0, revision 0, " +
"ARM EABI System Image, Android API 0, revision 0, " +
"Sources for Android SDK, API 0, revision 0, " +
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"));
+ }
+
+}