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.AndroidVersion; 20 import com.android.sdklib.internal.repository.AddonPackage; 21 import com.android.sdklib.internal.repository.Archive; 22 import com.android.sdklib.internal.repository.DocPackage; 23 import com.android.sdklib.internal.repository.ExtraPackage; 24 import com.android.sdklib.internal.repository.IPackageVersion; 25 import com.android.sdklib.internal.repository.MinToolsPackage; 26 import com.android.sdklib.internal.repository.Package; 27 import com.android.sdklib.internal.repository.PlatformPackage; 28 import com.android.sdklib.internal.repository.RepoSource; 29 import com.android.sdklib.internal.repository.RepoSources; 30 import com.android.sdklib.internal.repository.ToolPackage; 31 import com.android.sdklib.internal.repository.Package.UpdateInfo; 32 33 import java.util.ArrayList; 34 import java.util.Collection; 35 import java.util.HashMap; 36 37 /** 38 * The logic to compute which packages to install, based on the choices 39 * made by the user. This adds dependent packages as needed. 40 * <p/> 41 * When the user doesn't provide a selection, looks at local package to find 42 * those that can be updated and compute dependencies too. 43 */ 44 class UpdaterLogic { 45 46 /** 47 * Compute which packages to install by taking the user selection 48 * and adding dependent packages as needed. 49 * 50 * When the user doesn't provide a selection, looks at local package to find 51 * those that can be updated and compute dependencies too. 52 */ computeUpdates( Collection<Archive> selectedArchives, RepoSources sources, Package[] localPkgs)53 public ArrayList<ArchiveInfo> computeUpdates( 54 Collection<Archive> selectedArchives, 55 RepoSources sources, 56 Package[] localPkgs) { 57 58 ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>(); 59 ArrayList<Package> remotePkgs = new ArrayList<Package>(); 60 RepoSource[] remoteSources = sources.getSources(); 61 62 if (selectedArchives == null) { 63 selectedArchives = findUpdates(localPkgs, remotePkgs, remoteSources); 64 } 65 66 for (Archive a : selectedArchives) { 67 insertArchive(a, 68 archives, 69 selectedArchives, 70 remotePkgs, 71 remoteSources, 72 localPkgs, 73 false /*automated*/); 74 } 75 76 return archives; 77 } 78 79 /** 80 * Finds new platforms that the user does not have in his/her local SDK 81 * and adds them to the list of archives to install. 82 */ addNewPlatforms(ArrayList<ArchiveInfo> archives, RepoSources sources, Package[] localPkgs)83 public void addNewPlatforms(ArrayList<ArchiveInfo> archives, 84 RepoSources sources, 85 Package[] localPkgs) { 86 87 // Find the highest platform installed 88 float currentPlatformScore = 0; 89 float currentAddonScore = 0; 90 float currentDocScore = 0; 91 HashMap<String, Float> currentExtraScore = new HashMap<String, Float>(); 92 for (Package p : localPkgs) { 93 int rev = p.getRevision(); 94 int api = 0; 95 boolean isPreview = false; 96 if (p instanceof IPackageVersion) { 97 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 98 api = vers.getApiLevel(); 99 isPreview = vers.isPreview(); 100 } 101 102 // The score is 10*api + (1 if preview) + rev/100 103 // This allows previews to rank above a non-preview and 104 // allows revisions to rank appropriately. 105 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 106 107 if (p instanceof PlatformPackage) { 108 currentPlatformScore = Math.max(currentPlatformScore, score); 109 } else if (p instanceof AddonPackage) { 110 currentAddonScore = Math.max(currentAddonScore, score); 111 } else if (p instanceof ExtraPackage) { 112 currentExtraScore.put(((ExtraPackage) p).getPath(), score); 113 } else if (p instanceof DocPackage) { 114 currentDocScore = Math.max(currentDocScore, score); 115 } 116 } 117 118 RepoSource[] remoteSources = sources.getSources(); 119 ArrayList<Package> remotePkgs = new ArrayList<Package>(); 120 fetchRemotePackages(remotePkgs, remoteSources); 121 122 Package suggestedDoc = null; 123 124 for (Package p : remotePkgs) { 125 int rev = p.getRevision(); 126 int api = 0; 127 boolean isPreview = false; 128 if (p instanceof IPackageVersion) { 129 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 130 api = vers.getApiLevel(); 131 isPreview = vers.isPreview(); 132 } 133 134 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 135 136 boolean shouldAdd = false; 137 if (p instanceof PlatformPackage) { 138 shouldAdd = score > currentPlatformScore; 139 } else if (p instanceof AddonPackage) { 140 shouldAdd = score > currentAddonScore; 141 } else if (p instanceof ExtraPackage) { 142 String key = ((ExtraPackage) p).getPath(); 143 shouldAdd = !currentExtraScore.containsKey(key) || 144 score > currentExtraScore.get(key).floatValue(); 145 } else if (p instanceof DocPackage) { 146 // We don't want all the doc, only the most recent one 147 if (score > currentDocScore) { 148 suggestedDoc = p; 149 currentDocScore = score; 150 } 151 } 152 153 if (shouldAdd) { 154 // We should suggest this package for installation. 155 for (Archive a : p.getArchives()) { 156 if (a.isCompatible()) { 157 insertArchive(a, 158 archives, 159 null /*selectedArchives*/, 160 remotePkgs, 161 remoteSources, 162 localPkgs, 163 true /*automated*/); 164 } 165 } 166 } 167 } 168 169 if (suggestedDoc != null) { 170 // We should suggest this package for installation. 171 for (Archive a : suggestedDoc.getArchives()) { 172 if (a.isCompatible()) { 173 insertArchive(a, 174 archives, 175 null /*selectedArchives*/, 176 remotePkgs, 177 remoteSources, 178 localPkgs, 179 true /*automated*/); 180 } 181 } 182 } 183 184 } 185 186 /** 187 * Find suitable updates to all current local packages. 188 */ findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs, RepoSource[] remoteSources)189 private Collection<Archive> findUpdates(Package[] localPkgs, 190 ArrayList<Package> remotePkgs, 191 RepoSource[] remoteSources) { 192 ArrayList<Archive> updates = new ArrayList<Archive>(); 193 194 fetchRemotePackages(remotePkgs, remoteSources); 195 196 for (Package localPkg : localPkgs) { 197 for (Package remotePkg : remotePkgs) { 198 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) { 199 // Found a suitable update. Only accept the remote package 200 // if it provides at least one compatible archive. 201 202 for (Archive a : remotePkg.getArchives()) { 203 if (a.isCompatible()) { 204 updates.add(a); 205 break; 206 } 207 } 208 } 209 } 210 } 211 212 return updates; 213 } 214 insertArchive(Archive archive, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs, boolean automated)215 private ArchiveInfo insertArchive(Archive archive, 216 ArrayList<ArchiveInfo> outArchives, 217 Collection<Archive> selectedArchives, 218 ArrayList<Package> remotePkgs, 219 RepoSource[] remoteSources, 220 Package[] localPkgs, 221 boolean automated) { 222 Package p = archive.getParentPackage(); 223 224 // Is this an update? 225 Archive updatedArchive = null; 226 for (Package lp : localPkgs) { 227 assert lp.getArchives().length == 1; 228 if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) { 229 updatedArchive = lp.getArchives()[0]; 230 } 231 } 232 233 // find dependencies 234 ArchiveInfo dep = findDependency(p, 235 outArchives, 236 selectedArchives, 237 remotePkgs, 238 remoteSources, 239 localPkgs); 240 241 // Make sure it's not a dup 242 ArchiveInfo ai = null; 243 244 for (ArchiveInfo ai2 : outArchives) { 245 if (ai2.getNewArchive().getParentPackage().sameItemAs(archive.getParentPackage())) { 246 ai = ai2; 247 break; 248 } 249 } 250 251 if (ai == null) { 252 ai = new ArchiveInfo( 253 archive, //newArchive 254 updatedArchive, //replaced 255 dep //dependsOn 256 ); 257 outArchives.add(ai); 258 } 259 260 if (dep != null) { 261 dep.addDependencyFor(ai); 262 } 263 264 return ai; 265 } 266 findDependency(Package pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)267 private ArchiveInfo findDependency(Package pkg, 268 ArrayList<ArchiveInfo> outArchives, 269 Collection<Archive> selectedArchives, 270 ArrayList<Package> remotePkgs, 271 RepoSource[] remoteSources, 272 Package[] localPkgs) { 273 274 // Current dependencies can be: 275 // - addon: *always* depends on platform of same API level 276 // - platform: *might* depends on tools of rev >= min-tools-rev 277 278 if (pkg instanceof AddonPackage) { 279 AddonPackage addon = (AddonPackage) pkg; 280 281 return findPlatformDependency( 282 addon, 283 outArchives, 284 selectedArchives, 285 remotePkgs, 286 remoteSources, 287 localPkgs); 288 289 } else if (pkg instanceof MinToolsPackage) { 290 MinToolsPackage platformOrExtra = (MinToolsPackage) pkg; 291 292 return findToolsDependency( 293 platformOrExtra, 294 outArchives, 295 selectedArchives, 296 remotePkgs, 297 remoteSources, 298 localPkgs); 299 } 300 301 return null; 302 } 303 304 /** 305 * Resolves dependencies on tools. 306 * 307 * A platform or an extra package can both have a min-tools-rev, in which case it 308 * depends on having a tools package of the requested revision. 309 * Finds the tools dependency. If found, add it to the list of things to install. 310 * Returns the archive info dependency, if any. 311 */ findToolsDependency(MinToolsPackage platformOrExtra, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)312 protected ArchiveInfo findToolsDependency(MinToolsPackage platformOrExtra, 313 ArrayList<ArchiveInfo> outArchives, 314 Collection<Archive> selectedArchives, 315 ArrayList<Package> remotePkgs, 316 RepoSource[] remoteSources, 317 Package[] localPkgs) { 318 // This is the requirement to match. 319 int rev = platformOrExtra.getMinToolsRevision(); 320 321 if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) { 322 // Well actually there's no requirement. 323 return null; 324 } 325 326 // First look in local packages. 327 for (Package p : localPkgs) { 328 if (p instanceof ToolPackage) { 329 if (((ToolPackage) p).getRevision() >= rev) { 330 // We found one already installed. We don't report this dependency 331 // as the UI only cares about resolving "newly added dependencies". 332 return null; 333 } 334 } 335 } 336 337 // Look in archives already scheduled for install 338 for (ArchiveInfo ai : outArchives) { 339 Package p = ai.getNewArchive().getParentPackage(); 340 if (p instanceof ToolPackage) { 341 if (((ToolPackage) p).getRevision() >= rev) { 342 // The dependency is already scheduled for install, nothing else to do. 343 return ai; 344 } 345 } 346 } 347 348 // Otherwise look in the selected archives. 349 if (selectedArchives != null) { 350 for (Archive a : selectedArchives) { 351 Package p = a.getParentPackage(); 352 if (p instanceof ToolPackage) { 353 if (((ToolPackage) p).getRevision() >= rev) { 354 // It's not already in the list of things to install, so add it now 355 return insertArchive(a, outArchives, 356 selectedArchives, remotePkgs, remoteSources, localPkgs, 357 true /*automated*/); 358 } 359 } 360 } 361 } 362 363 // Finally nothing matched, so let's look at all available remote packages 364 fetchRemotePackages(remotePkgs, remoteSources); 365 for (Package p : remotePkgs) { 366 if (p instanceof ToolPackage) { 367 if (((ToolPackage) p).getRevision() >= rev) { 368 // It's not already in the list of things to install, so add the 369 // first compatible archive we can find. 370 for (Archive a : p.getArchives()) { 371 if (a.isCompatible()) { 372 return insertArchive(a, outArchives, 373 selectedArchives, remotePkgs, remoteSources, localPkgs, 374 true /*automated*/); 375 } 376 } 377 } 378 } 379 } 380 381 // We end up here if nothing matches. We don't have a good tools to match. 382 // Seriously, that can't happens unless we totally screwed our repo manifest. 383 // We'll let this one go through anyway. 384 return null; 385 } 386 387 /** 388 * Resolves dependencies on platform. 389 * 390 * An addon depends on having a platform with the same API version. 391 * Finds the platform dependency. If found, add it to the list of things to install. 392 * Returns the archive info dependency, if any. 393 */ findPlatformDependency(AddonPackage addon, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)394 protected ArchiveInfo findPlatformDependency(AddonPackage addon, 395 ArrayList<ArchiveInfo> outArchives, 396 Collection<Archive> selectedArchives, 397 ArrayList<Package> remotePkgs, 398 RepoSource[] remoteSources, 399 Package[] localPkgs) { 400 // This is the requirement to match. 401 AndroidVersion v = addon.getVersion(); 402 403 // Find a platform that would satisfy the requirement. 404 405 // First look in local packages. 406 for (Package p : localPkgs) { 407 if (p instanceof PlatformPackage) { 408 if (v.equals(((PlatformPackage) p).getVersion())) { 409 // We found one already installed. We don't report this dependency 410 // as the UI only cares about resolving "newly added dependencies". 411 return null; 412 } 413 } 414 } 415 416 // Look in archives already scheduled for install 417 for (ArchiveInfo ai : outArchives) { 418 Package p = ai.getNewArchive().getParentPackage(); 419 if (p instanceof PlatformPackage) { 420 if (v.equals(((PlatformPackage) p).getVersion())) { 421 // The dependency is already scheduled for install, nothing else to do. 422 return ai; 423 } 424 } 425 } 426 427 // Otherwise look in the selected archives. 428 if (selectedArchives != null) { 429 for (Archive a : selectedArchives) { 430 Package p = a.getParentPackage(); 431 if (p instanceof PlatformPackage) { 432 if (v.equals(((PlatformPackage) p).getVersion())) { 433 // It's not already in the list of things to install, so add it now 434 return insertArchive(a, outArchives, 435 selectedArchives, remotePkgs, remoteSources, localPkgs, 436 true /*automated*/); 437 } 438 } 439 } 440 } 441 442 // Finally nothing matched, so let's look at all available remote packages 443 fetchRemotePackages(remotePkgs, remoteSources); 444 for (Package p : remotePkgs) { 445 if (p instanceof PlatformPackage) { 446 if (v.equals(((PlatformPackage) p).getVersion())) { 447 // It's not already in the list of things to install, so add the 448 // first compatible archive we can find. 449 for (Archive a : p.getArchives()) { 450 if (a.isCompatible()) { 451 return insertArchive(a, outArchives, 452 selectedArchives, remotePkgs, remoteSources, localPkgs, 453 true /*automated*/); 454 } 455 } 456 } 457 } 458 } 459 460 // We end up here if nothing matches. We don't have a good platform to match. 461 // Seriously, that can't happens unless the repository contains a bogus addon 462 // entry that does not match any existing platform API level. 463 // It's conceivable that a 3rd part addon repo might have error, in which case 464 // we'll let this one go through anyway. 465 return null; 466 } 467 468 /** Fetch all remote packages only if really needed. */ fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources)469 protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) { 470 if (remotePkgs.size() > 0) { 471 return; 472 } 473 474 for (RepoSource remoteSrc : remoteSources) { 475 Package[] pkgs = remoteSrc.getPackages(); 476 if (pkgs != null) { 477 nextPackage: for (Package pkg : pkgs) { 478 for (Archive a : pkg.getArchives()) { 479 // Only add a package if it contains at least one compatible archive 480 if (a.isCompatible()) { 481 remotePkgs.add(pkg); 482 continue nextPackage; 483 } 484 } 485 } 486 } 487 } 488 } 489 490 } 491