diff options
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<lib. Never automatically change dest minsdk. + * Codenames are accepted if we can resolve their API level. * {@code @targetSdkVersion}: warning if dest<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<lib. Never automatically change dest minsdk. * - {@code @targetSdkVersion}: warning if dest<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")); + } + +} |