diff options
author | Raphael Moll <ralf@android.com> | 2011-05-18 21:16:38 -0700 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2011-05-20 11:59:43 -0700 |
commit | 9c7a0fd82e89b2f637919ff4371213b87624ae6e (patch) | |
tree | 59c6b5d584b47dc4d4ef7f29ccbc50bb14d0ace1 /sdkmanager | |
parent | 20ab1d2b7251aef8546f3c63e1a8b8eb006c0180 (diff) | |
download | sdk-9c7a0fd82e89b2f637919ff4371213b87624ae6e.zip sdk-9c7a0fd82e89b2f637919ff4371213b87624ae6e.tar.gz sdk-9c7a0fd82e89b2f637919ff4371213b87624ae6e.tar.bz2 |
SDK Manager dialog to perform specific updates from ADT.
As a working case example, this adds an ADT project
context menu to add the Android Compatibility JAR
to an android project.
Change-Id: Icd86930b72558240dc9f5a6f732478253b8cb0fb
Diffstat (limited to 'sdkmanager')
7 files changed, 914 insertions, 180 deletions
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdtUpdateDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdtUpdateDialog.java new file mode 100755 index 0000000..911b3a5 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdtUpdateDialog.java @@ -0,0 +1,271 @@ +/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.repository;
+
+
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.internal.repository.ExtraPackage;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.PlatformPackage;
+import com.android.sdkuilib.internal.repository.PackageManager.IAutoInstallTask;
+import com.android.sdkuilib.internal.tasks.ProgressView;
+import com.android.sdkuilib.internal.tasks.ProgressViewFactory;
+import com.android.sdkuilib.ui.GridDataBuilder;
+import com.android.sdkuilib.ui.GridLayoutBuilder;
+import com.android.sdkuilib.ui.SwtBaseDialog;
+import com.android.util.Pair;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.File;
+
+/**
+ * This is a private implementation of UpdateWindow for ADT,
+ * designed to install a very specific package.
+ * <p/>
+ * Example of usage:
+ * <pre>
+ * AdtUpdateDialog dialog = new AdtUpdateDialog(
+ * AdtPlugin.getDisplay().getActiveShell(),
+ * new AdtConsoleSdkLog(),
+ * sdk.getSdkLocation());
+ *
+ * Pair<Boolean, File> result = dialog.installExtraPackage(
+ * "android", "compatibility"); //$NON-NLS-1$ //$NON-NLS-2$
+ * or
+ * Pair<Boolean, File> result = dialog.installPlatformPackage(11);
+ * </pre>
+ */
+public class AdtUpdateDialog extends SwtBaseDialog {
+
+ private static final String APP_NAME = "Android SDK Manager";
+ private final UpdaterData mUpdaterData;
+
+ private Boolean mResultCode = Boolean.FALSE;
+ private File mResultPath = null;
+ private SettingsController mSettingsController;
+ private PackageFilter mPackageFilter;
+ private PackageManager mPackageMananger;
+
+ private ProgressBar mProgressBar;
+ private Label mStatusText;
+
+ /**
+ * Creates a new {@link AdtUpdateDialog}.
+ * Callers will want to call {@link #installExtraPackage} or
+ * {@link #installPlatformPackage} after this.
+ *
+ * @param parentShell The existing parent shell. Must not be null.
+ * @param sdkLog An SDK logger. Must not be null.
+ * @param osSdkRoot The current SDK root OS path. Must not be null or empty.
+ */
+ public AdtUpdateDialog(
+ Shell parentShell,
+ ISdkLog sdkLog,
+ String osSdkRoot) {
+ super(parentShell, SWT.NONE, APP_NAME);
+ mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
+ }
+
+ /**
+ * Displays the update dialog and triggers installation of the requested {@code extra}
+ * package with the specified vendor and path attributes.
+ * <p/>
+ * Callers must not try to reuse this dialog after this call.
+ *
+ * @param vendor The extra package vendor string to match.
+ * @param path The extra package path string to match.
+ * @return A boolean indicating whether the installation was successful (meaning the package
+ * was either already present, or got installed or updated properly) and a {@link File}
+ * with the path to the root folder of the package. The file is null when the boolean
+ * is false, otherwise it should point to an existing valid folder.
+ * @wbp.parser.entryPoint
+ */
+ public Pair<Boolean, File> installExtraPackage(String vendor, String path) {
+ mPackageFilter = PackageFilter.createExtraFilter(vendor, path);
+ open();
+ return Pair.of(mResultCode, mResultPath);
+ }
+
+ /**
+ * Displays the update dialog and triggers installation of the requested {@code platform}
+ * package with the specified api level attributes.
+ * <p/>
+ * Callers must not try to reuse this dialog after this call.
+ *
+ * @param apiLevel The platform api level to match.
+ * @return A boolean indicating whether the installation was successful (meaning the package
+ * was either already present, or got installed or updated properly) and a {@link File}
+ * with the path to the root folder of the package. The file is null when the boolean
+ * is false, otherwise it should point to an existing valid folder.
+ */
+ public Pair<Boolean, File> installPlatformPackage(int apiLevel) {
+ mPackageFilter = PackageFilter.createPlatformFilter(apiLevel);
+ open();
+ return Pair.of(mResultCode, mResultPath);
+ }
+
+ @Override
+ protected void createContents() {
+ Shell shell = getShell();
+ shell.setMinimumSize(new Point(450, 100));
+ shell.setSize(450, 100);
+
+ GridLayoutBuilder.create(shell).columns(1);
+
+ Composite composite1 = new Composite(shell, SWT.NONE);
+ composite1.setLayout(new GridLayout(1, false));
+ GridDataBuilder.create(composite1).fill().grab();
+
+ mProgressBar = new ProgressBar(composite1, SWT.NONE);
+ GridDataBuilder.create(mProgressBar).hFill().hGrab();
+
+ mStatusText = new Label(composite1, SWT.NONE);
+ mStatusText.setText("Status Placeholder"); //$NON-NLS-1$ placeholder
+ GridDataBuilder.create(mStatusText).hFill().hGrab();
+ }
+
+ @Override
+ protected void postCreate() {
+ ProgressViewFactory factory = new ProgressViewFactory();
+ factory.setProgressView(new ProgressView(
+ mStatusText, mProgressBar, null /*buttonStop*/));
+ mUpdaterData.setTaskFactory(factory);
+
+ setupSources();
+ initializeSettings();
+
+ if (mUpdaterData.checkIfInitFailed()) {
+ close();
+ return;
+ }
+
+ mUpdaterData.broadcastOnSdkLoaded();
+
+ mPackageMananger = new PackageManager(mUpdaterData) {
+ @Override
+ public void updatePackageTable() {
+ // pass
+ }
+ };
+
+ }
+
+ @Override
+ protected void eventLoop() {
+ mPackageMananger.loadPackages();
+ mPackageMananger.performAutoInstallTask(new IAutoInstallTask() {
+ public boolean acceptPackage(Package pkg) {
+ // Is this the package we want to install?
+ return mPackageFilter.accept(pkg);
+ }
+
+ public void setResult(Package pkg, boolean success, File installPath) {
+ // Capture the result from the installation.
+ mResultCode = Boolean.valueOf(success);
+ mResultPath = installPath;
+ }
+
+ public void taskCompleted() {
+ // We can close that window now.
+ close();
+ }
+ });
+
+ super.eventLoop();
+ }
+
+ // -- Start of internal part ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ // --- Public API -----------
+
+
+ // --- Internals & UI Callbacks -----------
+
+ /**
+ * Used to initialize the sources.
+ */
+ private void setupSources() {
+ mUpdaterData.setupDefaultSources();
+ }
+
+ /**
+ * Initializes settings.
+ */
+ private void initializeSettings() {
+ mSettingsController = mUpdaterData.getSettingsController();
+ mSettingsController.loadSettings();
+ mSettingsController.applySettings();
+ }
+
+ // ----
+
+ private static abstract class PackageFilter {
+
+ abstract boolean accept(Package pkg);
+
+ public static PackageFilter createExtraFilter(
+ final String vendor,
+ final String path) {
+ return new PackageFilter() {
+ String mVendor = vendor;
+ String mPath = path;
+ @Override
+ boolean accept(Package pkg) {
+ if (pkg instanceof ExtraPackage) {
+ ExtraPackage ep = (ExtraPackage) pkg;
+ return ep.getVendor().equals(mVendor) &&
+ ep.getPath().equals(mPath);
+ }
+ return false;
+ }
+ };
+ }
+
+ public static PackageFilter createPlatformFilter(final int apiLevel) {
+ return new PackageFilter() {
+ int mApiLevel = apiLevel;
+ @Override
+ boolean accept(Package pkg) {
+ if (pkg instanceof PlatformPackage) {
+ PlatformPackage pp = (PlatformPackage) pkg;
+ AndroidVersion v = pp.getVersion();
+ return !v.isPreview() && v.getApiLevel() == mApiLevel;
+ }
+ return false;
+ }
+ };
+ }
+ }
+
+
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+
+ // -----
+
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageManager.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageManager.java new file mode 100755 index 0000000..0447385 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageManager.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sdkuilib.internal.repository; + +import com.android.sdklib.internal.repository.Archive; +import com.android.sdklib.internal.repository.IPackageVersion; +import com.android.sdklib.internal.repository.ITask; +import com.android.sdklib.internal.repository.ITaskMonitor; +import com.android.sdklib.internal.repository.Package; +import com.android.sdklib.internal.repository.SdkSource; +import com.android.sdklib.internal.repository.Package.UpdateInfo; + +import org.eclipse.swt.widgets.Display; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages packages fetched from the remote SDK Repository and keeps track + * of their state compared with the current local SDK installation. + * <p/> + * This is currently just an embryonic manager which should evolve over time. + * There's a lot of overlap with the functionality of {@link UpdaterData} so + * merging them together might just be the way to go. + * <p/> + * Right now all it does is provide the ability to load packages using a task + * and perform some semi-automatic installation of a given package if available. + */ +abstract class PackageManager { + + private final List<PkgItem> mPackages = new ArrayList<PkgItem>(); + private final UpdaterData mUpdaterData; + + /** + * Interface describing the task of installing a specific package. + * For details on the operation, + * see {@link PackageManager#performAutoInstallTask(IAutoInstallTask)}. + * + * @see PackageManager#performAutoInstallTask(IAutoInstallTask) + */ + public interface IAutoInstallTask { + /** + * Called by the install task for every package available (new ones, updates as well + * as existing ones that don't have a potential update.) + * The method should return true if this is the package that should be installed. + */ + public boolean acceptPackage(Package pkg); + + /** + * Called when the accepted package has been installed, successfully or not. + * If an already installed (aka existing) package has been accepted, this will + * be called with a 'true' success and the actual install path. + */ + public void setResult(Package pkg, boolean success, File installPath); + + /** + * Called when the task is done iterating and completed. + */ + public void taskCompleted(); + } + + /** + * Creates a new PackageManager associated with the given {@link UpdaterData}. + * + * @param updaterData The {@link UpdaterData}. Must not be null. + */ + public PackageManager(UpdaterData updaterData) { + mUpdaterData = updaterData; + } + + /** + * Derived classes sohuld implement that to update their display of the current + * package list. Called by {@link #loadPackages()} when the package list has + * significantly changed. + * <p/> + * The package list is retrieved using {@link #getPackages()} which is guaranteed + * not to change during this call. + * <p/> + * This is called from a non-UI thread. An UI interaction done here <em>must</em> + * be wrapped in a {@link Display#syncExec(Runnable)}. + */ + public abstract void updatePackageTable(); + + /** + * Returns the current package list. + */ + public List<PkgItem> getPackages() { + return mPackages; + } + + /** + * Load all packages from the remote repository and + * updates the {@link PkgItem} package list. + * + * This runs in an {@link ITask}. The call is blocking. + */ + public void loadPackages() { + if (mUpdaterData == null) { + return; + } + + mPackages.clear(); + + // get local packages + for (Package pkg : mUpdaterData.getInstalledPackages()) { + PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED); + mPackages.add(pi); + } + + // get remote packages + final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); + mUpdaterData.loadRemoteAddonsList(); + mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() { + public void run(ITaskMonitor monitor) { + SdkSource[] sources = mUpdaterData.getSources().getAllSources(); + for (SdkSource source : sources) { + Package[] pkgs = source.getPackages(); + if (pkgs == null) { + source.load(monitor, forceHttp); + pkgs = source.getPackages(); + } + if (pkgs == null) { + continue; + } + + nextPkg: for(Package pkg : pkgs) { + boolean isUpdate = false; + for (PkgItem pi: mPackages) { + if (pi.isSameAs(pkg)) { + continue nextPkg; + } + if (pi.isUpdatedBy(pkg)) { + isUpdate = true; + break; + } + } + + if (!isUpdate) { + PkgItem pi = new PkgItem(pkg, PkgState.NEW); + mPackages.add(pi); + } + } + + // Dynamically update the table while we load after each source. + // Since the official Android source gets loaded first, it makes the + // window look non-empty a lot sooner. + updatePackageTable(); + } + + monitor.setDescription("Done loading %1$d packages from %2$d sources", + mPackages.size(), + sources.length); + } + }); + } + + /** + * Executes the given {@link IAutoInstallTask} on the current package list. + * That is for each package known, the install task is queried to find if + * the package is the one to be installed or updated. + * <p/> + * - If an already installed package is accepted by the task, it is returned. <br/> + * - If a new package (remotely available but not installed locally) is accepted, + * the user will be <em>prompted</em> for permission to install it. <br/> + * - If an existing package has updates, the install task will be accept if it + * accepts one of the updating packages, and if yes the the user will be + * <em>prompted</em> for permission to install it. <br/> + * <p/> + * Only one package can be accepted, after which the task is completed. + * There is no direct return value, {@link IAutoInstallTask#setResult} is called on the + * result of the accepted package. + * When the task is completed, {@link IAutoInstallTask#taskCompleted()} is called. + * <p/> + * The call is blocking. Although the name says "Task", this is not an {@link ITask} + * running in its own thread but merely a synchronous call. + * + * @param installTask The task to perform. + */ + public void performAutoInstallTask(IAutoInstallTask installTask) { + try { + for (PkgItem item : mPackages) { + Package pkg = null; + switch(item.getState()) { + case NEW: + if (installTask.acceptPackage(item.getPackage())) { + pkg = item.getPackage(); + } + break; + case HAS_UPDATE: + for (Package upd : item.getUpdatePkgs()) { + if (installTask.acceptPackage(upd)) { + pkg = upd; + break; + } + } + break; + case INSTALLED: + if (installTask.acceptPackage(item.getPackage())) { + // If the caller is accepting an installed package, + // return a success and give the package's install path + pkg = item.getPackage(); + Archive[] a = pkg.getArchives(); + // an installed package should have one local compatible archive + if (a.length == 1 && a[0].isCompatible()) { + installTask.setResult( + pkg, + true /*success*/, + new File(a[0].getLocalOsPath())); + return; + } + } + } + + if (pkg != null) { + // Try to install this package if it has one compatible archive. + Archive a = null; + + for (Archive a2 : pkg.getArchives()) { + if (a2.isCompatible()) { + a = a2; + break; + } + } + + if (a != null) { + ArrayList<Archive> archives = new ArrayList<Archive>(); + archives.add(a); + + mUpdaterData.updateOrInstallAll_WithGUI( + archives, + true /* includeObsoletes */); + + // loadPackages will also re-enable the UI + loadPackages(); + + // Try to locate the installed package in the new package list + for (PkgItem item2 : mPackages) { + if (item2.getState() == PkgState.INSTALLED) { + Package pkg2 = item2.getPackage(); + if (pkg2.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) { + Archive[] a2 = pkg.getArchives(); + if (a2.length == 1 && a2[0].isCompatible()) { + installTask.setResult( + pkg, + true /*success*/, + new File(a2[0].getLocalOsPath())); + return; + } + } + } + } + + // We failed to find the installed package. + // We'll assume we failed to install it. + installTask.setResult(pkg, false /*success*/, null); + return; + } + } + + } + } finally { + installTask.taskCompleted(); + } + } + + /** + * The state of the a given {@link PkgItem}, that is the relationship between + * a given remote package and the local repository. + */ + public enum PkgState { + /** + * Package is locally installed and has no update available on remote sites. + */ + INSTALLED, + + /** + * Package is installed and has an update available. + * In this case, {@link PkgItem#getUpdatePkgs()} provides the list of 1 or more + * packages that can update this {@link PkgItem}. + */ + HAS_UPDATE, + + /** + * There's a new package available on the remote site that isn't + * installed locally. + */ + NEW + } + + /** + * A {@link PkgItem} represents one {@link Package} available for the manager. + * It can be either a locally installed package, or a remotely available package. + * If the later, it can be either a new package or an update for a locally installed one. + * <p/> + * In the case of an update, the {@link PkgItem#getPackage()} represents the currently + * installed package and there's a separate list of {@link PkgItem#getUpdatePkgs()} that + * links to the updating packages. Note that in a typical repository there should only + * one update for a given installed package however the system is designed to be more + * generic and allow many. + */ + public static class PkgItem implements Comparable<PkgItem> { + private final Package mPkg; + private PkgState mState; + private List<Package> mUpdatePkgs; + + public PkgItem(Package pkg, PkgState state) { + mPkg = pkg; + mState = state; + assert mPkg != null; + } + + public boolean isObsolete() { + return mPkg.isObsolete(); + } + + public boolean isSameAs(Package pkg) { + return mPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE; + } + + /** + * Check whether the 'pkg' argument updates this package. + * If it does, record it as a sub-package. + * Returns true if it was recorded as an update, false otherwise. + */ + public boolean isUpdatedBy(Package pkg) { + if (mPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) { + if (mUpdatePkgs == null) { + mUpdatePkgs = new ArrayList<Package>(); + } + mUpdatePkgs.add(pkg); + mState = PkgState.HAS_UPDATE; + return true; + } + + return false; + } + + public String getName() { + return mPkg.getListDescription(); + } + + public int getRevision() { + return mPkg.getRevision(); + } + + public String getDescription() { + return mPkg.getDescription(); + } + + public Package getPackage() { + return mPkg; + } + + public PkgState getState() { + return mState; + } + + public SdkSource getSource() { + if (mState == PkgState.NEW) { + return mPkg.getParentSource(); + } else { + return null; + } + } + + public int getApi() { + return mPkg instanceof IPackageVersion ? + ((IPackageVersion) mPkg).getVersion().getApiLevel() : + -1; + } + + public List<Package> getUpdatePkgs() { + return mUpdatePkgs; + } + + public Archive[] getArchives() { + return mPkg.getArchives(); + } + + public int compareTo(PkgItem pkg) { + return getPackage().compareTo(pkg.getPackage()); + } + } +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java index b761510..a0eadbb 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java @@ -18,7 +18,6 @@ package com.android.sdkuilib.internal.repository; import com.android.sdklib.internal.repository.Archive; import com.android.sdklib.internal.repository.IDescription; -import com.android.sdklib.internal.repository.IPackageVersion; import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdklib.internal.repository.Package; @@ -26,7 +25,8 @@ import com.android.sdklib.internal.repository.PlatformPackage; import com.android.sdklib.internal.repository.PlatformToolPackage; import com.android.sdklib.internal.repository.SdkSource; import com.android.sdklib.internal.repository.ToolPackage; -import com.android.sdklib.internal.repository.Package.UpdateInfo; +import com.android.sdkuilib.internal.repository.PackageManager.PkgItem; +import com.android.sdkuilib.internal.repository.PackageManager.PkgState; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.repository.ISdkChangeListener; import com.android.util.Pair; @@ -123,7 +123,7 @@ public class PackagesPage extends UpdaterPage private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>(); - private final List<PkgItem> mPackages = new ArrayList<PkgItem>(); + private final PackageManagerImpl mPkgManager; private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>(); private final UpdaterData mUpdaterData; @@ -154,13 +154,14 @@ public class PackagesPage extends UpdaterPage super(parent, swtStyle); mUpdaterData = updaterData; + mPkgManager = new PackageManagerImpl(updaterData); createContents(this); postCreate(); //$hide$ } public void onPageSelected() { - if (mPackages.isEmpty()) { + if (mPkgManager.getPackages().isEmpty()) { // Initialize the package list the first time the page is shown. loadPackages(); } @@ -498,67 +499,13 @@ public class PackagesPage extends UpdaterPage return; } + try { enableUi(mGroupPackages, false); - boolean firstLoad = mPackages.size() == 0; - mPackages.clear(); - - // get local packages - for (Package pkg : mUpdaterData.getInstalledPackages()) { - PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED); - mPackages.add(pi); - } - - // get remote packages - final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); - mUpdaterData.loadRemoteAddonsList(); - mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() { - public void run(ITaskMonitor monitor) { - SdkSource[] sources = mUpdaterData.getSources().getAllSources(); - for (SdkSource source : sources) { - Package[] pkgs = source.getPackages(); - if (pkgs == null) { - source.load(monitor, forceHttp); - pkgs = source.getPackages(); - } - if (pkgs == null) { - continue; - } - - nextPkg: for(Package pkg : pkgs) { - boolean isUpdate = false; - for (PkgItem pi: mPackages) { - if (pi.isSameAs(pkg)) { - continue nextPkg; - } - if (pi.isUpdatedBy(pkg)) { - isUpdate = true; - break; - } - } + boolean firstLoad = mPkgManager.getPackages().isEmpty(); - if (!isUpdate) { - PkgItem pi = new PkgItem(pkg, PkgState.NEW); - mPackages.add(pi); - } - } - - // Dynamically update the table while we load after each source. - // Since the official Android source gets loaded first, it makes the - // window look non-empty a lot sooner. - mGroupPackages.getDisplay().syncExec(new Runnable() { - public void run() { - sortPackages(true /*updateButtons*/); - } - }); - } - - monitor.setDescription("Done loading %1$d packages from %2$d sources", - mPackages.size(), - sources.length); - } - }); + mPkgManager.loadPackages(); if (firstLoad) { // set the initial expanded state @@ -639,7 +586,7 @@ public class PackagesPage extends UpdaterPage mCategories.add(cat); } - for (PkgItem item : mPackages) { + for (PkgItem item : mPkgManager.getPackages()) { if (!keepItem(item)) { continue; } @@ -752,7 +699,7 @@ public class PackagesPage extends UpdaterPage mCategories.clear(); Set<SdkSource> sourceSet = new HashSet<SdkSource>(); - for (PkgItem item : mPackages) { + for (PkgItem item : mPkgManager.getPackages()) { if (keepItem(item)) { sourceSet.add(item.getSource()); } @@ -783,7 +730,7 @@ public class PackagesPage extends UpdaterPage key.toString(), iconRef); - for (PkgItem item : mPackages) { + for (PkgItem item : mPkgManager.getPackages()) { if (item.getSource() == source) { cat.getItems().add(item); } @@ -1312,112 +1259,27 @@ public class PackagesPage extends UpdaterPage } } - public enum PkgState { - /** - * Package is locally installed and has no update available on remote sites. - */ - INSTALLED, - - /** - * Package is installed and has an update available. - * In this case, {@link PkgItem#getUpdatePkgs()} provides the list of 1 or more - * packages that can update this {@link PkgItem}. - */ - HAS_UPDATE, - - /** - * There's a new package available on the remote site that isn't - * installed locally. - */ - NEW - } - - public static class PkgItem implements Comparable<PkgItem> { - private final Package mPkg; - private PkgState mState; - private List<Package> mUpdatePkgs; + private class PackageManagerImpl extends PackageManager { - public PkgItem(Package pkg, PkgState state) { - mPkg = pkg; - mState = state; - assert mPkg != null; + public PackageManagerImpl(UpdaterData updaterData) { + super(updaterData); } - public boolean isObsolete() { - return mPkg.isObsolete(); - } - - public boolean isSameAs(Package pkg) { - return mPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE; - } - - /** - * Check whether the 'pkg' argument updates this package. - * If it does, record it as a sub-package. - * Returns true if it was recorded as an update, false otherwise. - */ - public boolean isUpdatedBy(Package pkg) { - if (mPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) { - if (mUpdatePkgs == null) { - mUpdatePkgs = new ArrayList<Package>(); + @Override + public void updatePackageTable() { + // Dynamically update the table while we load after each source. + // Since the official Android source gets loaded first, it makes the + // window look non-empty a lot sooner. + mGroupPackages.getDisplay().syncExec(new Runnable() { + public void run() { + sortPackages(true /* updateButtons */); } - mUpdatePkgs.add(pkg); - mState = PkgState.HAS_UPDATE; - return true; - } - - return false; - } - - public String getName() { - return mPkg.getListDescription(); - } - - public int getRevision() { - return mPkg.getRevision(); - } - - public String getDescription() { - return mPkg.getDescription(); - } - - public Package getPackage() { - return mPkg; - } - - public PkgState getState() { - return mState; - } - - public SdkSource getSource() { - if (mState == PkgState.NEW) { - return mPkg.getParentSource(); - } else { - return null; - } - } - - public int getApi() { - return mPkg instanceof IPackageVersion ? - ((IPackageVersion) mPkg).getVersion().getApiLevel() : - -1; - } - - public List<Package> getUpdatePkgs() { - return mUpdatePkgs; - } - - public Archive[] getArchives() { - return mPkg.getArchives(); + }); } - public int compareTo(PkgItem pkg) { - return getPackage().compareTo(pkg.getPackage()); - } } - // --- Implementation of ISdkChangeListener --- public void onSdkLoaded() { diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java index 9878c85..c5727e0 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java @@ -65,6 +65,10 @@ public final class ProgressView implements IProgressUiProvider { /**
* Creates a new {@link ProgressView} object, a simple "holder" for the various
* widgets used to display and update a progress + status bar.
+ *
+ * @param label The label of the current operation. Must not be null.
+ * @param progressBar The progress bar showing the current progress. Must not be null.
+ * @param stopButton The stop button. Optional. Can be null.
*/
public ProgressView(Label label, ProgressBar progressBar, Control stopButton) {
mLabel = label;
@@ -72,13 +76,15 @@ public final class ProgressView implements IProgressUiProvider { mProgressBar.setEnabled(false);
mStopButton = stopButton;
- mStopButton.addListener(SWT.Selection, new Listener() {
- public void handleEvent(Event event) {
- if (mState == State.ACTIVE) {
- changeState(State.STOP_PENDING);
+ if (mStopButton != null) {
+ mStopButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event event) {
+ if (mState == State.ACTIVE) {
+ changeState(State.STOP_PENDING);
+ }
}
- }
- });
+ });
+ }
}
/**
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ImgDisabledButton.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ImgDisabledButton.java new file mode 100755 index 0000000..af1702b --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ImgDisabledButton.java @@ -0,0 +1,50 @@ +/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.widgets;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * A label that can display 2 images depending on its enabled/disabled state.
+ * This acts as a button by firing the {@link SWT#Selection} listener.
+ */
+public class ImgDisabledButton extends ToggleButton {
+ public ImgDisabledButton(Composite parent, int style,
+ Image imageEnabled, Image imageDisabled) {
+ super(parent, style, imageEnabled, imageDisabled);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ updateImage();
+ redraw();
+ }
+
+ @Override
+ public void setState(int state) {
+ throw new UnsupportedOperationException(); // not available for this type of button
+ }
+
+ @Override
+ public int getState() {
+ return (isDisposed() || !isEnabled()) ? 1 : 0;
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ToggleButton.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ToggleButton.java new file mode 100755 index 0000000..fc22dec --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ToggleButton.java @@ -0,0 +1,118 @@ +/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkuilib.internal.widgets;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+
+/**
+ * A label that can display 2 images depending on its internal state.
+ * This acts as a button by firing the {@link SWT#Selection} listener.
+ */
+public class ToggleButton extends CLabel {
+ private Image[] mImage = new Image[2];
+ private boolean mMouseIn;
+ private int mState = 0;
+
+
+ public ToggleButton(Composite parent, int style, Image image1, Image image2) {
+ super(parent, style);
+ mImage[0] = image1;
+ mImage[1] = image2;
+ updateImage();
+
+ addMouseListener(new MouseListener() {
+ public void mouseDown(MouseEvent e) {
+ // pass
+ }
+
+ public void mouseUp(MouseEvent e) {
+ // We select on mouse-up, as it should be properly done since this is the
+ // only way a user can cancel a button click by moving out of the button.
+ if (mMouseIn && e.button == 1) {
+ notifyListeners(SWT.Selection, new Event());
+ }
+ }
+
+ public void mouseDoubleClick(MouseEvent e) {
+ if (mMouseIn && e.button == 1) {
+ notifyListeners(SWT.DefaultSelection, new Event());
+ }
+ }
+ });
+
+ addMouseTrackListener(new MouseTrackListener() {
+ public void mouseExit(MouseEvent e) {
+ if (mMouseIn) {
+ mMouseIn = false;
+ redraw();
+ }
+ }
+
+ public void mouseEnter(MouseEvent e) {
+ if (!mMouseIn) {
+ mMouseIn = true;
+ redraw();
+ }
+ }
+
+ public void mouseHover(MouseEvent e) {
+ // pass
+ }
+ });
+ }
+
+ @Override
+ public int getStyle() {
+ int style = super.getStyle();
+ if (mMouseIn) {
+ style |= SWT.SHADOW_IN;
+ }
+ return style;
+ }
+
+ /**
+ * Sets current state.
+ * @param state A value 0 or 1.
+ */
+ public void setState(int state) {
+ assert state == 0 || state == 1;
+ mState = state;
+ updateImage();
+ redraw();
+ }
+
+ /**
+ * Returns the current state
+ * @return Returns the current state, either 0 or 1.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ protected void updateImage() {
+ setImage(mImage[getState()]);
+ }
+}
+
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/SwtBaseDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/SwtBaseDialog.java index f731baf..addb856 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/SwtBaseDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/SwtBaseDialog.java @@ -63,6 +63,7 @@ public abstract class SwtBaseDialog extends Dialog { /** Last dialog size for this session, different for each dialog class. */ private static Map<Class<?>, Point> sLastSizeMap = new HashMap<Class<?>, Point>(); + private volatile boolean mQuitRequested = false; private boolean mReturnValue; private Shell mShell; @@ -85,17 +86,22 @@ public abstract class SwtBaseDialog extends Dialog { * @return The last value set using {@link #setReturnValue(boolean)} or false by default. */ public boolean open() { - createShell(); - createContents(); - positionShell(); - postCreate(); - mShell.open(); - mShell.layout(); - Display display = getParent().getDisplay(); - while (!mShell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } + if (!mQuitRequested) { + createShell(); + } + if (!mQuitRequested) { + createContents(); + } + if (!mQuitRequested) { + positionShell(); + } + if (!mQuitRequested) { + postCreate(); + } + if (!mQuitRequested) { + mShell.open(); + mShell.layout(); + eventLoop(); } return mReturnValue; @@ -140,6 +146,22 @@ public abstract class SwtBaseDialog extends Dialog { protected abstract void postCreate(); /** + * Run the event loop. + * This is called from {@link #open()} after {@link #postCreate()} and + * after the window has been shown on screen. + * Derived classes might want to use this as a place to start automated + * tasks that will update the UI. + */ + protected void eventLoop() { + Display display = getParent().getDisplay(); + while (!mQuitRequested && !mShell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + + /** * Returns the current value that {@link #open()} will return to the caller. * Default is false. */ @@ -166,10 +188,16 @@ public abstract class SwtBaseDialog extends Dialog { /** * Saves the dialog size and close the dialog. * The {@link #open()} method will given return value (see {@link #setReturnValue(boolean)}. + * <p/> + * It's safe to call this method before the shell is initialized, + * in which case the dialog will close as soon as possible. */ protected void close() { - saveSize(); - getShell().close(); + if (mShell != null) { + saveSize(); + getShell().close(); + } + mQuitRequested = true; } //------- |