aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2011-05-18 21:16:38 -0700
committerRaphael Moll <ralf@android.com>2011-05-20 11:59:43 -0700
commit9c7a0fd82e89b2f637919ff4371213b87624ae6e (patch)
tree59c6b5d584b47dc4d4ef7f29ccbc50bb14d0ace1 /sdkmanager
parent20ab1d2b7251aef8546f3c63e1a8b8eb006c0180 (diff)
downloadsdk-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')
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AdtUpdateDialog.java271
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackageManager.java399
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java184
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java18
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ImgDisabledButton.java50
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/ToggleButton.java118
-rwxr-xr-xsdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/SwtBaseDialog.java54
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;
}
//-------