From 5de234e8caedb6382880583020c5b84a55922cc4 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Mon, 15 Jun 2009 13:04:58 -0700 Subject: [PATCH 1/3] Add downloadprovider permission tests runtest shortcut. --- testrunner/test_defs.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml index d89b7a54f..49e4a654d 100644 --- a/testrunner/test_defs.xml +++ b/testrunner/test_defs.xml @@ -380,6 +380,11 @@ Native tests: package="com.android.providers.contactstests" coverage_target="ContactsProvider" /> + + Date: Mon, 15 Jun 2009 13:36:11 -0700 Subject: [PATCH 2/3] Modify archquery to be able to print any system property. (this is change 4207 that was abandoned due to a merge conflict.) --- .../src/com/android/archquery/Main.java | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/tools/archquery/src/com/android/archquery/Main.java b/tools/archquery/src/com/android/archquery/Main.java index 5e65ff289..c76cca122 100644 --- a/tools/archquery/src/com/android/archquery/Main.java +++ b/tools/archquery/src/com/android/archquery/Main.java @@ -45,21 +45,28 @@ package com.android.archquery; */ public final class Main { public static void main(String[] args) { - // Values listed from http://lopica.sourceforge.net/os.html - String arch = System.getProperty("os.arch"); - if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) { - System.out.print("x86_64"); + for (String arg : args) { + System.out.println(String.format("%1$s: %2$s", arg, System.getProperty(arg))); + } - } else if (arch.equalsIgnoreCase("x86") - || arch.equalsIgnoreCase("i386") - || arch.equalsIgnoreCase("i686")) { - System.out.print("x86"); + if (args.length == 0) { + // Values listed from http://lopica.sourceforge.net/os.html + String arch = System.getProperty("os.arch"); - } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) { - System.out.print("ppc"); - } else { - System.out.print(arch); + if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) { + System.out.print("x86_64"); + + } else if (arch.equalsIgnoreCase("x86") + || arch.equalsIgnoreCase("i386") + || arch.equalsIgnoreCase("i686")) { + System.out.print("x86"); + + } else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) { + System.out.print("ppc"); + } else { + System.out.print(arch); + } } } -} \ No newline at end of file +} From e073146af34a3ee25bc65f0ef2430a13d41afdfd Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 13 Jun 2009 22:42:31 -0700 Subject: [PATCH 3/3] SDK Updater: update all existing local archives, license click through. --- .../internal/repository/AddonPackage.java | 25 + .../internal/repository/DocPackage.java | 23 + .../sdklib/internal/repository/Package.java | 17 + .../internal/repository/PlatformPackage.java | 24 + .../internal/repository/RepoSources.java | 6 + .../internal/repository/ToolPackage.java | 21 + .../repository/LocalPackagesPage.java | 22 +- .../repository/RemotePackagesPage.java | 11 +- .../repository/UpdateChooserDialog.java | 529 ++++++++++++++++++ .../internal/repository/UpdaterData.java | 158 ++++-- .../repository/UpdaterWindowImpl.java | 2 + 11 files changed, 788 insertions(+), 50 deletions(-) create mode 100755 tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateChooserDialog.java diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index f204ba5a0..08180b701 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -190,6 +190,9 @@ public class AddonPackage extends Package { String name = String.format("%s-%d", getName(), getApiLevel()); // $NON-NLS-1$ + // FIXME this will fail if the name is not ASCII compatible. This could easily + // happen: a Chinese or Japanese name etc for example, + // to name a few. name = name.toLowerCase(); name = name.replaceAll("[^a-zA-Z0-9_-]+", "_"); // $NON-NLS-1$ name = name.replaceAll("_+", "_"); // $NON-NLS-1$ @@ -199,4 +202,26 @@ public class AddonPackage extends Package { // TODO find similar existing addon in addons folder return folder; } + + /** + * Computes whether the given addon package is a suitable update for the current package. + * The base method checks the class type. + * The addon package also tests that the name is 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; + } + + 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 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()));