/* | |
* 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.ITask; | |
import com.android.sdklib.internal.repository.ITaskMonitor; | |
import com.android.sdklib.internal.repository.Package; | |
import com.android.sdklib.internal.repository.RepoSource; | |
import com.android.sdklib.internal.repository.Package.UpdateInfo; | |
import com.android.sdkuilib.internal.repository.icons.ImageFactory; | |
import org.eclipse.jface.viewers.IContentProvider; | |
import org.eclipse.jface.viewers.ILabelProvider; | |
import org.eclipse.jface.viewers.ITreeContentProvider; | |
import org.eclipse.jface.viewers.LabelProvider; | |
import org.eclipse.jface.viewers.Viewer; | |
import org.eclipse.swt.graphics.Image; | |
import java.util.ArrayList; | |
/** | |
* A list of sdk-repository sources. | |
* | |
* This implementation is UI dependent. | |
*/ | |
public class RepoSourcesAdapter { | |
private final UpdaterData mUpdaterData; | |
/** | |
* A dummy RepoSource entry returned for sources which had load errors. | |
* It displays a summary of the error as its short description or | |
* it displays the source's long description. | |
*/ | |
public static class RepoSourceError implements IDescription { | |
private final RepoSource mSource; | |
public RepoSourceError(RepoSource source) { | |
mSource = source; | |
} | |
public String getLongDescription() { | |
return mSource.getLongDescription(); | |
} | |
public String getShortDescription() { | |
return mSource.getFetchError(); | |
} | |
} | |
/** | |
* A dummy RepoSource entry returned for sources with no packages. | |
* We need that to force the SWT tree to display an open/close triangle | |
* even for empty sources. | |
*/ | |
public static class RepoSourceEmpty implements IDescription { | |
private final RepoSource mSource; | |
private final boolean mEmptyBecauseOfUpdateOnly; | |
public RepoSourceEmpty(RepoSource source, boolean emptyBecauseOfUpdateOnly) { | |
mSource = source; | |
mEmptyBecauseOfUpdateOnly = emptyBecauseOfUpdateOnly; | |
} | |
public String getLongDescription() { | |
return mSource.getLongDescription(); | |
} | |
public String getShortDescription() { | |
if (mEmptyBecauseOfUpdateOnly) { | |
return "Some packages were found but are not compatible updates."; | |
} else { | |
return "No packages found"; | |
} | |
} | |
} | |
public RepoSourcesAdapter(UpdaterData updaterData) { | |
mUpdaterData = updaterData; | |
} | |
public ILabelProvider getLabelProvider() { | |
return new ViewerLabelProvider(); | |
} | |
public IContentProvider getContentProvider() { | |
return new TreeContentProvider(); | |
} | |
// ------------ | |
private class ViewerLabelProvider extends LabelProvider { | |
/** Returns an image appropriate for this element. */ | |
@Override | |
public Image getImage(Object element) { | |
ImageFactory imgFactory = mUpdaterData.getImageFactory(); | |
if (imgFactory != null) { | |
return imgFactory.getImageForObject(element); | |
} | |
return super.getImage(element); | |
} | |
/** Returns the toString of the element. */ | |
@Override | |
public String getText(Object element) { | |
if (element instanceof IDescription) { | |
return ((IDescription) element).getShortDescription(); | |
} | |
return super.getText(element); | |
} | |
} | |
// ------------ | |
private class TreeContentProvider implements ITreeContentProvider { | |
// Called when the viewer is disposed | |
public void dispose() { | |
// pass | |
} | |
// Called when the input is set or changed on the provider | |
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { | |
assert newInput == RepoSourcesAdapter.this; | |
} | |
/** | |
* Called to collect the root elements for the given input. | |
* The input here is a {@link RepoSourcesAdapter} object, this returns an array | |
* of {@link RepoSource}. | |
*/ | |
public Object[] getElements(Object inputElement) { | |
return getChildren(inputElement); | |
} | |
/** | |
* Get the children of the given parent. This is requested on-demand as | |
* nodes are expanded. | |
* | |
* For a {@link RepoSourcesAdapter} object, returns an array of {@link RepoSource}s. | |
* For a {@link RepoSource}, returns an array of {@link Package}s. | |
* For a {@link Package}, returns an array of {@link Archive}s. | |
*/ | |
public Object[] getChildren(Object parentElement) { | |
if (parentElement == RepoSourcesAdapter.this) { | |
return mUpdaterData.getSources().getSources(); | |
} else if (parentElement instanceof RepoSource) { | |
return getRepoSourceChildren((RepoSource) parentElement); | |
} else if (parentElement instanceof Package) { | |
return getPackageChildren((Package) parentElement); | |
} | |
return new Object[0]; | |
} | |
/** | |
* Returns the list of packages for this repo source, eventually filtered to display | |
* only update packages. If the list is empty, returns a specific empty node. If there's | |
* an error, returns a specific error node. | |
*/ | |
private Object[] getRepoSourceChildren(final RepoSource source) { | |
Package[] packages = source.getPackages(); | |
if (packages == null && source.getFetchError() == null) { | |
final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); | |
mUpdaterData.getTaskFactory().start("Loading Source", new ITask() { | |
public void run(ITaskMonitor monitor) { | |
source.load(monitor, forceHttp); | |
} | |
}); | |
packages = source.getPackages(); | |
} | |
boolean wasEmptyBeforeFilter = (packages == null || packages.length == 0); | |
// filter out only the packages that are new/upgrade. | |
if (packages != null && mUpdaterData.getSettingsController().getShowUpdateOnly()) { | |
packages = filteredPackages(packages); | |
} | |
if (packages != null && packages.length == 0) { | |
packages = null; | |
} | |
if (packages == null && source.getFetchError() != null) { | |
// Return a dummy entry to display the fetch error | |
return new Object[] { new RepoSourceError(source) }; | |
} | |
// Either return a non-null package list or create a new empty node | |
if (packages != null) { | |
return packages; | |
} else { | |
return new Object[] { new RepoSourceEmpty(source, !wasEmptyBeforeFilter) } ; | |
} | |
} | |
/** | |
* Returns the list of archives for the given package, eventually filtering it | |
* to only show the compatible archives. | |
*/ | |
private Object[] getPackageChildren(Package pkg) { | |
Archive[] archives = pkg.getArchives(); | |
if (mUpdaterData.getSettingsController().getShowUpdateOnly()) { | |
for (Archive archive : archives) { | |
// if we only want the compatible archives, then we just take the first | |
// one. it's unlikely there are 2 compatible archives for the same | |
// package | |
if (archive.isCompatible()) { | |
return new Object[] { archive }; | |
} | |
} | |
} | |
return archives; | |
} | |
/** | |
* Returns the parent of a given element. | |
* The input {@link RepoSourcesAdapter} is the parent of all {@link RepoSource} elements. | |
*/ | |
public Object getParent(Object element) { | |
if (element instanceof RepoSource) { | |
return RepoSourcesAdapter.this; | |
} else if (element instanceof Package) { | |
return ((Package) element).getParentSource(); | |
} else if (element instanceof Archive) { | |
return ((Archive) element).getParentPackage(); | |
} | |
return null; | |
} | |
/** | |
* Returns true if a given element has children, which is used to display a | |
* "+/expand" box next to the tree node. | |
* All {@link RepoSource} and {@link Package} are expandable, whether they actually | |
* have any children or not. | |
*/ | |
public boolean hasChildren(Object element) { | |
return element instanceof RepoSource || element instanceof Package; | |
} | |
} | |
/** | |
* Filters out a list of remote packages to only keep the ones that are either new or | |
* updates of existing package. | |
* @param remotePackages the list of packages to filter. | |
* @return a non null (but maybe empty) list of new or update packages. | |
*/ | |
private Package[] filteredPackages(Package[] remotePackages) { | |
// get the installed packages | |
Package[] installedPackages = mUpdaterData.getInstalledPackage(); | |
ArrayList<Package> filteredList = new ArrayList<Package>(); | |
// for each remote packages, we look for an existing version. | |
// If no existing version -> add to the list | |
// if existing version but with older revision -> add it to the list | |
for (Package remotePkg : remotePackages) { | |
boolean newPkg = true; | |
// For all potential packages, we also make sure that there's an archive for the current | |
// platform, or we simply skip them. | |
if (remotePkg.hasCompatibleArchive()) { | |
for (Package installedPkg : installedPackages) { | |
UpdateInfo info = installedPkg.canBeUpdatedBy(remotePkg); | |
if (info == UpdateInfo.UPDATE) { | |
filteredList.add(remotePkg); | |
newPkg = false; | |
break; // there shouldn't be 2 revisions of the same package | |
} else if (info != UpdateInfo.INCOMPATIBLE) { | |
newPkg = false; | |
break; // there shouldn't be 2 revisions of the same package | |
} | |
} | |
// if we have not found the same package, then we add it (it's a new package) | |
if (newPkg) { | |
filteredList.add(remotePkg); | |
} | |
} | |
} | |
return filteredList.toArray(new Package[filteredList.size()]); | |
} | |
} |