[go: nahoru, domu]

blob: bb9bff9a8b014220291bff0934836870d624551d [file] [log] [blame]
/*
* 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.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.internal.repository.updater.ArchiveInfo;
import com.android.sdklib.internal.repository.updater.SdkUpdaterLogic;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.License;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
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.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.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
/**
* Implements an {@link SdkUpdaterChooserDialog}.
*/
final class SdkUpdaterChooserDialog extends GridDialog {
/** Last dialog size for this session. */
private static Point sLastSize;
/** Precomputed flag indicating whether the "accept license" radio is checked. */
private boolean mAcceptSameAllLicense;
private boolean mInternalLicenseRadioUpdate;
// UI fields
private SashForm mSashForm;
private Composite mPackageRootComposite;
private TreeViewer mTreeViewPackage;
private Tree mTreePackage;
private TreeColumn mTreeColum;
private StyledText mPackageText;
private Button mLicenseRadioAccept;
private Button mLicenseRadioReject;
private Button mLicenseRadioAcceptLicense;
private Group mPackageTextGroup;
private final SwtUpdaterData mSwtUpdaterData;
private Group mTableGroup;
private Label mErrorLabel;
/**
* List of all archives to be installed with dependency information.
* <p/>
* Note: in a lot of cases, we need to find the archive info for a given archive. This
* is currently done using a simple linear search, which is fine since we only have a very
* limited number of archives to deal with (e.g. < 10 now). We might want to revisit
* this later if it becomes an issue. Right now just do the simple thing.
* <p/>
* Typically we could add a map Archive=>ArchiveInfo later.
*/
private final Collection<ArchiveInfo> mArchives;
/**
* Create the dialog.
*
* @param parentShell The shell to use, typically updaterData.getWindowShell()
* @param swtUpdaterData The updater data
* @param archives The archives to be installed
*/
public SdkUpdaterChooserDialog(Shell parentShell,
SwtUpdaterData swtUpdaterData,
Collection<ArchiveInfo> archives) {
super(parentShell, 3, false/*makeColumnsEqual*/);
mSwtUpdaterData = swtUpdaterData;
mArchives = archives;
}
@Override
protected boolean isResizable() {
return true;
}
/**
* Returns the results, i.e. the list of selected new archives to install.
* This is similar to the {@link ArchiveInfo} list instance given to the constructor
* except only accepted archives are present.
* <p/>
* An empty list is returned if cancel was chosen.
*/
public ArrayList<ArchiveInfo> getResult() {
ArrayList<ArchiveInfo> ais = new ArrayList<ArchiveInfo>();
if (getReturnCode() == Window.OK) {
for (ArchiveInfo ai : mArchives) {
if (ai.isAccepted()) {
ais.add(ai);
}
}
}
return ais;
}
/**
* Create the main content of the dialog.
* See also {@link #createButtonBar(Composite)} below.
*/
@Override
public void createDialogContent(Composite parent) {
// Sash form
mSashForm = new SashForm(parent, SWT.NONE);
mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
// Left part of Sash Form
mTableGroup = new Group(mSashForm, SWT.NONE);
mTableGroup.setText("Packages");
mTableGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
mTreeViewPackage = new TreeViewer(mTableGroup, SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE);
mTreePackage = mTreeViewPackage.getTree();
mTreePackage.setHeaderVisible(false);
mTreePackage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
mTreePackage.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onPackageSelected(); //$hide$
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
onPackageDoubleClick();
}
});
mTreeColum = new TreeColumn(mTreePackage, SWT.NONE);
mTreeColum.setWidth(100);
mTreeColum.setText("Packages");
// Right part of Sash form
mPackageRootComposite = new Composite(mSashForm, SWT.NONE);
mPackageRootComposite.setLayout(new GridLayout(4, false/*makeColumnsEqual*/));
mPackageRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
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 StyledText(mPackageTextGroup,
SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
mPackageText.setBackground(
getParentShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
mLicenseRadioAccept = new Button(mPackageRootComposite, SWT.RADIO);
mLicenseRadioAccept.setText("Accept");
mLicenseRadioAccept.setToolTipText("Accept this package.");
mLicenseRadioAccept.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onLicenseRadioSelected();
}
});
mLicenseRadioReject = new Button(mPackageRootComposite, SWT.RADIO);
mLicenseRadioReject.setText("Reject");
mLicenseRadioReject.setToolTipText("Reject this package.");
mLicenseRadioReject.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onLicenseRadioSelected();
}
});
Link link = new Link(mPackageRootComposite, SWT.NONE);
link.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));
final String printAction = "Print"; // extracted for NLS, to compare with below.
link.setText(String.format("<a>Copy to clipboard</a> | <a>%1$s</a>", printAction));
link.setToolTipText("Copies all text and license to clipboard | Print using system defaults.");
link.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
if (printAction.equals(e.text)) {
mPackageText.print();
} else {
Point p = mPackageText.getSelection();
mPackageText.selectAll();
mPackageText.copy();
mPackageText.setSelection(p);
}
}
});
mLicenseRadioAcceptLicense = new Button(mPackageRootComposite, SWT.RADIO);
mLicenseRadioAcceptLicense.setText("Accept License");
mLicenseRadioAcceptLicense.setToolTipText("Accept all packages that use the same license.");
mLicenseRadioAcceptLicense.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onLicenseRadioSelected();
}
});
mSashForm.setWeights(new int[] {200, 300});
}
/**
* Creates and returns the contents of this dialog's button bar.
* <p/>
* This reimplements most of the code from the base class with a few exceptions:
* <ul>
* <li>Enforces 3 columns.
* <li>Inserts a full-width error label.
* <li>Inserts a help label on the left of the first button.
* <li>Renames the OK button into "Install"
* </ul>
*/
@Override
protected Control createButtonBar(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 0; // this is incremented by createButton
layout.makeColumnsEqualWidth = false;
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
composite.setLayout(layout);
GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1);
composite.setLayoutData(data);
composite.setFont(parent.getFont());
// Error message area
mErrorLabel = new Label(composite, SWT.NONE);
mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
// Label at the left of the install/cancel buttons
Label label = new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
label.setText("[*] Something depends on this package");
label.setEnabled(false);
layout.numColumns++;
// Add the ok/cancel to the button bar.
createButtonsForButtonBar(composite);
// the ok button should be an "install" button
Button button = getButton(IDialogConstants.OK_ID);
button.setText("Install");
return composite;
}
// -- End of UI, Start of internal logic ----------
// Hide everything down-below from SWT designer
//$hide>>$
@Override
public void create() {
super.create();
// set window title
getShell().setText("Choose Packages to Install");
setWindowImage();
// Automatically accept those with an empty license or no license
for (ArchiveInfo ai : mArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
License license = a.getParentPackage().getLicense();
boolean hasLicense = license != null &&
license.getLicense() != null &&
license.getLicense().length() > 0;
ai.setAccepted(!hasLicense);
}
}
// Fill the list with the replacement packages
mTreeViewPackage.setLabelProvider(new NewArchivesLabelProvider());
mTreeViewPackage.setContentProvider(new NewArchivesContentProvider());
mTreeViewPackage.setInput(createTreeInput(mArchives));
mTreeViewPackage.expandAll();
adjustColumnsWidth();
// select first item
onPackageSelected();
}
/**
* Creates the icon of the window shell.
*/
private void setWindowImage() {
String imageName = "android_icon_16.png"; //$NON-NLS-1$
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
imageName = "android_icon_128.png"; //$NON-NLS-1$
}
if (mSwtUpdaterData != null) {
ImageFactory imgFactory = mSwtUpdaterData.getImageFactory();
if (imgFactory != null) {
getShell().setImage(imgFactory.getImageByName(imageName));
}
}
}
/**
* 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 = mTreePackage.getClientArea();
mTreeColum.setWidth(r.width);
}
};
mTreePackage.addControlListener(resizer);
resizer.controlResized(null);
}
/**
* Captures the window size before closing this.
* @see #getInitialSize()
*/
@Override
public boolean close() {
sLastSize = getShell().getSize();
return super.close();
}
/**
* Tries to reuse the last window size during this session.
* <p/>
* Note: the alternative would be to implement {@link #getDialogBoundsSettings()}
* since the default {@link #getDialogBoundsStrategy()} is to persist both location
* and size.
*/
@Override
protected Point getInitialSize() {
if (sLastSize != null) {
return sLastSize;
} else {
// Arbitrary values that look good on my screen and fit on 800x600
return new Point(740, 470);
}
}
/**
* Callback invoked when a package item is selected in the list.
*/
private void onPackageSelected() {
Object item = getSelectedItem();
// Update mAcceptSameAllLicense : true if all items under the same license are accepted.
ArchiveInfo ai = null;
List<ArchiveInfo> list = null;
if (item instanceof ArchiveInfo) {
ai = (ArchiveInfo) item;
Object p =
((NewArchivesContentProvider) mTreeViewPackage.getContentProvider()).getParent(ai);
if (p instanceof LicenseEntry) {
list = ((LicenseEntry) p).getArchives();
}
displayPackageInformation(ai);
} else if (item instanceof LicenseEntry) {
LicenseEntry entry = (LicenseEntry) item;
list = entry.getArchives();
displayLicenseInformation(entry);
} else {
// Fallback, should not happen.
displayEmptyInformation();
}
// the "Accept License" radio is selected if there's a license with >= 0 items
// and they are all in "accepted" state.
mAcceptSameAllLicense = list != null && list.size() > 0;
if (mAcceptSameAllLicense) {
assert list != null;
License lic0 = getLicense(list.get(0));
for (ArchiveInfo ai2 : list) {
License lic2 = getLicense(ai2);
if (ai2.isAccepted() && (lic0 == lic2 || lic0.equals(lic2))) {
continue;
} else {
mAcceptSameAllLicense = false;
break;
}
}
}
displayMissingDependency(ai);
updateLicenceRadios(ai);
}
/** Returns the currently selected tree item.
* @return Either {@link ArchiveInfo} or {@link LicenseEntry} or null. */
private Object getSelectedItem() {
ISelection sel = mTreeViewPackage.getSelection();
if (sel instanceof IStructuredSelection) {
Object elem = ((IStructuredSelection) sel).getFirstElement();
if (elem instanceof ArchiveInfo || elem instanceof LicenseEntry) {
return elem;
}
}
return null;
}
/**
* Information displayed when nothing valid is selected.
*/
private void displayEmptyInformation() {
mPackageText.setText("Please select a package or a license.");
}
/**
* Updates the package description and license text depending on the selected package.
* <p/>
* Note that right now there is no logic to support more than one level of dependencies
* (e.g. A <- B <- C and A is disabled so C should be disabled; currently C's state depends
* solely on B's state). We currently don't need this. It would be straightforward to add
* if we had a need for it, though. This would require changes to {@link ArchiveInfo} and
* {@link SdkUpdaterLogic}.
*/
private void displayPackageInformation(ArchiveInfo ai) {
Archive aNew = ai == null ? null : ai.getNewArchive();
Package pNew = aNew == null ? null : aNew.getParentPackage();
if (pNew == null) {
displayEmptyInformation();
return;
}
assert ai != null; // make Eclipse null detector happy
assert aNew != null;
mPackageText.setText(""); //$NON-NLS-1$
addSectionTitle("Package Description\n");
addText(pNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
Archive aOld = ai.getReplaced();
if (aOld != null) {
Package pOld = aOld.getParentPackage();
FullRevision rOld = pOld.getRevision();
FullRevision rNew = pNew.getRevision();
boolean showRev = true;
if (pNew instanceof IAndroidVersionProvider &&
pOld instanceof IAndroidVersionProvider) {
AndroidVersion vOld = ((IAndroidVersionProvider) pOld).getAndroidVersion();
AndroidVersion vNew = ((IAndroidVersionProvider) pNew).getAndroidVersion();
if (!vOld.equals(vNew)) {
// Versions are different, so indicate more than just the revision.
addText(String.format("This update will replace API %1$s revision %2$s with API %3$s revision %4$s.\n\n",
vOld.getApiString(), rOld.toShortString(),
vNew.getApiString(), rNew.toShortString()));
showRev = false;
}
}
if (showRev) {
addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",
rOld.toShortString(),
rNew.toShortString()));
}
}
ArchiveInfo[] aDeps = ai.getDependsOn();
if ((aDeps != null && aDeps.length > 0) || ai.isDependencyFor()) {
addSectionTitle("Dependencies\n");
if (aDeps != null && aDeps.length > 0) {
addText("Installing this package also requires installing:");
for (ArchiveInfo aDep : aDeps) {
addText(String.format("\n- %1$s",
aDep.getShortDescription()));
}
addText("\n\n");
}
if (ai.isDependencyFor()) {
addText("This package is a dependency for:");
for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
addText(String.format("\n- %1$s",
ai2.getShortDescription()));
}
addText("\n\n");
}
}
addSectionTitle("Archive Description\n");
addText(aNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
License license = pNew.getLicense();
if (license != null) {
String text = license.getLicense();
if (text != null) {
addSectionTitle("License\n");
addText(text.trim(), "\n\n"); //$NON-NLS-1$
}
}
addSectionTitle("Site\n");
SdkSource source = pNew.getParentSource();
if (source != null) {
addText(source.getShortDescription());
}
}
/**
* Updates the description for a license entry.
*/
private void displayLicenseInformation(LicenseEntry entry) {
List<ArchiveInfo> archives = entry == null ? null : entry.getArchives();
if (archives == null) {
// There should not be a license entry without any package in it.
displayEmptyInformation();
return;
}
assert entry != null;
mPackageText.setText(""); //$NON-NLS-1$
License license = null;
addSectionTitle("Packages\n");
for (ArchiveInfo ai : entry.getArchives()) {
Archive aNew = ai.getNewArchive();
if (aNew != null) {
Package pNew = aNew.getParentPackage();
if (pNew != null) {
if (license == null) {
license = pNew.getLicense();
} else {
assert license.equals(pNew.getLicense()); // all items have the same license
}
addText("- ", pNew.getShortDescription(), "\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
if (license != null) {
String text = license.getLicense();
if (text != null) {
addSectionTitle("\nLicense\n");
addText(text.trim(), "\n\n"); //$NON-NLS-1$
}
}
}
/**
* Computes and displays missing dependencies.
*
* If there's a selected package, check the dependency for that one.
* Otherwise display the first missing dependency of any other package.
*/
private void displayMissingDependency(ArchiveInfo ai) {
String error = null;
try {
if (ai != null) {
if (ai.isAccepted()) {
// Case where this package is accepted but blocked by another non-accepted one
ArchiveInfo[] adeps = ai.getDependsOn();
if (adeps != null) {
for (ArchiveInfo adep : adeps) {
if (!adep.isAccepted()) {
error = String.format("This package depends on '%1$s'.",
adep.getShortDescription());
return;
}
}
}
} else {
// Case where this package blocks another one when not accepted
for (ArchiveInfo adep : ai.getDependenciesFor()) {
// It only matters if the blocked one is accepted
if (adep.isAccepted()) {
error = String.format("Package '%1$s' depends on this one.",
adep.getShortDescription());
return;
}
}
}
}
// If there is no missing dependency on the current selection,
// just find the first missing dependency of any other package.
for (ArchiveInfo ai2 : mArchives) {
if (ai2 == ai) {
// We already processed that one above.
continue;
}
if (ai2.isAccepted()) {
// The user requested to install this package.
// Check if all its dependencies are met.
ArchiveInfo[] adeps = ai2.getDependsOn();
if (adeps != null) {
for (ArchiveInfo adep : adeps) {
if (!adep.isAccepted()) {
error = String.format("Package '%1$s' depends on '%2$s'",
ai2.getShortDescription(),
adep.getShortDescription());
return;
}
}
}
} else {
// The user did not request to install this package.
// Check whether this package blocks another one when not accepted.
for (ArchiveInfo adep : ai2.getDependenciesFor()) {
// It only matters if the blocked one is accepted
// or if it's a local archive that is already installed (these
// are marked as implicitly accepted, so it's the same test.)
if (adep.isAccepted()) {
error = String.format("Package '%1$s' depends on '%2$s'",
adep.getShortDescription(),
ai2.getShortDescription());
return;
}
}
}
}
} finally {
mErrorLabel.setText(error == null ? "" : error); //$NON-NLS-1$
}
}
private void addText(String...string) {
for (String s : string) {
mPackageText.append(s);
}
}
private void addSectionTitle(String string) {
String s = mPackageText.getText();
int start = (s == null ? 0 : s.length());
mPackageText.append(string);
StyleRange sr = new StyleRange();
sr.start = start;
sr.length = string.length();
sr.fontStyle = SWT.BOLD;
sr.underline = true;
mPackageText.setStyleRange(sr);
}
private void updateLicenceRadios(ArchiveInfo ai) {
if (mInternalLicenseRadioUpdate) {
return;
}
mInternalLicenseRadioUpdate = true;
boolean oneAccepted = false;
mLicenseRadioAcceptLicense.setSelection(mAcceptSameAllLicense);
oneAccepted = ai != null && ai.isAccepted();
mLicenseRadioAccept.setEnabled(ai != null);
mLicenseRadioReject.setEnabled(ai != null);
mLicenseRadioAccept.setSelection(oneAccepted);
mLicenseRadioReject.setSelection(ai != null && ai.isRejected());
// The install button is enabled if there's at least one package accepted.
// If the current one isn't, look for another one.
boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;
if (!missing && !oneAccepted) {
for(ArchiveInfo ai2 : mArchives) {
if (ai2.isAccepted()) {
oneAccepted = true;
break;
}
}
}
getButton(IDialogConstants.OK_ID).setEnabled(!missing && oneAccepted);
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 with the *same* license
*/
private void onLicenseRadioSelected() {
if (mInternalLicenseRadioUpdate) {
return;
}
mInternalLicenseRadioUpdate = true;
Object item = getSelectedItem();
ArchiveInfo ai = (item instanceof ArchiveInfo) ? (ArchiveInfo) item : null;
boolean needUpdate = true;
if (!mAcceptSameAllLicense && mLicenseRadioAcceptLicense.getSelection()) {
// Accept all has been switched on. Mark all packages as accepted
List<ArchiveInfo> list = null;
if (item instanceof LicenseEntry) {
list = ((LicenseEntry) item).getArchives();
} else if (ai != null) {
Object p = ((NewArchivesContentProvider) mTreeViewPackage.getContentProvider())
.getParent(ai);
if (p instanceof LicenseEntry) {
list = ((LicenseEntry) p).getArchives();
}
}
if (list != null && list.size() > 0) {
mAcceptSameAllLicense = true;
for(ArchiveInfo ai2 : list) {
ai2.setAccepted(true);
ai2.setRejected(false);
}
}
} else if (ai != null && mLicenseRadioAccept.getSelection()) {
// Accept only this one
mAcceptSameAllLicense = false;
ai.setAccepted(true);
ai.setRejected(false);
} else if (ai != null && mLicenseRadioReject.getSelection()) {
// Reject only this one
mAcceptSameAllLicense = false;
ai.setAccepted(false);
ai.setRejected(true);
} else {
needUpdate = false;
}
mInternalLicenseRadioUpdate = false;
if (needUpdate) {
if (mAcceptSameAllLicense) {
mTreeViewPackage.refresh();
} else {
mTreeViewPackage.refresh(ai);
mTreeViewPackage.refresh(
((NewArchivesContentProvider) mTreeViewPackage.getContentProvider()).
getParent(ai));
}
displayMissingDependency(ai);
updateLicenceRadios(ai);
}
}
/**
* Callback invoked when a package item is double-clicked in the list.
*/
private void onPackageDoubleClick() {
Object item = getSelectedItem();
if (item instanceof ArchiveInfo) {
ArchiveInfo ai = (ArchiveInfo) item;
boolean wasAccepted = ai.isAccepted();
ai.setAccepted(!wasAccepted);
ai.setRejected(wasAccepted);
// update state
mAcceptSameAllLicense = false;
mTreeViewPackage.refresh(ai);
// refresh parent since its icon might have changed.
mTreeViewPackage.refresh(
((NewArchivesContentProvider) mTreeViewPackage.getContentProvider()).
getParent(ai));
displayMissingDependency(ai);
updateLicenceRadios(ai);
} else if (item instanceof LicenseEntry) {
mTreeViewPackage.setExpandedState(item, !mTreeViewPackage.getExpandedState(item));
}
}
/**
* Provides the labels for the tree view.
* Root branches are {@link LicenseEntry} elements.
* Leave nodes are {@link ArchiveInfo} which all have the same license.
*/
private class NewArchivesLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
if (element instanceof ArchiveInfo) {
// Archive icon: accepted (green), rejected (red), not set yet (question mark)
ArchiveInfo ai = (ArchiveInfo) element;
ImageFactory imgFactory = mSwtUpdaterData.getImageFactory();
if (imgFactory != null) {
if (ai.isAccepted()) {
return imgFactory.getImageByName("accept_icon16.png");
} else if (ai.isRejected()) {
return imgFactory.getImageByName("reject_icon16.png");
}
return imgFactory.getImageByName("unknown_icon16.png");
}
return super.getImage(element);
} else if (element instanceof LicenseEntry) {
// License icon: green if all below are accepted, red if all rejected, otherwise
// no icon.
ImageFactory imgFactory = mSwtUpdaterData.getImageFactory();
if (imgFactory != null) {
boolean allAccepted = true;
boolean allRejected = true;
for (ArchiveInfo ai : ((LicenseEntry) element).getArchives()) {
allAccepted = allAccepted && ai.isAccepted();
allRejected = allRejected && ai.isRejected();
}
if (allAccepted && !allRejected) {
return imgFactory.getImageByName("accept_icon16.png");
} else if (!allAccepted && allRejected) {
return imgFactory.getImageByName("reject_icon16.png");
}
}
}
return null;
}
@Override
public String getText(Object element) {
if (element instanceof LicenseEntry) {
return ((LicenseEntry) element).getLicenseRef();
} else if (element instanceof ArchiveInfo) {
ArchiveInfo ai = (ArchiveInfo) element;
String desc = ai.getShortDescription();
if (ai.isDependencyFor()) {
desc += " [*]";
}
return desc;
}
assert element instanceof String || element instanceof ArchiveInfo;
return null;
}
}
/**
* Provides the content for the tree view.
* Root branches are {@link LicenseEntry} elements.
* Leave nodes are {@link ArchiveInfo} which all have the same license.
*/
private class NewArchivesContentProvider implements ITreeContentProvider {
private List<LicenseEntry> mInput;
@Override
public void dispose() {
// pass
}
@SuppressWarnings("unchecked")
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// Input should be the result from createTreeInput.
if (newInput instanceof List<?> &&
((List<?>) newInput).size() > 0 &&
((List<?>) newInput).get(0) instanceof LicenseEntry) {
mInput = (List<LicenseEntry>) newInput;
} else {
mInput = null;
}
}
@Override
public boolean hasChildren(Object parent) {
if (parent instanceof List<?>) {
// This is the root of the tree.
return true;
} else if (parent instanceof LicenseEntry) {
return ((LicenseEntry) parent).getArchives().size() > 0;
}
return false;
}
@Override
public Object[] getElements(Object parent) {
return getChildren(parent);
}
@Override
public Object[] getChildren(Object parent) {
if (parent instanceof List<?>) {
return ((List<?>) parent).toArray();
} else if (parent instanceof LicenseEntry) {
return ((LicenseEntry) parent).getArchives().toArray();
}
return new Object[0];
}
@Override
public Object getParent(Object child) {
if (child instanceof LicenseEntry) {
return ((LicenseEntry) child).getRoot();
} else if (child instanceof ArchiveInfo && mInput != null) {
for (LicenseEntry entry : mInput) {
if (entry.getArchives().contains(child)) {
return entry;
}
}
}
return null;
}
}
/**
* Represents a branch in the view tree: an entry where all the sub-archive info
* share the same license. Contains a link to the share root list for convenience.
*/
private static class LicenseEntry {
private final List<LicenseEntry> mRoot;
private final String mLicenseRef;
private final List<ArchiveInfo> mArchives;
public LicenseEntry(
@NonNull List<LicenseEntry> root,
@NonNull String licenseRef,
@NonNull List<ArchiveInfo> archives) {
mRoot = root;
mLicenseRef = licenseRef;
mArchives = archives;
}
@NonNull
public List<LicenseEntry> getRoot() {
return mRoot;
}
@NonNull
public String getLicenseRef() {
return mLicenseRef;
}
@NonNull
public List<ArchiveInfo> getArchives() {
return mArchives;
}
}
/**
* Creates the tree structure based on the given archives.
* The current structure is to have a branch per license type,
* with all the archives sharing the same license under it.
* Elements with no license are left at the root.
*
* @param archives The non-null collection of archive info to display. Ideally non-empty.
* @return A list of {@link LicenseEntry}, each containing a list of {@link ArchiveInfo}.
*/
@NonNull
private List<LicenseEntry> createTreeInput(@NonNull Collection<ArchiveInfo> archives) {
// Build an ordered map with all the licenses, ordered by license ref name.
final String noLicense = "No license"; //NLS
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
boolean first1 = noLicense.equals(s1);
boolean first2 = noLicense.equals(s2);
if (first1 && first2) {
return 0;
} else if (first1) {
return -1;
} else if (first2) {
return 1;
}
return s1.compareTo(s2);
}
};
Map<String, List<ArchiveInfo>> map = new TreeMap<String, List<ArchiveInfo>>(comp);
for (ArchiveInfo info : archives) {
String ref = noLicense;
License license = getLicense(info);
if (license != null && license.getLicenseRef() != null) {
ref = prettyLicenseRef(license.getLicenseRef());
}
List<ArchiveInfo> list = map.get(ref);
if (list == null) {
map.put(ref, list = new ArrayList<ArchiveInfo>());
}
list.add(info);
}
// Transform result into a list
List<LicenseEntry> licensesList = new ArrayList<LicenseEntry>();
for (Map.Entry<String, List<ArchiveInfo>> entry : map.entrySet()) {
licensesList.add(new LicenseEntry(licensesList, entry.getKey(), entry.getValue()));
}
return licensesList;
}
/**
* Helper method to retrieve the {@link License} for a given {@link ArchiveInfo}.
*
* @param ai The archive info. Can be null.
* @return The license for the package owning the archive. Can be null.
*/
@Nullable
private License getLicense(@Nullable ArchiveInfo ai) {
if (ai != null) {
Archive aNew = ai.getNewArchive();
if (aNew != null) {
Package pNew = aNew.getParentPackage();
if (pNew != null) {
return pNew.getLicense();
}
}
}
return null;
}
/**
* Reformats the licenseRef to be more human-readable.
* It's an XML ref and in practice it looks like [oem-]android-[type]-license.
* If it's not a format we can deal with, leave it alone.
*/
private String prettyLicenseRef(String ref) {
// capitalize every word
StringBuilder sb = new StringBuilder();
boolean capitalize = true;
for (char c : ref.toCharArray()) {
if (c >= 'a' && c <= 'z') {
if (capitalize) {
c = (char) (c + 'A' - 'a');
capitalize = false;
}
} else {
if (c == '-') {
c = ' ';
}
capitalize = true;
}
sb.append(c);
}
ref = sb.toString();
// A few acronyms should stay upper-case
for (String w : new String[] { "Sdk", "Mips", "Arm" }) {
ref = ref.replaceAll(w, w.toUpperCase(Locale.US));
}
return ref;
}
// End of hiding from SWT Designer
//$hide<<$
}