1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.sdkuilib.internal.repository; 18 19 import com.android.sdklib.internal.repository.Archive; 20 import com.android.sdklib.internal.repository.IDescription; 21 import com.android.sdklib.internal.repository.ITask; 22 import com.android.sdklib.internal.repository.ITaskMonitor; 23 import com.android.sdklib.internal.repository.Package; 24 import com.android.sdklib.internal.repository.RepoSource; 25 import com.android.sdklib.internal.repository.Package.UpdateInfo; 26 import com.android.sdkuilib.internal.repository.icons.ImageFactory; 27 28 import org.eclipse.jface.viewers.IContentProvider; 29 import org.eclipse.jface.viewers.ILabelProvider; 30 import org.eclipse.jface.viewers.ITreeContentProvider; 31 import org.eclipse.jface.viewers.LabelProvider; 32 import org.eclipse.jface.viewers.Viewer; 33 import org.eclipse.swt.graphics.Image; 34 35 import java.util.ArrayList; 36 37 /** 38 * A list of sdk-repository sources. 39 * 40 * This implementation is UI dependent. 41 */ 42 public class RepoSourcesAdapter { 43 44 private final UpdaterData mUpdaterData; 45 46 /** 47 * A dummy RepoSource entry returned for sources which had load errors. 48 * It displays a summary of the error as its short description or 49 * it displays the source's long description. 50 */ 51 public static class RepoSourceError implements IDescription { 52 53 private final RepoSource mSource; 54 55 public RepoSourceError(RepoSource source) { 56 mSource = source; 57 } 58 59 public String getLongDescription() { 60 return mSource.getLongDescription(); 61 } 62 63 public String getShortDescription() { 64 return mSource.getFetchError(); 65 } 66 } 67 68 /** 69 * A dummy RepoSource entry returned for sources with no packages. 70 * We need that to force the SWT tree to display an open/close triangle 71 * even for empty sources. 72 */ 73 public static class RepoSourceEmpty implements IDescription { 74 75 private final RepoSource mSource; 76 private final boolean mEmptyBecauseOfUpdateOnly; 77 78 public RepoSourceEmpty(RepoSource source, boolean emptyBecauseOfUpdateOnly) { 79 mSource = source; 80 mEmptyBecauseOfUpdateOnly = emptyBecauseOfUpdateOnly; 81 } 82 83 public String getLongDescription() { 84 return mSource.getLongDescription(); 85 } 86 87 public String getShortDescription() { 88 if (mEmptyBecauseOfUpdateOnly) { 89 return "Some packages were found but are not compatible updates."; 90 } else { 91 return "No packages found"; 92 } 93 } 94 } 95 96 public RepoSourcesAdapter(UpdaterData updaterData) { 97 mUpdaterData = updaterData; 98 } 99 100 public ILabelProvider getLabelProvider() { 101 return new ViewerLabelProvider(); 102 } 103 104 105 public IContentProvider getContentProvider() { 106 return new TreeContentProvider(); 107 } 108 109 // ------------ 110 111 private class ViewerLabelProvider extends LabelProvider { 112 113 /** Returns an image appropriate for this element. */ 114 @Override 115 public Image getImage(Object element) { 116 117 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 118 119 if (imgFactory != null) { 120 return imgFactory.getImageForObject(element); 121 } 122 123 return super.getImage(element); 124 } 125 126 /** Returns the toString of the element. */ 127 @Override 128 public String getText(Object element) { 129 if (element instanceof IDescription) { 130 return ((IDescription) element).getShortDescription(); 131 } 132 return super.getText(element); 133 } 134 } 135 136 // ------------ 137 138 private class TreeContentProvider implements ITreeContentProvider { 139 140 // Called when the viewer is disposed 141 public void dispose() { 142 // pass 143 } 144 145 // Called when the input is set or changed on the provider 146 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 147 assert newInput == RepoSourcesAdapter.this; 148 } 149 150 /** 151 * Called to collect the root elements for the given input. 152 * The input here is a {@link RepoSourcesAdapter} object, this returns an array 153 * of {@link RepoSource}. 154 */ 155 public Object[] getElements(Object inputElement) { 156 return getChildren(inputElement); 157 } 158 159 /** 160 * Get the children of the given parent. This is requested on-demand as 161 * nodes are expanded. 162 * 163 * For a {@link RepoSourcesAdapter} object, returns an array of {@link RepoSource}s. 164 * For a {@link RepoSource}, returns an array of {@link Package}s. 165 * For a {@link Package}, returns an array of {@link Archive}s. 166 */ 167 public Object[] getChildren(Object parentElement) { 168 if (parentElement == RepoSourcesAdapter.this) { 169 return mUpdaterData.getSources().getSources(); 170 171 } else if (parentElement instanceof RepoSource) { 172 return getRepoSourceChildren((RepoSource) parentElement); 173 174 } else if (parentElement instanceof Package) { 175 return getPackageChildren((Package) parentElement); 176 } 177 178 return new Object[0]; 179 } 180 181 /** 182 * Returns the list of packages for this repo source, eventually filtered to display 183 * only update packages. If the list is empty, returns a specific empty node. If there's 184 * an error, returns a specific error node. 185 */ 186 private Object[] getRepoSourceChildren(final RepoSource source) { 187 Package[] packages = source.getPackages(); 188 189 if (packages == null && source.getFetchError() == null) { 190 final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); 191 192 mUpdaterData.getTaskFactory().start("Loading Source", new ITask() { 193 public void run(ITaskMonitor monitor) { 194 source.load(monitor, forceHttp); 195 } 196 }); 197 198 packages = source.getPackages(); 199 } 200 201 boolean wasEmptyBeforeFilter = (packages == null || packages.length == 0); 202 203 // filter out only the packages that are new/upgrade. 204 if (packages != null && mUpdaterData.getSettingsController().getShowUpdateOnly()) { 205 packages = filteredPackages(packages); 206 } 207 if (packages != null && packages.length == 0) { 208 packages = null; 209 } 210 211 ArrayList<Object> results = new ArrayList<Object>(); 212 213 if (source.getFetchError() != null) { 214 // Insert a dummy entry to display the fetch error 215 results.add(new RepoSourceError(source)); 216 } 217 218 // Either return a non-null package list or create a new empty node 219 if (packages != null) { 220 for (Package p : packages) { 221 results.add(p); 222 } 223 } else { 224 results.add(new RepoSourceEmpty(source, !wasEmptyBeforeFilter)); 225 } 226 227 return results.toArray(); 228 } 229 230 /** 231 * Returns the list of archives for the given package, eventually filtering it 232 * to only show the compatible archives. 233 */ 234 private Object[] getPackageChildren(Package pkg) { 235 Archive[] archives = pkg.getArchives(); 236 if (mUpdaterData.getSettingsController().getShowUpdateOnly()) { 237 for (Archive archive : archives) { 238 239 // if we only want the compatible archives, then we just take the first 240 // one. it's unlikely there are 2 compatible archives for the same 241 // package 242 if (archive.isCompatible()) { 243 return new Object[] { archive }; 244 } 245 } 246 } 247 248 return archives; 249 } 250 251 /** 252 * Returns the parent of a given element. 253 * The input {@link RepoSourcesAdapter} is the parent of all {@link RepoSource} elements. 254 */ 255 public Object getParent(Object element) { 256 257 if (element instanceof RepoSource) { 258 return RepoSourcesAdapter.this; 259 260 } else if (element instanceof Package) { 261 return ((Package) element).getParentSource(); 262 263 } else if (element instanceof Archive) { 264 return ((Archive) element).getParentPackage(); 265 } 266 return null; 267 } 268 269 /** 270 * Returns true if a given element has children, which is used to display a 271 * "+/expand" box next to the tree node. 272 * All {@link RepoSource} and {@link Package} are expandable, whether they actually 273 * have any children or not. 274 */ 275 public boolean hasChildren(Object element) { 276 return element instanceof RepoSource || element instanceof Package; 277 } 278 } 279 280 /** 281 * Filters out a list of remote packages to only keep the ones that are either new or 282 * updates of existing package. 283 * @param remotePackages the list of packages to filter. 284 * @return a non null (but maybe empty) list of new or update packages. 285 */ 286 private Package[] filteredPackages(Package[] remotePackages) { 287 // get the installed packages 288 Package[] installedPackages = mUpdaterData.getInstalledPackage(); 289 290 ArrayList<Package> filteredList = new ArrayList<Package>(); 291 292 // for each remote packages, we look for an existing version. 293 // If no existing version -> add to the list 294 // if existing version but with older revision -> add it to the list 295 for (Package remotePkg : remotePackages) { 296 boolean newPkg = true; 297 298 // We're not going to offer obsolete packages as updates. 299 if (remotePkg.isObsolete()) { 300 continue; 301 } 302 303 // For all potential packages, we also make sure that there's an archive for 304 // the current platform, or we simply skip them. 305 if (remotePkg.hasCompatibleArchive()) { 306 for (Package installedPkg : installedPackages) { 307 UpdateInfo info = installedPkg.canBeUpdatedBy(remotePkg); 308 if (info == UpdateInfo.UPDATE) { 309 filteredList.add(remotePkg); 310 newPkg = false; 311 break; // there shouldn't be 2 revisions of the same package 312 } else if (info != UpdateInfo.INCOMPATIBLE) { 313 newPkg = false; 314 break; // there shouldn't be 2 revisions of the same package 315 } 316 } 317 318 // if we have not found the same package, then we add it (it's a new package) 319 if (newPkg) { 320 filteredList.add(remotePkg); 321 } 322 } 323 } 324 325 return filteredList.toArray(new Package[filteredList.size()]); 326 } 327 } 328