--- /dev/null
+/*
+ * 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.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.PlatformPackage;
+import com.android.sdklib.internal.repository.SdkSource;
+import com.android.sdklib.internal.repository.Package.UpdateInfo;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+import com.android.sdkuilib.repository.ISdkChangeListener;
+
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ITableColorProvider;
+import org.eclipse.jface.viewers.ITableFontProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreeColumnViewerLabelProvider;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Image;
+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.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Page that displays both locally installed packages as well as all known
+ * remote available packages. This gives an overview of what is installed
+ * vs what is available and allows the user to update or install packages.
+ */
+public class PackagesPage extends Composite
+ implements ISdkChangeListener, IPageListener {
+
+ private final List<PkgItem> mPackages = new ArrayList<PkgItem>();
+ private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>();
+ private final UpdaterData mUpdaterData;
+
+ private Text mTextSdkOsPath;
+
+ private Button mCheckSortSource;
+ private Button mCheckSortApi;
+ private Button mCheckFilterObsolete;
+ private Button mCheckFilterInstalled;
+ private Button mCheckFilterNew;
+ private Composite mGroupOptions;
+ private Composite mGroupSdk;
+ private Group mGroupPackages;
+ private Composite mGroupButtons;
+ private Button mButtonDelete;
+ private Button mButtonInstall;
+ private Tree mTree;
+ private CheckboxTreeViewer mTreeViewer;
+ private TreeViewerColumn mColumnName;
+ private TreeViewerColumn mColumnApi;
+ private TreeViewerColumn mColumnRevision;
+ private TreeViewerColumn mColumnStatus;
+ private Color mColorUpdate;
+ private Color mColorNew;
+ private Font mTreeFontItalic;
+
+ public PackagesPage(Composite parent, UpdaterData updaterData) {
+ super(parent, SWT.BORDER);
+
+ mUpdaterData = updaterData;
+ createContents(this);
+ postCreate(); //$hide$
+ }
+
+ public void onPageSelected() {
+ if (mPackages.isEmpty()) {
+ // Initialize the package list the first time the page is shown.
+ loadPackages();
+ }
+ }
+
+ protected void createContents(Composite parent) {
+ parent.setLayout(new GridLayout(1, false));
+
+ mGroupSdk = new Composite(parent, SWT.NONE);
+ mGroupSdk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mGroupSdk.setLayout(new GridLayout(2, false));
+
+ Label label1 = new Label(mGroupSdk, SWT.NONE);
+ label1.setText("SDK Path:");
+
+ mTextSdkOsPath = new Text(mGroupSdk, SWT.NONE);
+ mTextSdkOsPath.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mTextSdkOsPath.setEnabled(false);
+
+ mGroupPackages = new Group(parent, SWT.NONE);
+ mGroupPackages.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+ mGroupPackages.setText("Packages");
+ mGroupPackages.setLayout(new GridLayout(1, false));
+
+ mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER);
+ mTree = mTreeViewer.getTree();
+ mTree.setHeaderVisible(true);
+ mTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn1 = mColumnName.getColumn();
+ treeColumn1.setImage(getImage("platform_pkg_16.png")); //$NON-NLS-1$
+ treeColumn1.setWidth(290);
+ treeColumn1.setText("Name");
+
+ mColumnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn2 = mColumnApi.getColumn();
+ treeColumn2.setWidth(50);
+ treeColumn2.setText("API");
+
+ mColumnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn3 = mColumnRevision.getColumn();
+ treeColumn3.setWidth(50);
+ treeColumn3.setText("Rev.");
+ treeColumn3.setToolTipText("Revision currently installed");
+
+
+ mColumnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE);
+ TreeColumn treeColumn4 = mColumnStatus.getColumn();
+ treeColumn4.setWidth(88);
+ treeColumn4.setText("Status");
+
+ mGroupOptions = new Composite(mGroupPackages, SWT.NONE);
+ mGroupOptions.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mGroupOptions.setLayout(new GridLayout(8, false));
+
+ Label label2 = new Label(mGroupOptions, SWT.NONE);
+ label2.setText("Sort by");
+
+ mCheckSortSource = new Button(mGroupOptions, SWT.RADIO);
+ mCheckSortSource.setToolTipText("Sort by Source");
+ mCheckSortSource.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ sortPackages();
+ }
+ });
+ mCheckSortSource.setImage(getImage("source_icon16.png")); //$NON-NLS-1$
+ mCheckSortSource.setText("Source");
+
+ mCheckSortApi = new Button(mGroupOptions, SWT.RADIO);
+ mCheckSortApi.setToolTipText("Sort by API level");
+ mCheckSortApi.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ sortPackages();
+ }
+ });
+ mCheckSortApi.setImage(getImage("platform_pkg_16.png")); //$NON-NLS-1$
+ mCheckSortApi.setText("API level");
+ mCheckSortApi.setSelection(true);
+
+ Label expandPlaceholder = new Label(mGroupOptions, SWT.NONE);
+ expandPlaceholder.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ Label label3 = new Label(mGroupOptions, SWT.NONE);
+ label3.setText("Show:");
+
+ mCheckFilterNew = new Button(mGroupOptions, SWT.CHECK);
+ mCheckFilterNew.setToolTipText("Show Updates and New");
+ mCheckFilterNew.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ sortPackages();
+ }
+ });
+ mCheckFilterNew.setImage(getImage("reject_icon16.png"));
+ mCheckFilterNew.setSelection(true);
+ mCheckFilterNew.setText("Updates/New");
+
+ mCheckFilterInstalled = new Button(mGroupOptions, SWT.CHECK);
+ mCheckFilterInstalled.setToolTipText("Show Installed");
+ mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ sortPackages();
+ }
+ });
+ mCheckFilterInstalled.setImage(getImage("accept_icon16.png")); //$NON-NLS-1$
+ mCheckFilterInstalled.setSelection(true);
+ mCheckFilterInstalled.setText("Installed");
+
+ mCheckFilterObsolete = new Button(mGroupOptions, SWT.CHECK);
+ mCheckFilterObsolete.setToolTipText("Show Obsolete");
+ mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ sortPackages();
+ }
+ });
+ mCheckFilterObsolete.setImage(getImage("nopkg_icon16.png")); //$NON-NLS-1$
+ mCheckFilterObsolete.setSelection(false);
+ mCheckFilterObsolete.setText("Obsolete");
+
+ mGroupButtons = new Composite(parent, SWT.NONE);
+ mGroupButtons.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
+ false, 1, 1));
+ mGroupButtons.setLayout(new GridLayout(3, false));
+
+ mButtonDelete = new Button(mGroupButtons, SWT.NONE);
+ mButtonDelete.setText("Delete");
+
+ Label label4 = new Label(mGroupButtons, SWT.NONE);
+ label4.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ mButtonInstall = new Button(mGroupButtons, SWT.NONE);
+ mButtonInstall.setText("Install");
+ }
+
+ private Image getImage(String filename) {
+ if (mUpdaterData != null) {
+ ImageFactory imgFact = mUpdaterData.getImageFactory();
+ if (imgFact != null) {
+ imgFact.getImageByName(filename);
+ }
+ }
+ return null;
+ }
+
+
+ // -- Start of internal part ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ private void postCreate() {
+ if (mUpdaterData != null) {
+ mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
+ }
+
+ mTreeViewer.setContentProvider(new PkgContentProvider());
+ //--mTreeViewer.setLabelProvider(new PkgLabelProvider());
+
+
+ mColumnApi.setLabelProvider(new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnApi)));
+ mColumnName.setLabelProvider(new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnName)));
+ mColumnStatus.setLabelProvider(new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnStatus)));
+ mColumnRevision.setLabelProvider(new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnRevision)));
+
+ FontData fontData = mTree.getFont().getFontData()[0];
+ fontData.setStyle(SWT.ITALIC);
+ mTreeFontItalic = new Font(mTree.getDisplay(), fontData);
+
+ mColorUpdate = new Color(mTree.getDisplay(), 0xff, 0xff, 0xcc);
+ mColorNew = new Color(mTree.getDisplay(), 0xff, 0xee, 0xcc);
+
+ mTree.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ mTreeFontItalic.dispose();
+ mColorUpdate.dispose();
+ mColorNew.dispose();
+ mTreeFontItalic = null;
+ mColorUpdate = null;
+ mColorNew = null;
+ }
+ });
+ }
+
+ private 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 Source", new ITask() {
+ public void run(ITaskMonitor monitor) {
+ for (SdkSource source : mUpdaterData.getSources().getAllSources()) {
+ 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_AVAILABLE);
+ mPackages.add(pi);
+ }
+ }
+ }
+ }
+ });
+
+ sortPackages();
+ }
+
+ private void sortPackages() {
+ if (mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection()) {
+ sortByAPI();
+ } else {
+ sortBySource();
+ }
+ }
+
+ private void sortByAPI() {
+ mCategories.clear();
+
+ Set<Integer> apiSet = new HashSet<Integer>();
+ for (PkgItem item : mPackages) {
+ if (keepItem(item)) {
+ apiSet.add(item.getApi());
+ }
+ }
+
+ Integer[] apis = apiSet.toArray(new Integer[apiSet.size()]);
+ Arrays.sort(apis, new Comparator<Integer>() {
+ public int compare(Integer o1, Integer o2) {
+ return o2.compareTo(o1);
+ }
+ });
+
+ for (Integer api : apis) {
+ String name = api == -1 ? "Other" : "API " + api.toString();
+ Object iconRef = null;
+
+ List<PkgItem> items = new ArrayList<PkgItem>();
+ for (PkgItem item : mPackages) {
+ if (item.getApi() == api) {
+ items.add(item);
+
+ if (api != -1) {
+ Package p = item.getPackage();
+ if (p instanceof PlatformPackage) {
+ String vn = ((PlatformPackage) p).getVersionName();
+ name = String.format("%1$s (Android %2$s)", name, vn);
+ iconRef = p;
+ }
+ }
+ }
+ }
+
+ PkgCategory cat = new PkgCategory(
+ name,
+ iconRef,
+ items.toArray(new PkgItem[items.size()]));
+ mCategories.add(cat);
+ }
+
+ mTreeViewer.setInput(mCategories);
+
+ // expand all items by default
+ expandItem(mCategories);
+ }
+
+ private void sortBySource() {
+ mCategories.clear();
+
+ Set<SdkSource> sourceSet = new HashSet<SdkSource>();
+ for (PkgItem item : mPackages) {
+ if (keepItem(item)) {
+ sourceSet.add(item.getSource());
+ }
+ }
+
+ SdkSource[] sources = sourceSet.toArray(new SdkSource[sourceSet.size()]);
+ Arrays.sort(sources, new Comparator<SdkSource>() {
+ public int compare(SdkSource o1, SdkSource o2) {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null && o2 != null) {
+ return -1;
+ } else if (o1 != null && o2 == null) {
+ return 1;
+ }
+ assert o1 != null;
+ return o1.toString().compareTo(o2.toString());
+ }
+ });
+
+ for (SdkSource source : sources) {
+ Object key = source != null ? source : "Installed Packages";
+
+ List<PkgItem> items = new ArrayList<PkgItem>();
+ for (PkgItem item : mPackages) {
+ if (item.getSource() == source) {
+ items.add(item);
+ }
+ }
+
+ PkgCategory cat = new PkgCategory(
+ key,
+ key,
+ items.toArray(new PkgItem[items.size()]));
+ mCategories.add(cat);
+ }
+
+ mTreeViewer.setInput(mCategories);
+
+ // expand all items by default
+ expandItem(mCategories);
+ }
+
+ private boolean keepItem(PkgItem item) {
+ if (!mCheckFilterObsolete.getSelection()) {
+ if (item.isObsolete()) {
+ return false;
+ }
+ }
+
+ if (!mCheckFilterInstalled.getSelection()) {
+ if (item.getState() == PkgState.INSTALLED) {
+ return false;
+ }
+ }
+
+ if (!mCheckFilterNew.getSelection()) {
+ if (item.getState() == PkgState.NEW_AVAILABLE ||
+ item.getState() == PkgState.UPDATE_AVAILABLE) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void expandItem(Object elem) {
+ //if (elem instanceof SdkSource || elem instanceof SdkSourceCategory) {
+ mTreeViewer.setExpandedState(elem, true);
+ for (Object pkg :
+ ((ITreeContentProvider) mTreeViewer.getContentProvider()).getChildren(elem)) {
+ mTreeViewer.setChecked(pkg, true);
+ expandItem(pkg);
+ }
+ //}
+ }
+
+ // ----------------------
+
+ public class PkgCellLabelProvider extends ColumnLabelProvider
+ implements ITableFontProvider, ITableColorProvider {
+
+ private final TreeViewerColumn mColumn;
+
+ public PkgCellLabelProvider(TreeViewerColumn column) {
+ super();
+ mColumn = column;
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof PkgCategory) {
+ if (mColumn == mColumnName) {
+ return ((PkgCategory) element).getLabel();
+ }
+ } else if (element instanceof PkgItem) {
+ PkgItem pkg = (PkgItem) element;
+
+ if (mColumn == mColumnName) {
+ return pkg.getName();
+
+ } else if (mColumn == mColumnApi) {
+ int api = pkg.getApi();
+ if (api < 1) {
+ return ""; //$NON-NLS-1$
+ } else {
+ return Integer.toString(api);
+ }
+ } else if (mColumn == mColumnRevision) {
+ if (pkg.getState() == PkgState.INSTALLED ||
+ pkg.getState() == PkgState.UPDATE_AVAILABLE) {
+ return Integer.toString(pkg.getRevision());
+ }
+
+ } else if (mColumn == mColumnStatus) {
+ switch(pkg.getState()) {
+ case INSTALLED:
+ return "Installed";
+ case UPDATE_AVAILABLE:
+ return "Rev. " + Integer.toString(pkg.getUpdateRev());
+ case NEW_AVAILABLE:
+ return "New rev. " + Integer.toString(pkg.getRevision());
+ case LOCKED_NO_INSTALL:
+ return "Locked";
+ }
+ return pkg.getState().toString();
+ }
+ } else if (element instanceof Package) {
+ return ((Package) element).getShortDescription();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+
+ if (imgFactory != null) {
+ if (mColumn == mColumnName) {
+ if (element instanceof PkgCategory) {
+ return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
+ }
+ return imgFactory.getImageForObject(element);
+
+ } else if (mColumn == mColumnStatus && element instanceof PkgItem) {
+ switch(((PkgItem) element).getState()) {
+ case INSTALLED:
+ return imgFactory.getImageByName("accept_icon16.png");
+ case UPDATE_AVAILABLE:
+ case NEW_AVAILABLE:
+ return imgFactory.getImageByName("reject_icon16.png");
+ case LOCKED_NO_INSTALL:
+ return imgFactory.getImageByName("broken_pkg_16.png");
+ }
+ }
+ }
+ return super.getImage(element);
+ }
+
+ // -- ITableFontProvider
+
+ public Font getFont(Object element, int columnIndex) {
+ if (element instanceof PkgItem) {
+ if (((PkgItem) element).getState() != PkgState.INSTALLED) {
+ return mTreeFontItalic;
+ }
+ }
+ return super.getFont(element);
+ }
+
+ // -- ITableColorProvider
+
+ public Color getBackground(Object element, int columnIndex) {
+ if (element instanceof PkgItem) {
+ if (((PkgItem) element).getState() == PkgState.NEW_AVAILABLE) {
+ return mColorNew;
+ } else if (((PkgItem) element).getState() == PkgState.UPDATE_AVAILABLE) {
+ return mColorUpdate;
+ }
+ }
+ return null;
+ }
+
+ public Color getForeground(Object element, int columnIndex) {
+ // Not used
+ return null;
+ }
+ }
+
+ public class PkgLabelProvider extends LabelProvider {
+
+ @Override
+ public String getText(Object element) {
+ return super.getText(element);
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+
+ if (imgFactory != null) {
+ return imgFactory.getImageForObject(element);
+ }
+ return super.getImage(element);
+ }
+
+ }
+
+ public static class PkgContentProvider implements ITreeContentProvider {
+
+ public Object[] getChildren(Object parentElement) {
+
+ if (parentElement instanceof ArrayList<?>) {
+ return ((ArrayList<?>) parentElement).toArray();
+
+ } else if (parentElement instanceof PkgCategory) {
+ return ((PkgCategory) parentElement).getItems();
+
+ } else if (parentElement instanceof PkgItem) {
+ List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (pkgs != null) {
+ return pkgs.toArray();
+ }
+ }
+
+ return new Object[0];
+ }
+
+ public Object getParent(Object element) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean hasChildren(Object parentElement) {
+ if (parentElement instanceof ArrayList<?>) {
+ return true;
+
+ } else if (parentElement instanceof PkgCategory) {
+ return true;
+
+ } else if (parentElement instanceof PkgItem) {
+ List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs();
+ if (pkgs != null) {
+ return !pkgs.isEmpty();
+ }
+ }
+
+ return false;
+ }
+
+ public Object[] getElements(Object inputElement) {
+ return getChildren(inputElement);
+ }
+
+ public void dispose() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
+ // TODO Auto-generated method stub
+
+ }
+
+ }
+
+ public static class PkgCategory {
+ private final Object mKey;
+ private final PkgItem[] mItems;
+ private final Object mIconRef;
+
+ public PkgCategory(Object key, Object iconRef, PkgItem[] items) {
+ mKey = key;
+ mIconRef = iconRef;
+ mItems = items;
+ }
+
+ public String getLabel() {
+ return mKey.toString();
+ }
+
+ public Object getIconRef() {
+ return mIconRef;
+ }
+
+ public PkgItem[] getItems() {
+ return mItems;
+ }
+ }
+
+ public enum PkgState {
+ INSTALLED, UPDATE_AVAILABLE, NEW_AVAILABLE, LOCKED_NO_INSTALL
+ }
+
+ public static class PkgItem {
+ private final Package mPkg;
+ private PkgState mState;
+ private int mUpdateRev;
+ private List<Package> mUpdatePkgs;
+
+ public PkgItem(Package pkg, PkgState state) {
+ mPkg = pkg;
+ mState = state;
+ }
+
+ 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.UPDATE_AVAILABLE;
+ 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_AVAILABLE) {
+ return mPkg.getParentSource();
+ } else {
+ return null;
+ }
+ }
+
+ public int getApi() {
+ return mPkg instanceof IPackageVersion ?
+ ((IPackageVersion) mPkg).getVersion().getApiLevel() :
+ -1;
+ }
+
+ public int getUpdateRev() {
+ return mUpdateRev;
+ }
+
+ public List<Package> getUpdatePkgs() {
+ return mUpdatePkgs;
+ }
+ }
+
+
+
+ // --- Implementation of ISdkChangeListener ---
+
+ public void onSdkLoaded() {
+ onSdkReload();
+ }
+
+ public void onSdkReload() {
+ loadPackages();
+ }
+
+ public void preInstallHook() {
+ // nothing to be done for now.
+ }
+
+ public void postInstallHook() {
+ // nothing to be done for now.
+ }
+
+ // --- End of hiding from SWT Designer ---
+ //$hide<<$
+
+}
-/*\r
- * Copyright (C) 2009 The Android Open Source Project\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.android.sdkuilib.internal.repository;\r
-\r
-\r
-import com.android.sdklib.internal.repository.Archive;\r
-import com.android.sdklib.internal.repository.IDescription;\r
-import com.android.sdklib.internal.repository.Package;\r
-import com.android.sdklib.internal.repository.SdkAddonSource;\r
-import com.android.sdklib.internal.repository.SdkSource;\r
-import com.android.sdklib.internal.repository.SdkSourceCategory;\r
-import com.android.sdkuilib.repository.ISdkChangeListener;\r
-\r
-import org.eclipse.jface.dialogs.IInputValidator;\r
-import org.eclipse.jface.dialogs.InputDialog;\r
-import org.eclipse.jface.dialogs.MessageDialog;\r
-import org.eclipse.jface.viewers.CheckStateChangedEvent;\r
-import org.eclipse.jface.viewers.CheckboxTreeViewer;\r
-import org.eclipse.jface.viewers.ICheckStateListener;\r
-import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.jface.viewers.ITreeContentProvider;\r
-import org.eclipse.jface.viewers.ITreeSelection;\r
-import org.eclipse.jface.window.Window;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.events.ControlAdapter;\r
-import org.eclipse.swt.events.ControlEvent;\r
-import org.eclipse.swt.events.SelectionAdapter;\r
-import org.eclipse.swt.events.SelectionEvent;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.layout.GridData;\r
-import org.eclipse.swt.layout.GridLayout;\r
-import org.eclipse.swt.widgets.Button;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Group;\r
-import org.eclipse.swt.widgets.Label;\r
-import org.eclipse.swt.widgets.Tree;\r
-import org.eclipse.swt.widgets.TreeColumn;\r
-\r
-import java.util.ArrayList;\r
-\r
-\r
-public class RemotePackagesPage extends Composite implements ISdkChangeListener {\r
-\r
- private final UpdaterData mUpdaterData;\r
-\r
- private CheckboxTreeViewer mTreeViewerSources;\r
- private Tree mTreeSources;\r
- private TreeColumn mColumnSource;\r
- private Button mUpdateOnlyCheckBox;\r
- private Group mDescriptionContainer;\r
- private Button mAddSiteButton;\r
- private Button mDeleteSiteButton;\r
- private Button mRefreshButton;\r
- private Button mInstallSelectedButton;\r
- private Label mDescriptionLabel;\r
- private Label mSdkLocLabel;\r
-\r
-\r
-\r
- /**\r
- * Create the composite.\r
- * @param parent The parent of the composite.\r
- * @param updaterData An instance of {@link UpdaterData}.\r
- */\r
- RemotePackagesPage(Composite parent, UpdaterData updaterData) {\r
- super(parent, SWT.BORDER);\r
-\r
- mUpdaterData = updaterData;\r
-\r
- createContents(this);\r
- postCreate(); //$hide$\r
- }\r
-\r
- private void createContents(Composite parent) {\r
- parent.setLayout(new GridLayout(5, false));\r
-\r
- mSdkLocLabel = new Label(parent, SWT.NONE);\r
- mSdkLocLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 5, 1));\r
- mSdkLocLabel.setText("SDK Location: " +\r
- (mUpdaterData.getOsSdkRoot() != null ? mUpdaterData.getOsSdkRoot()\r
- : "<unknown>"));\r
-\r
- mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);\r
- mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {\r
- public void checkStateChanged(CheckStateChangedEvent event) {\r
- onTreeCheckStateChanged(event); //$hide$\r
- }\r
- });\r
- mTreeSources = mTreeViewerSources.getTree();\r
- mTreeSources.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent e) {\r
- onTreeSelected(); //$hide$\r
- }\r
- });\r
- mTreeSources.setHeaderVisible(true);\r
- mTreeSources.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));\r
-\r
- mColumnSource = new TreeColumn(mTreeSources, SWT.NONE);\r
- mColumnSource.setWidth(289);\r
- mColumnSource.setText("Packages available for download");\r
-\r
- mDescriptionContainer = new Group(parent, SWT.NONE);\r
- mDescriptionContainer.setLayout(new GridLayout(1, false));\r
- mDescriptionContainer.setText("Description");\r
- mDescriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 5, 1));\r
-\r
- mDescriptionLabel = new Label(mDescriptionContainer, SWT.NONE);\r
- mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));\r
- mDescriptionLabel.setText("Line1\nLine2\nLine3"); //$NON-NLS-1$\r
-\r
- mAddSiteButton = new Button(parent, SWT.NONE);\r
- mAddSiteButton.setText("Add Add-on Site...");\r
- mAddSiteButton.setToolTipText("Allows you to enter a new add-on site. " +\r
- "Such site can only contribute add-ons and extra packages.");\r
- mAddSiteButton.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent e) {\r
- onAddSiteSelected(); //$hide$\r
- }\r
- });\r
-\r
- mDeleteSiteButton = new Button(parent, SWT.NONE);\r
- mDeleteSiteButton.setText("Delete Add-on Site...");\r
- mDeleteSiteButton.setToolTipText("Allows you to remove an add-on site. " +\r
- "Built-in default sites cannot be removed.");\r
- mDeleteSiteButton.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent e) {\r
- onRemoveSiteSelected(); //$hide$\r
- }\r
- });\r
-\r
- mUpdateOnlyCheckBox = new Button(parent, SWT.CHECK);\r
- mUpdateOnlyCheckBox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));\r
- mUpdateOnlyCheckBox.setText("Display updates only");\r
- mUpdateOnlyCheckBox.setToolTipText("When selected, only compatible non-obsolete update packages are shown in the list above.");\r
- mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());\r
- mUpdateOnlyCheckBox.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent arg0) {\r
- onShowUpdateOnly(); //$hide$\r
- }\r
- });\r
-\r
- mRefreshButton = new Button(parent, SWT.NONE);\r
- mRefreshButton.setText("Refresh");\r
- mRefreshButton.setToolTipText("Refreshes the list of packages from open sites.");\r
- mRefreshButton.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent e) {\r
- onRefreshSelected(); //$hide$\r
- }\r
- });\r
-\r
- mInstallSelectedButton = new Button(parent, SWT.NONE);\r
- mInstallSelectedButton.setText("Install Selected");\r
- mInstallSelectedButton.setToolTipText("Allows you to review all selected packages " +\r
- "and install them.");\r
- mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {\r
- @Override\r
- public void widgetSelected(SelectionEvent e) {\r
- onInstallSelectedArchives(); //$hide$\r
- }\r
- });\r
- }\r
-\r
- @Override\r
- public void dispose() {\r
- mUpdaterData.removeListener(this);\r
- super.dispose();\r
- }\r
-\r
- @Override\r
- protected void checkSubclass() {\r
- // Disable the check that prevents subclassing of SWT components\r
- }\r
-\r
- // -- Start of internal part ----------\r
- // Hide everything down-below from SWT designer\r
- //$hide>>$\r
-\r
- /**\r
- * Called by the constructor right after {@link #createContents(Composite)}.\r
- */\r
- private void postCreate() {\r
- mUpdaterData.addListeners(this);\r
- adjustColumnsWidth();\r
- updateButtonsState();\r
- }\r
-\r
- /**\r
- * Adds a listener to adjust the columns width when the parent is resized.\r
- * <p/>\r
- * If we need something more fancy, we might want to use this:\r
- * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co\r
- */\r
- private void adjustColumnsWidth() {\r
- // Add a listener to resize the column to the full width of the table\r
- ControlAdapter resizer = new ControlAdapter() {\r
- @Override\r
- public void controlResized(ControlEvent e) {\r
- Rectangle r = mTreeSources.getClientArea();\r
- mColumnSource.setWidth(r.width);\r
- }\r
- };\r
-\r
- mTreeSources.addControlListener(resizer);\r
- resizer.controlResized(null);\r
- }\r
-\r
- /**\r
- * Called when an item in the package table viewer is selected.\r
- * If the items is an {@link IDescription} (as it should), this will display its long\r
- * description in the description area. Otherwise when the item is not of the expected\r
- * type or there is no selection, it empties the description area.\r
- */\r
- private void onTreeSelected() {\r
- updateButtonsState();\r
-\r
- ISelection sel = mTreeViewerSources.getSelection();\r
- if (sel instanceof ITreeSelection) {\r
- Object elem = ((ITreeSelection) sel).getFirstElement();\r
- if (elem instanceof IDescription) {\r
- mDescriptionLabel.setText(((IDescription) elem).getLongDescription());\r
- mDescriptionContainer.layout(true);\r
- return;\r
- }\r
- }\r
- mDescriptionLabel.setText(""); //$NON-NLS1-$\r
- }\r
-\r
- /**\r
- * Handle checking and unchecking of the tree items.\r
- *\r
- * When unchecking, all sub-tree items checkboxes are cleared too.\r
- * When checking a source, all of its packages are checked too.\r
- * When checking a package, only its compatible archives are checked.\r
- */\r
- private void onTreeCheckStateChanged(CheckStateChangedEvent event) {\r
- updateButtonsState();\r
-\r
- boolean b = event.getChecked();\r
- Object elem = event.getElement(); // Will be Archive or Package or RepoSource\r
-\r
- assert event.getSource() == mTreeViewerSources;\r
-\r
- // when deselecting, we just deselect all children too\r
- if (b == false) {\r
- mTreeViewerSources.setSubtreeChecked(elem, b);\r
- return;\r
- }\r
-\r
- ITreeContentProvider provider =\r
- (ITreeContentProvider) mTreeViewerSources.getContentProvider();\r
-\r
- // When selecting, we want to only select compatible archives\r
- // and expand the super nodes.\r
- expandItem(elem, provider);\r
- }\r
-\r
- private void expandItem(Object elem, ITreeContentProvider provider) {\r
- if (elem instanceof SdkSource || elem instanceof SdkSourceCategory) {\r
- mTreeViewerSources.setExpandedState(elem, true);\r
- for (Object pkg : provider.getChildren(elem)) {\r
- mTreeViewerSources.setChecked(pkg, true);\r
- expandItem(pkg, provider);\r
- }\r
- } else if (elem instanceof Package) {\r
- selectCompatibleArchives(elem, provider);\r
- }\r
- }\r
-\r
- private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {\r
- for (Object archive : provider.getChildren(pkg)) {\r
- if (archive instanceof Archive) {\r
- mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible());\r
- }\r
- }\r
- }\r
-\r
- private void onShowUpdateOnly() {\r
- SettingsController controller = mUpdaterData.getSettingsController();\r
- controller.setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection());\r
- controller.saveSettings();\r
-\r
- // Get the list of selected archives\r
- ArrayList<Archive> archives = new ArrayList<Archive>();\r
- for (Object element : mTreeViewerSources.getCheckedElements()) {\r
- if (element instanceof Archive) {\r
- archives.add((Archive) element);\r
- }\r
- // Deselect them all\r
- mTreeViewerSources.setChecked(element, false);\r
- }\r
-\r
- mTreeViewerSources.refresh();\r
-\r
- // Now reselect those that still exist in the tree but only if they\r
- // are compatible archives\r
- for (Archive a : archives) {\r
- if (a.isCompatible() && mTreeViewerSources.setChecked(a, true)) {\r
- // If we managed to select the archive, also select the parent package.\r
- // Technically we should only select the parent package if *all* the\r
- // compatible archives children are selected. In practice we'll rarely\r
- // have more than one compatible archive per package.\r
- mTreeViewerSources.setChecked(a.getParentPackage(), true);\r
- }\r
- }\r
-\r
- updateButtonsState();\r
- }\r
-\r
- private void onInstallSelectedArchives() {\r
- ArrayList<Archive> archives = new ArrayList<Archive>();\r
- for (Object element : mTreeViewerSources.getCheckedElements()) {\r
- if (element instanceof Archive) {\r
- archives.add((Archive) element);\r
- }\r
- }\r
-\r
- if (mUpdaterData != null) {\r
- mUpdaterData.updateOrInstallAll_WithGUI(\r
- archives,\r
- mUpdateOnlyCheckBox.getSelection() /* includeObsoletes */);\r
- }\r
- }\r
-\r
- private void onAddSiteSelected() {\r
-\r
- final SdkSource[] knowSources = mUpdaterData.getSources().getAllSources();\r
- String title = "Add Add-on Site URL";\r
-\r
- String msg =\r
- "This dialog lets you add the URL of a new add-on site.\n" +\r
- "\n" +\r
- "An add-on site can only provide new add-ons or \"user\" packages.\n" +\r
- "Add-on sites cannot provide standard Android platforms, docs or samples packages.\n" +\r
- "Inserting a URL here will not allow you to clone an official Android repository.\n" +\r
- "\n" +\r
- "Please enter the URL of the repository.xml for the new add-on site:";\r
-\r
- InputDialog dlg = new InputDialog(getShell(), title, msg, null, new IInputValidator() {\r
- public String isValid(String newText) {\r
-\r
- newText = newText == null ? null : newText.trim();\r
-\r
- if (newText == null || newText.length() == 0) {\r
- return "Error: URL field is empty. Please enter a URL.";\r
- }\r
-\r
- // A URL should have one of the following prefixes\r
- if (!newText.startsWith("file://") && //$NON-NLS-1$\r
- !newText.startsWith("ftp://") && //$NON-NLS-1$\r
- !newText.startsWith("http://") && //$NON-NLS-1$\r
- !newText.startsWith("https://")) { //$NON-NLS-1$\r
- return "Error: The URL must start by one of file://, ftp://, http:// or https://";\r
- }\r
-\r
- // Reject URLs that are already in the source list.\r
- // URLs are generally case-insensitive (except for file:// where it all depends\r
- // on the current OS so we'll ignore this case.)\r
- for (SdkSource s : knowSources) {\r
- if (newText.equalsIgnoreCase(s.getUrl())) {\r
- return "Error : This site is already listed.";\r
- }\r
- }\r
-\r
- return null;\r
- }\r
- });\r
-\r
- if (dlg.open() == Window.OK) {\r
- String url = dlg.getValue().trim();\r
- mUpdaterData.getSources().add(\r
- SdkSourceCategory.USER_ADDONS,\r
- new SdkAddonSource(url, null/*uiName*/));\r
- onRefreshSelected();\r
- }\r
- }\r
-\r
- private void onRemoveSiteSelected() {\r
- boolean changed = false;\r
-\r
- ISelection sel = mTreeViewerSources.getSelection();\r
- if (mUpdaterData != null && sel instanceof ITreeSelection) {\r
- for (Object c : ((ITreeSelection) sel).toList()) {\r
- if (c instanceof SdkSource) {\r
- SdkSource source = (SdkSource) c;\r
-\r
- if (mUpdaterData.getSources().hasSourceUrl(\r
- SdkSourceCategory.USER_ADDONS, source)) {\r
- String title = "Delete Add-on Site?";\r
-\r
- String msg = String.format("Are you sure you want to delete the add-on site '%1$s'?",\r
- source.getUrl());\r
-\r
- if (MessageDialog.openQuestion(getShell(), title, msg)) {\r
- mUpdaterData.getSources().remove(source);\r
- changed = true;\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- if (changed) {\r
- onRefreshSelected();\r
- }\r
- }\r
-\r
- private void onRefreshSelected() {\r
- if (mUpdaterData != null) {\r
- mUpdaterData.refreshSources(false /*forceFetching*/);\r
- }\r
- mTreeViewerSources.refresh();\r
- updateButtonsState();\r
- }\r
-\r
- private void updateButtonsState() {\r
- // We install archives, so there should be at least one checked archive.\r
- // Having sites or packages checked does not count.\r
- boolean hasCheckedArchive = false;\r
- Object[] checked = mTreeViewerSources.getCheckedElements();\r
- if (checked != null) {\r
- for (Object c : checked) {\r
- if (c instanceof Archive) {\r
- hasCheckedArchive = true;\r
- break;\r
- }\r
- }\r
- }\r
-\r
- // Is there a selected site Source?\r
- boolean hasSelectedUserSource = false;\r
- ISelection sel = mTreeViewerSources.getSelection();\r
- if (sel instanceof ITreeSelection) {\r
- for (Object c : ((ITreeSelection) sel).toList()) {\r
- if (c instanceof SdkSource &&\r
- mUpdaterData.getSources().hasSourceUrl(\r
- SdkSourceCategory.USER_ADDONS, (SdkSource) c)) {\r
- hasSelectedUserSource = true;\r
- break;\r
- }\r
- }\r
- }\r
-\r
- mAddSiteButton.setEnabled(true);\r
- mDeleteSiteButton.setEnabled(hasSelectedUserSource);\r
- mRefreshButton.setEnabled(true);\r
- mInstallSelectedButton.setEnabled(hasCheckedArchive);\r
-\r
- // set value on the show only update checkbox\r
- mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());\r
- }\r
-\r
- // --- Implementation of ISdkChangeListener ---\r
-\r
- public void onSdkLoaded() {\r
- onSdkReload();\r
- }\r
-\r
- public void onSdkReload() {\r
- RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter();\r
- mTreeViewerSources.setContentProvider(sources.getContentProvider());\r
- mTreeViewerSources.setLabelProvider( sources.getLabelProvider());\r
- mTreeViewerSources.setInput(sources);\r
- onTreeSelected();\r
- }\r
-\r
- public void preInstallHook() {\r
- // nothing to be done for now.\r
- }\r
-\r
- public void postInstallHook() {\r
- // nothing to be done for now.\r
- }\r
-\r
- // End of hiding from SWT Designer\r
- //$hide<<$\r
-}\r
+/*
+ * 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 com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.SdkAddonSource;
+import com.android.sdklib.internal.repository.SdkSource;
+import com.android.sdklib.internal.repository.SdkSourceCategory;
+import com.android.sdkuilib.repository.ISdkChangeListener;
+
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.ITreeSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+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.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.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+import java.util.ArrayList;
+
+/**
+ * Page that displays remote repository & add-ons sources and let the user
+ * select packages for installation.
+ */
+public class RemotePackagesPage extends Composite implements ISdkChangeListener {
+
+ private final UpdaterData mUpdaterData;
+
+ private CheckboxTreeViewer mTreeViewerSources;
+ private Tree mTreeSources;
+ private TreeColumn mColumnSource;
+ private Button mUpdateOnlyCheckBox;
+ private Group mDescriptionContainer;
+ private Button mAddSiteButton;
+ private Button mDeleteSiteButton;
+ private Button mRefreshButton;
+ private Button mInstallSelectedButton;
+ private Label mDescriptionLabel;
+ private Label mSdkLocLabel;
+
+
+
+ /**
+ * Create the composite.
+ * @param parent The parent of the composite.
+ * @param updaterData An instance of {@link UpdaterData}.
+ */
+ RemotePackagesPage(Composite parent, UpdaterData updaterData) {
+ super(parent, SWT.BORDER);
+
+ mUpdaterData = updaterData;
+
+ createContents(this);
+ postCreate(); //$hide$
+ }
+
+ private void createContents(Composite parent) {
+ parent.setLayout(new GridLayout(5, false));
+
+ mSdkLocLabel = new Label(parent, SWT.NONE);
+ mSdkLocLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 5, 1));
+ mSdkLocLabel.setText("SDK Location: " +
+ (mUpdaterData.getOsSdkRoot() != null ? mUpdaterData.getOsSdkRoot()
+ : "<unknown>"));
+
+ mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);
+ mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ onTreeCheckStateChanged(event); //$hide$
+ }
+ });
+ mTreeSources = mTreeViewerSources.getTree();
+ mTreeSources.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onTreeSelected(); //$hide$
+ }
+ });
+ mTreeSources.setHeaderVisible(true);
+ mTreeSources.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
+
+ mColumnSource = new TreeColumn(mTreeSources, SWT.NONE);
+ mColumnSource.setWidth(289);
+ mColumnSource.setText("Packages available for download");
+
+ mDescriptionContainer = new Group(parent, SWT.NONE);
+ mDescriptionContainer.setLayout(new GridLayout(1, false));
+ mDescriptionContainer.setText("Description");
+ mDescriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 5, 1));
+
+ mDescriptionLabel = new Label(mDescriptionContainer, SWT.NONE);
+ mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+ mDescriptionLabel.setText("Line1\nLine2\nLine3"); //$NON-NLS-1$
+
+ mAddSiteButton = new Button(parent, SWT.NONE);
+ mAddSiteButton.setText("Add Add-on Site...");
+ mAddSiteButton.setToolTipText("Allows you to enter a new add-on site. " +
+ "Such site can only contribute add-ons and extra packages.");
+ mAddSiteButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onAddSiteSelected(); //$hide$
+ }
+ });
+
+ mDeleteSiteButton = new Button(parent, SWT.NONE);
+ mDeleteSiteButton.setText("Delete Add-on Site...");
+ mDeleteSiteButton.setToolTipText("Allows you to remove an add-on site. " +
+ "Built-in default sites cannot be removed.");
+ mDeleteSiteButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRemoveSiteSelected(); //$hide$
+ }
+ });
+
+ mUpdateOnlyCheckBox = new Button(parent, SWT.CHECK);
+ mUpdateOnlyCheckBox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));
+ mUpdateOnlyCheckBox.setText("Display updates only");
+ mUpdateOnlyCheckBox.setToolTipText("When selected, only compatible non-obsolete update packages are shown in the list above.");
+ mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());
+ mUpdateOnlyCheckBox.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent arg0) {
+ onShowUpdateOnly(); //$hide$
+ }
+ });
+
+ mRefreshButton = new Button(parent, SWT.NONE);
+ mRefreshButton.setText("Refresh");
+ mRefreshButton.setToolTipText("Refreshes the list of packages from open sites.");
+ mRefreshButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRefreshSelected(); //$hide$
+ }
+ });
+
+ mInstallSelectedButton = new Button(parent, SWT.NONE);
+ mInstallSelectedButton.setText("Install Selected");
+ mInstallSelectedButton.setToolTipText("Allows you to review all selected packages " +
+ "and install them.");
+ mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onInstallSelectedArchives(); //$hide$
+ }
+ });
+ }
+
+ @Override
+ public void dispose() {
+ mUpdaterData.removeListener(this);
+ super.dispose();
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+
+ // -- Start of internal part ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ /**
+ * Called by the constructor right after {@link #createContents(Composite)}.
+ */
+ private void postCreate() {
+ mUpdaterData.addListeners(this);
+ adjustColumnsWidth();
+ updateButtonsState();
+ }
+
+ /**
+ * Adds a listener to adjust the columns width when the parent is resized.
+ * <p/>
+ * 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 = mTreeSources.getClientArea();
+ mColumnSource.setWidth(r.width);
+ }
+ };
+
+ mTreeSources.addControlListener(resizer);
+ resizer.controlResized(null);
+ }
+
+ /**
+ * Called when an item in the package table viewer is selected.
+ * If the items is an {@link IDescription} (as it should), this will display its long
+ * description in the description area. Otherwise when the item is not of the expected
+ * type or there is no selection, it empties the description area.
+ */
+ private void onTreeSelected() {
+ updateButtonsState();
+
+ ISelection sel = mTreeViewerSources.getSelection();
+ if (sel instanceof ITreeSelection) {
+ Object elem = ((ITreeSelection) sel).getFirstElement();
+ if (elem instanceof IDescription) {
+ mDescriptionLabel.setText(((IDescription) elem).getLongDescription());
+ mDescriptionContainer.layout(true);
+ return;
+ }
+ }
+ mDescriptionLabel.setText(""); //$NON-NLS1-$
+ }
+
+ /**
+ * Handle checking and unchecking of the tree items.
+ *
+ * When unchecking, all sub-tree items checkboxes are cleared too.
+ * When checking a source, all of its packages are checked too.
+ * When checking a package, only its compatible archives are checked.
+ */
+ private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
+ updateButtonsState();
+
+ boolean b = event.getChecked();
+ Object elem = event.getElement(); // Will be Archive or Package or RepoSource
+
+ assert event.getSource() == mTreeViewerSources;
+
+ // when deselecting, we just deselect all children too
+ if (b == false) {
+ mTreeViewerSources.setSubtreeChecked(elem, b);
+ return;
+ }
+
+ ITreeContentProvider provider =
+ (ITreeContentProvider) mTreeViewerSources.getContentProvider();
+
+ // When selecting, we want to only select compatible archives
+ // and expand the super nodes.
+ expandItem(elem, provider);
+ }
+
+ private void expandItem(Object elem, ITreeContentProvider provider) {
+ if (elem instanceof SdkSource || elem instanceof SdkSourceCategory) {
+ mTreeViewerSources.setExpandedState(elem, true);
+ for (Object pkg : provider.getChildren(elem)) {
+ mTreeViewerSources.setChecked(pkg, true);
+ expandItem(pkg, provider);
+ }
+ } else if (elem instanceof Package) {
+ selectCompatibleArchives(elem, provider);
+ }
+ }
+
+ private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
+ for (Object archive : provider.getChildren(pkg)) {
+ if (archive instanceof Archive) {
+ mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible());
+ }
+ }
+ }
+
+ private void onShowUpdateOnly() {
+ SettingsController controller = mUpdaterData.getSettingsController();
+ controller.setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection());
+ controller.saveSettings();
+
+ // Get the list of selected archives
+ ArrayList<Archive> archives = new ArrayList<Archive>();
+ for (Object element : mTreeViewerSources.getCheckedElements()) {
+ if (element instanceof Archive) {
+ archives.add((Archive) element);
+ }
+ // Deselect them all
+ mTreeViewerSources.setChecked(element, false);
+ }
+
+ mTreeViewerSources.refresh();
+
+ // Now reselect those that still exist in the tree but only if they
+ // are compatible archives
+ for (Archive a : archives) {
+ if (a.isCompatible() && mTreeViewerSources.setChecked(a, true)) {
+ // If we managed to select the archive, also select the parent package.
+ // Technically we should only select the parent package if *all* the
+ // compatible archives children are selected. In practice we'll rarely
+ // have more than one compatible archive per package.
+ mTreeViewerSources.setChecked(a.getParentPackage(), true);
+ }
+ }
+
+ updateButtonsState();
+ }
+
+ private void onInstallSelectedArchives() {
+ ArrayList<Archive> archives = new ArrayList<Archive>();
+ for (Object element : mTreeViewerSources.getCheckedElements()) {
+ if (element instanceof Archive) {
+ archives.add((Archive) element);
+ }
+ }
+
+ if (mUpdaterData != null) {
+ mUpdaterData.updateOrInstallAll_WithGUI(
+ archives,
+ mUpdateOnlyCheckBox.getSelection() /* includeObsoletes */);
+ }
+ }
+
+ private void onAddSiteSelected() {
+ final SdkSource[] knowSources = mUpdaterData.getSources().getAllSources();
+ String title = "Add Add-on Site URL";
+
+ String msg =
+ "This dialog lets you add the URL of a new add-on site.\n" +
+ "\n" +
+ "An add-on site can only provide new add-ons or \"user\" packages.\n" +
+ "Add-on sites cannot provide standard Android platforms, docs or samples packages.\n" +
+ "Inserting a URL here will not allow you to clone an official Android repository.\n" +
+ "\n" +
+ "Please enter the URL of the repository.xml for the new add-on site:";
+
+ InputDialog dlg = new InputDialog(getShell(), title, msg, null, new IInputValidator() {
+ public String isValid(String newText) {
+
+ newText = newText == null ? null : newText.trim();
+
+ if (newText == null || newText.length() == 0) {
+ return "Error: URL field is empty. Please enter a URL.";
+ }
+
+ // A URL should have one of the following prefixes
+ if (!newText.startsWith("file://") && //$NON-NLS-1$
+ !newText.startsWith("ftp://") && //$NON-NLS-1$
+ !newText.startsWith("http://") && //$NON-NLS-1$
+ !newText.startsWith("https://")) { //$NON-NLS-1$
+ return "Error: The URL must start by one of file://, ftp://, http:// or https://";
+ }
+
+ // Reject URLs that are already in the source list.
+ // URLs are generally case-insensitive (except for file:// where it all depends
+ // on the current OS so we'll ignore this case.)
+ for (SdkSource s : knowSources) {
+ if (newText.equalsIgnoreCase(s.getUrl())) {
+ return "Error : This site is already listed.";
+ }
+ }
+
+ return null;
+ }
+ });
+
+ if (dlg.open() == Window.OK) {
+ String url = dlg.getValue().trim();
+ mUpdaterData.getSources().add(
+ SdkSourceCategory.USER_ADDONS,
+ new SdkAddonSource(url, null/*uiName*/));
+ onRefreshSelected();
+ }
+ }
+
+ private void onRemoveSiteSelected() {
+ boolean changed = false;
+
+ ISelection sel = mTreeViewerSources.getSelection();
+ if (mUpdaterData != null && sel instanceof ITreeSelection) {
+ for (Object c : ((ITreeSelection) sel).toList()) {
+ if (c instanceof SdkSource) {
+ SdkSource source = (SdkSource) c;
+
+ if (mUpdaterData.getSources().hasSourceUrl(
+ SdkSourceCategory.USER_ADDONS, source)) {
+ String title = "Delete Add-on Site?";
+
+ String msg = String.format("Are you sure you want to delete the add-on site '%1$s'?",
+ source.getUrl());
+
+ if (MessageDialog.openQuestion(getShell(), title, msg)) {
+ mUpdaterData.getSources().remove(source);
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (changed) {
+ onRefreshSelected();
+ }
+ }
+
+ private void onRefreshSelected() {
+ if (mUpdaterData != null) {
+ mUpdaterData.refreshSources(false /*forceFetching*/);
+ }
+ mTreeViewerSources.refresh();
+ updateButtonsState();
+ }
+
+ private void updateButtonsState() {
+ // We install archives, so there should be at least one checked archive.
+ // Having sites or packages checked does not count.
+ boolean hasCheckedArchive = false;
+ Object[] checked = mTreeViewerSources.getCheckedElements();
+ if (checked != null) {
+ for (Object c : checked) {
+ if (c instanceof Archive) {
+ hasCheckedArchive = true;
+ break;
+ }
+ }
+ }
+
+ // Is there a selected site Source?
+ boolean hasSelectedUserSource = false;
+ ISelection sel = mTreeViewerSources.getSelection();
+ if (sel instanceof ITreeSelection) {
+ for (Object c : ((ITreeSelection) sel).toList()) {
+ if (c instanceof SdkSource &&
+ mUpdaterData.getSources().hasSourceUrl(
+ SdkSourceCategory.USER_ADDONS, (SdkSource) c)) {
+ hasSelectedUserSource = true;
+ break;
+ }
+ }
+ }
+
+ mAddSiteButton.setEnabled(true);
+ mDeleteSiteButton.setEnabled(hasSelectedUserSource);
+ mRefreshButton.setEnabled(true);
+ mInstallSelectedButton.setEnabled(hasCheckedArchive);
+
+ // set value on the show only update checkbox
+ mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());
+ }
+
+ // --- Implementation of ISdkChangeListener ---
+
+ public void onSdkLoaded() {
+ onSdkReload();
+ }
+
+ public void onSdkReload() {
+ RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter();
+ mTreeViewerSources.setContentProvider(sources.getContentProvider());
+ mTreeViewerSources.setLabelProvider( sources.getLabelProvider());
+ mTreeViewerSources.setInput(sources);
+ onTreeSelected();
+ }
+
+ public void preInstallHook() {
+ // nothing to be done for now.
+ }
+
+ public void postInstallHook() {
+ // nothing to be done for now.
+ }
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+}