diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 988866f3f..8982202d1 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -392,6 +392,11 @@ Native tests:
package="com.android.providers.contactstests"
coverage_target="ContactsProvider" />
+
+
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package has the same revision as the current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return True if the replacement package is a suitable update for this one.
+ */
+ @Override
+ public boolean canBeUpdatedBy(Package replacementPackage) {
+ if (!super.canBeUpdatedBy(replacementPackage)) {
+ return false;
+ }
+
+ AddonPackage newPkg = (AddonPackage) replacementPackage;
+ return newPkg.getName().equalsIgnoreCase(this.getName()) &&
+ newPkg.getRevision() > this.getRevision();
+ }
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index 4770765b6..66e32558a 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -104,4 +104,27 @@ public class DocPackage extends Package {
public File getInstallFolder(String osSdkRoot) {
return new File(osSdkRoot, SdkConstants.FD_DOCS);
}
+
+ /**
+ * Computes whether the given doc package is a suitable update for the current package.
+ * The base method checks the class type.
+ * The doc package also tests the API level and revision number: the revision number must
+ * always be bumped. The API level can be the same or greater.
+ *
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package has the same revision as the current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return True if the replacement package is a suitable update for this one.
+ */
+ @Override
+ public boolean canBeUpdatedBy(Package replacementPackage) {
+ if (!super.canBeUpdatedBy(replacementPackage)) {
+ return false;
+ }
+
+ DocPackage newPkg = (DocPackage) replacementPackage;
+ return newPkg.getRevision() > this.getRevision() &&
+ newPkg.getApiLevel() >= this.getApiLevel();
+ }
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index 64ff007ce..cdac3c11a 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -202,6 +202,23 @@ public abstract class Package implements IDescription {
*/
public abstract File getInstallFolder(String osSdkRoot);
+ /**
+ * Computes whether the given package is a suitable update for the current package.
+ * The base class method only checks that the {@link Package} class type is the same.
+ * Derived classes must add more specific checks, including the revision number.
+ *
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package has the same revision as the current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return True if the replacement package is a suitable update for this one.
+ */
+ public boolean canBeUpdatedBy(Package replacementPackage) {
+ return replacementPackage != null &&
+ replacementPackage.getClass() == this.getClass() &&
+ replacementPackage.getRevision() > this.getRevision();
+ }
+
//---
/**
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index 0724e3026..b4b8cf37e 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -111,4 +111,28 @@ public class PlatformPackage extends Package {
// TODO find similar existing platform in platforms folder
return folder;
}
+
+ /**
+ * Computes whether the given platform package is a suitable update for the current package.
+ * The base method checks the class type.
+ * The platform package also tests that the version and API level are the same and
+ * the revision number is greater
+ *
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package has the same revision as the current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return True if the replacement package is a suitable update for this one.
+ */
+ @Override
+ public boolean canBeUpdatedBy(Package replacementPackage) {
+ if (!super.canBeUpdatedBy(replacementPackage)) {
+ return false;
+ }
+
+ PlatformPackage newPkg = (PlatformPackage) replacementPackage;
+ return newPkg.getVersion().equalsIgnoreCase(this.getVersion()) &&
+ newPkg.getApiLevel() == this.getApiLevel() &&
+ newPkg.getRevision() > this.getRevision();
+ }
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
index 2e126e946..42462d90b 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
@@ -28,10 +28,16 @@ public class RepoSources {
public RepoSources() {
}
+ /**
+ * Adds a new source to the Sources list.
+ */
public void add(RepoSource source) {
mSources.add(source);
}
+ /**
+ * Returns the sources list array. This is never null.
+ */
public ArrayList getSources() {
return mSources;
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index 1b23e8ae3..b355b933c 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -88,4 +88,25 @@ public class ToolPackage extends Package {
public File getInstallFolder(String osSdkRoot) {
return new File(osSdkRoot, SdkConstants.FD_TOOLS);
}
+
+ /**
+ * Computes whether the given tools package is a suitable update for the current package.
+ * The base method checks the class type.
+ * The tools package also tests that the revision number is greater.
+ *
+ * An update is just that: a new package that supersedes the current one. If the new
+ * package has the same revision as the current one, it's not an update.
+ *
+ * @param replacementPackage The potential replacement package.
+ * @return True if the replacement package is a suitable update for this one.
+ */
+ @Override
+ public boolean canBeUpdatedBy(Package replacementPackage) {
+ if (!super.canBeUpdatedBy(replacementPackage)) {
+ return false;
+ }
+
+ ToolPackage newPkg = (ToolPackage) replacementPackage;
+ return newPkg.getRevision() > this.getRevision();
+ }
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
index 1f0b23f81..c8257b71c 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
@@ -81,7 +81,6 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
super(parent, SWT.BORDER);
mUpdaterData = updaterData;
- mUpdaterData.addListeners(this);
createContents(this);
postCreate(); //$hide$
@@ -121,39 +120,39 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
mContainerButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
mUpdateButton = new Button(mContainerButtons, SWT.NONE);
+ mUpdateButton.setText("Update All...");
mUpdateButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onUpdateInstalledPackage(); //$hide$ (hide from SWT designer)
}
});
- mUpdateButton.setText("Update...");
mPlaceholder1 = new Label(mContainerButtons, SWT.NONE);
mPlaceholder1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
mDeleteButton = new Button(mContainerButtons, SWT.NONE);
+ mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
+ mDeleteButton.setText("Delete...");
mDeleteButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onDeleteSelected(); //$hide$ (hide from SWT designer)
}
});
- mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
- mDeleteButton.setText("Delete...");
mPlaceholder2 = new Label(mContainerButtons, SWT.NONE);
mPlaceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
mRefreshButton = new Button(mContainerButtons, SWT.NONE);
+ mRefreshButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mRefreshButton.setText("Refresh");
mRefreshButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onRefreshSelected(); //$hide$ (hide from SWT designer)
}
});
- mRefreshButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
- mRefreshButton.setText("Refresh");
}
private void createSdkLocation(Composite parent) {
@@ -199,6 +198,7 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
* Called by the constructor right after {@link #createContents(Composite)}.
*/
private void postCreate() {
+ mUpdaterData.addListeners(this);
adjustColumnsWidth();
}
@@ -210,13 +210,15 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
*/
private void adjustColumnsWidth() {
// Add a listener to resize the column to the full width of the table
- mTablePackages.addControlListener(new ControlAdapter() {
+ ControlAdapter resizer = new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = mTablePackages.getClientArea();
mColumnPackages.setWidth(r.width);
}
- });
+ };
+ mTablePackages.addControlListener(resizer);
+ resizer.controlResized(null);
}
/**
@@ -239,9 +241,7 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
}
private void onUpdateInstalledPackage() {
- if (mUpdaterData != null) {
- mUpdaterData.reloadSdk();
- }
+ mUpdaterData.updateAll();
}
private void onDeleteSelected() {
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
index e497462e0..81498e052 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
@@ -86,7 +86,6 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
super(parent, SWT.BORDER);
mUpdaterData = updaterData;
- mUpdaterData.addListeners(this);
createContents(this);
postCreate(); //$hide$
@@ -188,6 +187,7 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
* Called by the constructor right after {@link #createContents(Composite)}.
*/
private void postCreate() {
+ mUpdaterData.addListeners(this);
adjustColumnsWidth();
}
@@ -199,13 +199,16 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
*/
private void adjustColumnsWidth() {
// Add a listener to resize the column to the full width of the table
- mTreeSources.addControlListener(new ControlAdapter() {
+ ControlAdapter resizer = new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = mTreeSources.getClientArea();
mColumnSource.setWidth(r.width);
}
- });
+ };
+
+ mTreeSources.addControlListener(resizer);
+ resizer.controlResized(null);
}
/**
@@ -303,7 +306,7 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
private void onRefreshSelected() {
if (mUpdaterData != null) {
- mUpdaterData.refreshSources(false /*forceFetching*/, null /*monitor*/);
+ mUpdaterData.refreshSources(false /*forceFetching*/);
}
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
new file mode 100755
index 000000000..fd7449d7b
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2009 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 org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+/**
+ * Implements an {@link UpdateChooserDialog}.
+ */
+final class UpdateChooserDialog extends Dialog {
+
+ /** Last dialog size for this session. */
+ private static Point sLastSize;
+ private boolean mCompleted;
+ private final Map mNewToOldArchiveMap;
+ private boolean mLicenseAcceptAll;
+ private boolean mInternalLicenseRadioUpdate;
+ private ArrayList mResult = new ArrayList();
+
+ // UI fields
+ private Shell mDialogShell;
+ private SashForm mSashForm;
+ private Composite mPackageRootComposite;
+ private Button mCancelButton;
+ private Button mInstallButton;
+ private CheckboxTableViewer mTableViewPackage;
+ private Table mTablePackage;
+ private TableColumn mTableColum;
+ private Text mPackageText;
+ private Button mLicenseRadioAccept;
+ private Button mLicenseRadioRefuse;
+ private Button mLicenseRadioAcceptAll;
+ private Group mPackageTextGroup;
+
+
+ /**
+ * Create the dialog.
+ * @param parent Parent container
+ * @param newToOldUpdates The map [new archive => old archive] of potential updates
+ */
+ public UpdateChooserDialog(Shell parent, Map newToOldUpdates) {
+ super(parent, SWT.APPLICATION_MODAL);
+
+ mNewToOldArchiveMap = new TreeMap(new Comparator() {
+ public int compare(Archive a1, Archive a2) {
+ // The items are archive but what we show are packages so we'll
+ // sort of packages short descriptions
+ String desc1 = a1.getParentPackage().getShortDescription();
+ String desc2 = a2.getParentPackage().getShortDescription();
+ return desc1.compareTo(desc2);
+ }
+ });
+ mNewToOldArchiveMap.putAll(newToOldUpdates);
+ }
+
+ /**
+ * Returns the results, i.e. the list of selected new archives to install.
+ * The list is always non null. It is empty when cancel is selected or when
+ * all potential updates have been refused.
+ */
+ public Collection getResult() {
+ return mResult;
+ }
+
+ /**
+ * Open the dialog and blocks till it gets closed
+ */
+ public void open() {
+ createContents();
+ positionShell(); //$hide$ (hide from SWT designer)
+ mDialogShell.open();
+ mDialogShell.layout();
+
+ postCreate(); //$hide$ (hide from SWT designer)
+
+ Display display = getParent().getDisplay();
+ while (!mDialogShell.isDisposed() && !mCompleted) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+ if (!mDialogShell.isDisposed()) {
+ mDialogShell.close();
+ }
+ }
+
+ /**
+ * Create contents of the dialog.
+ */
+ private void createContents() {
+ mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX);
+ mDialogShell.addShellListener(new ShellAdapter() {
+ @Override
+ public void shellClosed(ShellEvent e) {
+ onShellClosed(e);
+ }
+ });
+ mDialogShell.setLayout(new GridLayout(3, false/*makeColumnsEqual*/));
+ mDialogShell.setSize(600, 400);
+ mDialogShell.setText(getText());
+
+ // Sash form
+ mSashForm = new SashForm(mDialogShell, SWT.NONE);
+ mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
+
+
+ // Left part of Sash Form
+
+ mTableViewPackage = CheckboxTableViewer.newCheckList(mSashForm,
+ SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE);
+ mTablePackage = mTableViewPackage.getTable();
+ mTablePackage.setHeaderVisible(false);
+
+ mTablePackage.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onPackageSelected(); //$hide$
+ }
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ onPackageDoubleClick();
+ }
+ });
+
+ mTableViewPackage.addCheckStateListener(new ICheckStateListener() {
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ onPackageChecked(event);
+ }
+ });
+
+ mTableColum = new TableColumn(mTablePackage, SWT.NONE);
+ mTableColum.setWidth(100);
+ mTableColum.setText("Packages");
+
+
+ // Right part of Sash form
+ mPackageRootComposite = new Composite(mSashForm, SWT.NONE);
+ mPackageRootComposite.setLayout(new GridLayout(4, false/*makeColumnsEqual*/));
+
+ mPackageTextGroup = new Group(mPackageRootComposite, SWT.NONE);
+ mPackageTextGroup.setText("Package Description && License");
+ mPackageTextGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
+ mPackageTextGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
+
+ mPackageText = new Text(mPackageTextGroup,
+ SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
+ mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ mLicenseRadioAccept = new Button(mPackageRootComposite, SWT.RADIO);
+ mLicenseRadioAccept.setText("Accept");
+ mLicenseRadioAccept.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
+ mLicenseRadioAccept.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onLicenseRadioSelected();
+ }
+ });
+
+ mLicenseRadioRefuse = new Button(mPackageRootComposite, SWT.RADIO);
+ mLicenseRadioRefuse.setText("Refuse");
+ mLicenseRadioRefuse.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
+ mLicenseRadioRefuse.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onLicenseRadioSelected();
+ }
+ });
+
+ Label placeholder = new Label(mPackageRootComposite, SWT.NONE);
+ placeholder.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
+
+ mLicenseRadioAcceptAll = new Button(mPackageRootComposite, SWT.RADIO);
+ mLicenseRadioAcceptAll.setText("Accept All");
+ mLicenseRadioAcceptAll.setLayoutData(
+ new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
+ mLicenseRadioAcceptAll.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onLicenseRadioSelected();
+ }
+ });
+
+ mSashForm.setWeights(new int[] {200, 300});
+
+ // Bottom buttons
+ placeholder = new Label(mDialogShell, SWT.NONE);
+ placeholder.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1));
+
+ mInstallButton = new Button(mDialogShell, SWT.PUSH);
+ mInstallButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mInstallButton.setText("Install Selected");
+ mInstallButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onInstallSelected();
+ }
+ });
+
+ mCancelButton = new Button(mDialogShell, SWT.PUSH);
+ mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mCancelButton.setText("Cancel");
+ mCancelButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onCancelSelected();
+ }
+ });
+ }
+
+ // -- End of UI, Start of internal logic ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ /**
+ * Starts the thread that runs the task.
+ * This is deferred till the UI is created.
+ */
+ private void postCreate() {
+ // Fill the list with the replacement packages
+ mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());
+ mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
+ mTableViewPackage.setInput(mNewToOldArchiveMap);
+
+ adjustColumnsWidth();
+
+ // select first item
+ mTablePackage.select(0);
+ onPackageSelected();
+ }
+
+ /**
+ * Adds a listener to adjust the columns width when the parent is resized.
+ *
+ * If we need something more fancy, we might want to use this:
+ * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
+ */
+ private void adjustColumnsWidth() {
+ // Add a listener to resize the column to the full width of the table
+ ControlAdapter resizer = new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = mTablePackage.getClientArea();
+ mTableColum.setWidth(r.width);
+ }
+ };
+ mTablePackage.addControlListener(resizer);
+ resizer.controlResized(null);
+ }
+
+ /**
+ * Callback invoked when the shell is closed either by clicking the close button
+ * on by calling shell.close().
+ * Captures the window size before closing this.
+ */
+ private void onShellClosed(ShellEvent e) {
+ sLastSize = mDialogShell.getSize();
+ }
+
+ /**
+ * Centers the dialog in its parent shell.
+ */
+ private void positionShell() {
+ // Centers the dialog in its parent shell
+ Shell child = mDialogShell;
+ Shell parent = getParent();
+ if (child != null && parent != null) {
+
+ // get the parent client area with a location relative to the display
+ Rectangle parentArea = parent.getClientArea();
+ Point parentLoc = parent.getLocation();
+ int px = parentLoc.x;
+ int py = parentLoc.y;
+ int pw = parentArea.width;
+ int ph = parentArea.height;
+
+ // Reuse the last size if there's one, otherwise use the default
+ Point childSize = sLastSize != null ? sLastSize : child.getSize();
+ int cw = childSize.x;
+ int ch = childSize.y;
+
+ child.setLocation(px + (pw - cw) / 2, py + (ph - ch) / 2);
+ child.setSize(cw, ch);
+ }
+ }
+
+ /**
+ * Callback invoked when the Install button is selected. Fills {@link #mResult} and
+ * completes the dialog.
+ */
+ private void onInstallSelected() {
+
+ // get list of checked items
+ Object[] checked = mTableViewPackage.getCheckedElements();
+
+ if (checked != null) {
+ for (Object obj : checked) {
+ mResult.add((Archive) obj);
+ }
+ }
+
+ mCompleted = true;
+ }
+
+ /**
+ * Callback invoked when the Cancel button is selected.
+ */
+ private void onCancelSelected() {
+ mCompleted = true;
+ }
+
+ /**
+ * Callback invoked when a package item is selected in the list.
+ */
+ private void onPackageSelected() {
+ Archive a = getSelectedArchive();
+ displayInformation(a);
+ updateLicenceRadios(a);
+ }
+
+ /** Returns the currently selected Archive or null. */
+ private Archive getSelectedArchive() {
+ ISelection sel = mTableViewPackage.getSelection();
+ if (sel instanceof IStructuredSelection) {
+ Object elem = ((IStructuredSelection) sel).getFirstElement();
+ if (elem instanceof Archive) {
+ return (Archive) elem;
+ }
+ }
+ return null;
+ }
+
+ private void setSelectedAchive(Archive a) {
+ if (a == null) {
+ mTablePackage.deselectAll();
+ } else {
+ IStructuredSelection sel = new StructuredSelection(a);
+ mTableViewPackage.setSelection(sel, true /*reveal*/);
+ }
+ }
+
+ private void displayInformation(Archive a) {
+ if (a == null) {
+ mPackageText.setText("Please select a package.");
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ Archive aold = mNewToOldArchiveMap.get(a);
+ if (aold != null) {
+ sb.append("*** Existing Package Description:\n");
+ sb.append(aold.getParentPackage().getLongDescription()).append("\n\n");
+ }
+
+ sb.append("*** New Package Description:\n");
+ sb.append(a.getParentPackage().getLongDescription()).append("\n\n");
+
+ sb.append("\n*** Archive Description:\n");
+ sb.append(a.getLongDescription()).append("\n");
+
+ sb.append("\n*** Package License:\n");
+ sb.append(a.getParentPackage().getLicense()).append("\n");
+
+ mPackageText.setText(sb.toString());
+ }
+
+ private void updateLicenceRadios(Archive a) {
+ if (mInternalLicenseRadioUpdate) {
+ return;
+ }
+ mInternalLicenseRadioUpdate = true;
+
+ if (mLicenseAcceptAll) {
+ mLicenseRadioAcceptAll.setSelection(true);
+ mLicenseRadioAccept.setSelection(false);
+ mLicenseRadioRefuse.setSelection(false);
+ } else {
+ boolean accept = mTableViewPackage.getChecked(a);
+
+ mLicenseRadioAcceptAll.setSelection(false);
+ mLicenseRadioAccept.setSelection(accept);
+ mLicenseRadioRefuse.setSelection(!accept);
+ }
+
+ mInternalLicenseRadioUpdate = false;
+ }
+
+ /**
+ * Callback invoked when one of the radio license buttons is selected.
+ *
+ * - accept/refuse: toggle, update item checkbox
+ * - accept all: set accept-all, check all items
+ */
+ private void onLicenseRadioSelected() {
+ if (mInternalLicenseRadioUpdate) {
+ return;
+ }
+ mInternalLicenseRadioUpdate = true;
+
+ Archive a = getSelectedArchive();
+
+ if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
+ // Accept all has been switched on. Mark all packages as accepted
+ mLicenseAcceptAll = true;
+ mTableViewPackage.setAllChecked(true);
+
+ } else if (mLicenseRadioAccept.getSelection()) {
+ // Accept only this one
+ mLicenseAcceptAll = false;
+ mTableViewPackage.setChecked(a, true);
+
+ } else if (mLicenseRadioRefuse.getSelection()) {
+ // Refuse only this one
+ mLicenseAcceptAll = false;
+ mTableViewPackage.setChecked(a, false);
+ }
+
+ mInternalLicenseRadioUpdate = false;
+ }
+
+ /**
+ * Callback invoked when a package item is double-clicked in the list.
+ */
+ private void onPackageDoubleClick() {
+ Archive a = getSelectedArchive();
+
+ mTableViewPackage.setChecked(a, !mTableViewPackage.getChecked(a));
+ }
+
+ private void onPackageChecked(CheckStateChangedEvent event) {
+ Object e = event.getElement();
+ if (e instanceof Archive) {
+ Archive a = (Archive) e;
+
+ // select it
+ mLicenseAcceptAll = false;
+ setSelectedAchive(a);
+ updateLicenceRadios(a);
+ }
+ }
+
+ private class NewArchivesLabelProvider extends LabelProvider {
+ @Override
+ public Image getImage(Object element) {
+ return super.getImage(element);
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Archive) {
+ return ((Archive) element).getParentPackage().getShortDescription();
+ }
+ return super.getText(element);
+ }
+ }
+
+ private class NewArchivesContentProvider implements IStructuredContentProvider {
+
+ public void dispose() {
+ // pass
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // Ignore. The input is always mNewArchives
+ }
+
+ public Object[] getElements(Object inputElement) {
+ return mNewToOldArchiveMap.keySet().toArray();
+ }
+ }
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index f08d59d7c..be769b2ff 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -25,14 +25,18 @@ import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskFactory;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.LocalSdkParser;
+import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
/**
* Data shared between {@link UpdaterWindowImpl} and its pages.
@@ -59,7 +63,8 @@ class UpdaterData {
private final ArrayList mListeners = new ArrayList();
- private Display mDisplay;
+ /** @deprecated */private Display mDisplay; // TODO remove
+ private Shell mWindowShell;
public interface ISdkListener {
void onSdkChange();
@@ -72,6 +77,8 @@ class UpdaterData {
initSdk();
}
+ // ----- getters, setters ----
+
public void setOsSdkRoot(String osSdkRoot) {
if (mOsSdkRoot == null || mOsSdkRoot.equals(osSdkRoot) == false) {
mOsSdkRoot = osSdkRoot;
@@ -153,6 +160,35 @@ class UpdaterData {
mListeners.remove(listener);
}
+ public void setWindowShell(Shell windowShell) {
+ mWindowShell = windowShell;
+ }
+
+ public Shell getWindowShell() {
+ return mWindowShell;
+ }
+
+ // -----
+
+ /**
+ * Initializes the {@link SdkManager} and the {@link AvdManager}.
+ */
+ private void initSdk() {
+ mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
+ try {
+ mAvdManager = null; // remove the old one if needed.
+ mAvdManager = new AvdManager(mSdkManager, mSdkLog);
+ } catch (AndroidLocationException e) {
+ mSdkLog.error(e, "Unable to read AVDs");
+ }
+
+ // notify adapters/parsers
+ // TODO
+
+ // notify listeners.
+ notifyListeners();
+ }
+
/**
* Reloads the SDK content (targets).
* This also reloads the AVDs in case their status changed.
@@ -288,18 +324,17 @@ class UpdaterData {
public void updateAll() {
assert mTaskFactory != null;
- mTaskFactory.start("Update Archives", new ITask() {
- public void run(ITaskMonitor monitor) {
- monitor.setProgressMax(3);
+ refreshSources(true);
- monitor.setDescription("Refresh sources");
- refreshSources(true, monitor.createSubMonitor(1));
+ final Map updates = findUpdates();
- // TODO compare available vs local
- // TODO suggest update packages to user (also validate license click-through)
- // TODO install selected packages
- }
- });
+ UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), updates);
+ dialog.open();
+
+ Collection result = dialog.getResult();
+ if (result != null && result.size() > 0) {
+ installArchives(result);
+ }
}
/**
@@ -307,15 +342,15 @@ class UpdaterData {
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is
* null and a new task should be created.)
*
- * @param forceFetching When true, load sources that haven't been loaded yet. When
- * false, only refresh sources that have been loaded yet.
+ * @param forceFetching When true, load sources that haven't been loaded yet.
+ * When false, only refresh sources that have been loaded yet.
*/
- public void refreshSources(final boolean forceFetching, ITaskMonitor monitor) {
+ public void refreshSources(final boolean forceFetching) {
assert mTaskFactory != null;
final boolean forceHttp = getSettingsController().getForceHttp();
- ITask task = new ITask() {
+ mTaskFactory.start("Refresh Sources",new ITask() {
public void run(ITaskMonitor monitor) {
ArrayList sources = mSources.getSources();
monitor.setProgressMax(sources.size());
@@ -324,34 +359,87 @@ class UpdaterData {
source.load(monitor.createSubMonitor(1), forceHttp);
}
monitor.incProgress(1);
-
}
}
- };
-
- if (monitor != null) {
- task.run(monitor);
- } else {
- mTaskFactory.start("Refresh Sources", task);
- }
+ });
}
/**
- * Initializes the {@link SdkManager} and the {@link AvdManager}.
+ * Check the local archives vs the remote available packages to find potential updates.
+ * Return a map [remote archive => local archive] of suitable update candidates.
+ * Returns null if there's an unexpected error. Otherwise returns a map that can be
+ * empty but not null.
*/
- private void initSdk() {
- mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
- try {
- mAvdManager = null; // remove the old one if needed.
- mAvdManager = new AvdManager(mSdkManager, mSdkLog);
- } catch (AndroidLocationException e) {
- mSdkLog.error(e, "Unable to read AVDs");
+ private Map findUpdates() {
+ // Map [remote archive => local archive] of suitable update candidates
+ Map result = new HashMap();
+
+ // First go thru all sources and make a local list of all available archives
+ // sorted by package class.
+ HashMap, ArrayList> availPkgs =
+ new HashMap, ArrayList>();
+
+ ArrayList remoteSources = getSources().getSources();
+
+ for (RepoSource remoteSrc : remoteSources) {
+ Package[] remotePkgs = remoteSrc.getPackages();
+ if (remotePkgs != null) {
+ for (Package remotePkg : remotePkgs) {
+ Class extends Package> clazz = remotePkg.getClass();
+
+ ArrayList list = availPkgs.get(clazz);
+ if (list == null) {
+ availPkgs.put(clazz, list = new ArrayList());
+ }
+
+ for (Archive a : remotePkg.getArchives()) {
+ // Only add compatible archives
+ if (a.isCompatible()) {
+ list.add(a);
+ }
+ }
+ }
+ }
}
- // notify adapters/parsers
- // TODO
+ Package[] localPkgs = getLocalSdkParser().getPackages();
+ if (localPkgs == null) {
+ // This is unexpected. The local sdk parser should have been called first.
+ return null;
+ }
- // notify listeners.
- notifyListeners();
+ for (Package localPkg : localPkgs) {
+ // get the available archive list for this package type
+ ArrayList list = availPkgs.get(localPkg.getClass());
+
+ // if this list is empty, we'll never find anything that matches
+ if (list == null || list.size() == 0) {
+ continue;
+ }
+
+ // local packages should have one archive at most
+ Archive[] localArchives = localPkg.getArchives();
+ if (localArchives != null && localArchives.length > 0) {
+ Archive localArchive = localArchives[0];
+ // only consider archive compatible with the current platform
+ if (localArchive != null && localArchive.isCompatible()) {
+
+ // We checked all this archive stuff because that's what eventually gets
+ // installed, but the "update" mechanism really works on packages. So now
+ // the real question: is there a remote package that can update this
+ // local package?
+
+ for (Archive availArchive : list) {
+ if (localPkg.canBeUpdatedBy(availArchive.getParentPackage())) {
+ // Found one!
+ result.put(availArchive, localArchive);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return result;
}
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
index d9eac5cea..48a155fc7 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
@@ -131,6 +131,7 @@ public class UpdaterWindowImpl {
mAvdManagerPage = new AvdManagerPage(mPagesRootComposite, mUpdaterData);
mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);
mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, mUpdaterData);
+
mSashForm.setWeights(new int[] {150, 576});
}
@@ -200,6 +201,7 @@ public class UpdaterWindowImpl {
* This creates the pages, selects the first one, setup sources and scan for local folders.
*/
private void firstInit() {
+ mUpdaterData.setWindowShell(getShell());
mTaskFactory = new ProgressTaskFactory(getShell());
mUpdaterData.setTaskFactory(mTaskFactory);
mUpdaterData.setImageFactory(new ImageFactory(getShell().getDisplay()));