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.IExactApiLevelDependency; 25 import com.android.sdklib.internal.repository.IMinApiLevelDependency; 26 import com.android.sdklib.internal.repository.IMinPlatformToolsDependency; 27 import com.android.sdklib.internal.repository.IMinToolsDependency; 28 import com.android.sdklib.internal.repository.IPackageVersion; 29 import com.android.sdklib.internal.repository.IPlatformDependency; 30 import com.android.sdklib.internal.repository.ITask; 31 import com.android.sdklib.internal.repository.ITaskMonitor; 32 import com.android.sdklib.internal.repository.MinToolsPackage; 33 import com.android.sdklib.internal.repository.Package; 34 import com.android.sdklib.internal.repository.PlatformPackage; 35 import com.android.sdklib.internal.repository.PlatformToolPackage; 36 import com.android.sdklib.internal.repository.SamplePackage; 37 import com.android.sdklib.internal.repository.SdkSource; 38 import com.android.sdklib.internal.repository.SdkSources; 39 import com.android.sdklib.internal.repository.ToolPackage; 40 import com.android.sdklib.internal.repository.Package.UpdateInfo; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collection; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Set; 49 50 /** 51 * The logic to compute which packages to install, based on the choices 52 * made by the user. This adds required packages as needed. 53 * <p/> 54 * When the user doesn't provide a selection, looks at local package to find 55 * those that can be updated and compute dependencies too. 56 */ 57 class SdkUpdaterLogic { 58 59 private final IUpdaterData mUpdaterData; 60 SdkUpdaterLogic(IUpdaterData updaterData)61 public SdkUpdaterLogic(IUpdaterData updaterData) { 62 mUpdaterData = updaterData; 63 } 64 65 /** 66 * Compute which packages to install by taking the user selection 67 * and adding required packages as needed. 68 * 69 * When the user doesn't provide a selection, looks at local packages to find 70 * those that can be updated and compute dependencies too. 71 */ computeUpdates( Collection<Archive> selectedArchives, SdkSources sources, Package[] localPkgs, boolean includeObsoletes)72 public List<ArchiveInfo> computeUpdates( 73 Collection<Archive> selectedArchives, 74 SdkSources sources, 75 Package[] localPkgs, 76 boolean includeObsoletes) { 77 78 List<ArchiveInfo> archives = new ArrayList<ArchiveInfo>(); 79 List<Package> remotePkgs = new ArrayList<Package>(); 80 SdkSource[] remoteSources = sources.getAllSources(); 81 82 // Create ArchiveInfos out of local (installed) packages. 83 ArchiveInfo[] localArchives = createLocalArchives(localPkgs); 84 85 // If we do not have a specific list of archives to install (that is the user 86 // selected "update all" rather than request specific packages), then we try to 87 // find updates based on the *existing* packages. 88 if (selectedArchives == null) { 89 selectedArchives = findUpdates( 90 localArchives, 91 remotePkgs, 92 remoteSources, 93 includeObsoletes); 94 } 95 96 // Once we have a list of packages to install, we try to solve all their 97 // dependencies by automatically adding them to the list of things to install. 98 // This works on the list provided either by the user directly or the list 99 // computed from potential updates. 100 for (Archive a : selectedArchives) { 101 insertArchive(a, 102 archives, 103 selectedArchives, 104 remotePkgs, 105 remoteSources, 106 localArchives, 107 false /*automated*/); 108 } 109 110 // Finally we need to look at *existing* packages which are not being updated 111 // and check if they have any missing dependencies and suggest how to fix 112 // these dependencies. 113 fixMissingLocalDependencies( 114 archives, 115 selectedArchives, 116 remotePkgs, 117 remoteSources, 118 localArchives); 119 120 return archives; 121 } 122 123 /** 124 * Finds new packages that the user does not have in his/her local SDK 125 * and adds them to the list of archives to install. 126 * <p/> 127 * The default is to only find "new" platforms, that is anything more 128 * recent than the highest platform currently installed. 129 * A side effect is that for an empty SDK install this will list *all* 130 * platforms available (since there's no "highest" installed platform.) 131 * 132 * @param archives The in-out list of archives to install. Typically the 133 * list is not empty at first as it should contain any archives that is 134 * already scheduled for install. This method will add to the list. 135 * @param sources The list of all sources, to fetch them as necessary. 136 * @param localPkgs The list of all currently installed packages. 137 * @param includeObsoletes When true, this will list all platform 138 * (included these lower than the highest installed one) as well as 139 * all obsolete packages of these platforms. 140 */ addNewPlatforms( Collection<ArchiveInfo> archives, SdkSources sources, Package[] localPkgs, boolean includeObsoletes)141 public void addNewPlatforms( 142 Collection<ArchiveInfo> archives, 143 SdkSources sources, 144 Package[] localPkgs, 145 boolean includeObsoletes) { 146 147 // Create ArchiveInfos out of local (installed) packages. 148 ArchiveInfo[] localArchives = createLocalArchives(localPkgs); 149 150 // Find the highest platform installed 151 float currentPlatformScore = 0; 152 float currentSampleScore = 0; 153 float currentAddonScore = 0; 154 float currentDocScore = 0; 155 HashMap<String, Float> currentExtraScore = new HashMap<String, Float>(); 156 if (!includeObsoletes) { 157 if (localPkgs != null) { 158 for (Package p : localPkgs) { 159 int rev = p.getRevision(); 160 int api = 0; 161 boolean isPreview = false; 162 if (p instanceof IPackageVersion) { 163 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 164 api = vers.getApiLevel(); 165 isPreview = vers.isPreview(); 166 } 167 168 // The score is 10*api + (1 if preview) + rev/100 169 // This allows previews to rank above a non-preview and 170 // allows revisions to rank appropriately. 171 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 172 173 if (p instanceof PlatformPackage) { 174 currentPlatformScore = Math.max(currentPlatformScore, score); 175 } else if (p instanceof SamplePackage) { 176 currentSampleScore = Math.max(currentSampleScore, score); 177 } else if (p instanceof AddonPackage) { 178 currentAddonScore = Math.max(currentAddonScore, score); 179 } else if (p instanceof ExtraPackage) { 180 currentExtraScore.put(((ExtraPackage) p).getPath(), score); 181 } else if (p instanceof DocPackage) { 182 currentDocScore = Math.max(currentDocScore, score); 183 } 184 } 185 } 186 } 187 188 SdkSource[] remoteSources = sources.getAllSources(); 189 ArrayList<Package> remotePkgs = new ArrayList<Package>(); 190 fetchRemotePackages(remotePkgs, remoteSources); 191 192 Package suggestedDoc = null; 193 194 for (Package p : remotePkgs) { 195 // Skip obsolete packages unless requested to include them. 196 if (p.isObsolete() && !includeObsoletes) { 197 continue; 198 } 199 200 int rev = p.getRevision(); 201 int api = 0; 202 boolean isPreview = false; 203 if (p instanceof IPackageVersion) { 204 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 205 api = vers.getApiLevel(); 206 isPreview = vers.isPreview(); 207 } 208 209 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 210 211 boolean shouldAdd = false; 212 if (p instanceof PlatformPackage) { 213 shouldAdd = score > currentPlatformScore; 214 } else if (p instanceof SamplePackage) { 215 shouldAdd = score > currentSampleScore; 216 } else if (p instanceof AddonPackage) { 217 shouldAdd = score > currentAddonScore; 218 } else if (p instanceof ExtraPackage) { 219 String key = ((ExtraPackage) p).getPath(); 220 shouldAdd = !currentExtraScore.containsKey(key) || 221 score > currentExtraScore.get(key).floatValue(); 222 } else if (p instanceof DocPackage) { 223 // We don't want all the doc, only the most recent one 224 if (score > currentDocScore) { 225 suggestedDoc = p; 226 currentDocScore = score; 227 } 228 } 229 230 if (shouldAdd) { 231 // We should suggest this package for installation. 232 for (Archive a : p.getArchives()) { 233 if (a.isCompatible()) { 234 insertArchive(a, 235 archives, 236 null /*selectedArchives*/, 237 remotePkgs, 238 remoteSources, 239 localArchives, 240 true /*automated*/); 241 } 242 } 243 } 244 } 245 246 if (suggestedDoc != null) { 247 // We should suggest this package for installation. 248 for (Archive a : suggestedDoc.getArchives()) { 249 if (a.isCompatible()) { 250 insertArchive(a, 251 archives, 252 null /*selectedArchives*/, 253 remotePkgs, 254 remoteSources, 255 localArchives, 256 true /*automated*/); 257 } 258 } 259 } 260 } 261 262 /** 263 * Create a array of {@link ArchiveInfo} based on all local (already installed) 264 * packages. The array is always non-null but may be empty. 265 * <p/> 266 * The local {@link ArchiveInfo} are guaranteed to have one non-null archive 267 * that you can retrieve using {@link ArchiveInfo#getNewArchive()}. 268 */ createLocalArchives(Package[] localPkgs)269 protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) { 270 271 if (localPkgs != null) { 272 ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>(); 273 for (Package p : localPkgs) { 274 // Only accept packages that have one compatible archive. 275 // Local package should have 1 and only 1 compatible archive anyway. 276 for (Archive a : p.getArchives()) { 277 if (a != null && a.isCompatible()) { 278 // We create an "installed" archive info to wrap the local package. 279 // Note that dependencies are not computed since right now we don't 280 // deal with more than one level of dependencies and installed archives 281 // are deemed implicitly accepted anyway. 282 list.add(new LocalArchiveInfo(a)); 283 } 284 } 285 } 286 287 return list.toArray(new ArchiveInfo[list.size()]); 288 } 289 290 return new ArchiveInfo[0]; 291 } 292 293 /** 294 * Find suitable updates to all current local packages. 295 * <p/> 296 * Returns a list of potential updates for *existing* packages. This does NOT solve 297 * dependencies for the new packages. 298 * <p/> 299 * Always returns a non-null collection, which can be empty. 300 */ findUpdates( ArchiveInfo[] localArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, boolean includeObsoletes)301 private Collection<Archive> findUpdates( 302 ArchiveInfo[] localArchives, 303 Collection<Package> remotePkgs, 304 SdkSource[] remoteSources, 305 boolean includeObsoletes) { 306 ArrayList<Archive> updates = new ArrayList<Archive>(); 307 308 fetchRemotePackages(remotePkgs, remoteSources); 309 310 for (ArchiveInfo ai : localArchives) { 311 Archive na = ai.getNewArchive(); 312 if (na == null) { 313 continue; 314 } 315 Package localPkg = na.getParentPackage(); 316 317 for (Package remotePkg : remotePkgs) { 318 // Only look for non-obsolete updates unless requested to include them 319 if ((includeObsoletes || !remotePkg.isObsolete()) && 320 localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) { 321 // Found a suitable update. Only accept the remote package 322 // if it provides at least one compatible archive 323 324 addArchives: 325 for (Archive a : remotePkg.getArchives()) { 326 if (a.isCompatible()) { 327 328 // If we're trying to add a package for revision N, 329 // make sure we don't also have a package for revision N-1. 330 for (int i = updates.size() - 1; i >= 0; i--) { 331 Package pkgFound = updates.get(i).getParentPackage(); 332 if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) { 333 // This package can update one we selected earlier. 334 // Remove the one that can be updated by this new one. 335 updates.remove(i); 336 } else if (remotePkg.canBeUpdatedBy(pkgFound) == 337 UpdateInfo.UPDATE) { 338 // There is a package in the list that is already better 339 // than the one we want to add, so don't add it. 340 break addArchives; 341 } 342 } 343 344 updates.add(a); 345 break; 346 } 347 } 348 } 349 } 350 } 351 352 return updates; 353 } 354 355 /** 356 * Check all local archives which are NOT being updated and see if they 357 * miss any dependency. If they do, try to fix that dependency by selecting 358 * an appropriate package. 359 */ fixMissingLocalDependencies( Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)360 private void fixMissingLocalDependencies( 361 Collection<ArchiveInfo> outArchives, 362 Collection<Archive> selectedArchives, 363 Collection<Package> remotePkgs, 364 SdkSource[] remoteSources, 365 ArchiveInfo[] localArchives) { 366 367 nextLocalArchive: for (ArchiveInfo ai : localArchives) { 368 Archive a = ai.getNewArchive(); 369 Package p = a == null ? null : a.getParentPackage(); 370 if (p == null) { 371 continue; 372 } 373 374 // Is this local archive being updated? 375 for (ArchiveInfo ai2 : outArchives) { 376 if (ai2.getReplaced() == a) { 377 // this new archive will replace the current local one, 378 // so we don't have to care about fixing dependencies (since the 379 // new archive should already have had its dependencies resolved) 380 continue nextLocalArchive; 381 } 382 } 383 384 // find dependencies for the local archive and add them as needed 385 // to the outArchives collection. 386 ArchiveInfo[] deps = findDependency(p, 387 outArchives, 388 selectedArchives, 389 remotePkgs, 390 remoteSources, 391 localArchives); 392 393 if (deps != null) { 394 // The already installed archive has a missing dependency, which we 395 // just selected for install. Make sure we remember the dependency 396 // so that we can enforce it later in the UI. 397 for (ArchiveInfo aid : deps) { 398 aid.addDependencyFor(ai); 399 } 400 } 401 } 402 } 403 insertArchive(Archive archive, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives, boolean automated)404 private ArchiveInfo insertArchive(Archive archive, 405 Collection<ArchiveInfo> outArchives, 406 Collection<Archive> selectedArchives, 407 Collection<Package> remotePkgs, 408 SdkSource[] remoteSources, 409 ArchiveInfo[] localArchives, 410 boolean automated) { 411 Package p = archive.getParentPackage(); 412 413 // Is this an update? 414 Archive updatedArchive = null; 415 for (ArchiveInfo ai : localArchives) { 416 Archive a = ai.getNewArchive(); 417 if (a != null) { 418 Package lp = a.getParentPackage(); 419 420 if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) { 421 updatedArchive = a; 422 } 423 } 424 } 425 426 // Find dependencies and adds them as needed to outArchives 427 ArchiveInfo[] deps = findDependency(p, 428 outArchives, 429 selectedArchives, 430 remotePkgs, 431 remoteSources, 432 localArchives); 433 434 // Make sure it's not a dup 435 ArchiveInfo ai = null; 436 437 for (ArchiveInfo ai2 : outArchives) { 438 Archive a2 = ai2.getNewArchive(); 439 if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) { 440 ai = ai2; 441 break; 442 } 443 } 444 445 if (ai == null) { 446 ai = new ArchiveInfo( 447 archive, //newArchive 448 updatedArchive, //replaced 449 deps //dependsOn 450 ); 451 outArchives.add(ai); 452 } 453 454 if (deps != null) { 455 for (ArchiveInfo d : deps) { 456 d.addDependencyFor(ai); 457 } 458 } 459 460 return ai; 461 } 462 463 /** 464 * Resolves dependencies for a given package. 465 * 466 * Returns null if no dependencies were found. 467 * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have 468 * at least size 1 and contain no null elements. 469 */ findDependency(Package pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)470 private ArchiveInfo[] findDependency(Package pkg, 471 Collection<ArchiveInfo> outArchives, 472 Collection<Archive> selectedArchives, 473 Collection<Package> remotePkgs, 474 SdkSource[] remoteSources, 475 ArchiveInfo[] localArchives) { 476 477 // Current dependencies can be: 478 // - addon: *always* depends on platform of same API level 479 // - platform: *might* depends on tools of rev >= min-tools-rev 480 // - extra: *might* depends on platform with api >= min-api-level 481 482 Set<ArchiveInfo> aiFound = new HashSet<ArchiveInfo>(); 483 484 if (pkg instanceof IPlatformDependency) { 485 ArchiveInfo ai = findPlatformDependency( 486 (IPlatformDependency) pkg, 487 outArchives, 488 selectedArchives, 489 remotePkgs, 490 remoteSources, 491 localArchives); 492 493 if (ai != null) { 494 aiFound.add(ai); 495 } 496 } 497 498 if (pkg instanceof IMinToolsDependency) { 499 500 ArchiveInfo ai = findToolsDependency( 501 (IMinToolsDependency) pkg, 502 outArchives, 503 selectedArchives, 504 remotePkgs, 505 remoteSources, 506 localArchives); 507 508 if (ai != null) { 509 aiFound.add(ai); 510 } 511 } 512 513 if (pkg instanceof IMinPlatformToolsDependency) { 514 515 ArchiveInfo ai = findPlatformToolsDependency( 516 (IMinPlatformToolsDependency) pkg, 517 outArchives, 518 selectedArchives, 519 remotePkgs, 520 remoteSources, 521 localArchives); 522 523 if (ai != null) { 524 aiFound.add(ai); 525 } 526 } 527 528 if (pkg instanceof IMinApiLevelDependency) { 529 530 ArchiveInfo ai = findMinApiLevelDependency( 531 (IMinApiLevelDependency) pkg, 532 outArchives, 533 selectedArchives, 534 remotePkgs, 535 remoteSources, 536 localArchives); 537 538 if (ai != null) { 539 aiFound.add(ai); 540 } 541 } 542 543 if (pkg instanceof IExactApiLevelDependency) { 544 545 ArchiveInfo ai = findExactApiLevelDependency( 546 (IExactApiLevelDependency) pkg, 547 outArchives, 548 selectedArchives, 549 remotePkgs, 550 remoteSources, 551 localArchives); 552 553 if (ai != null) { 554 aiFound.add(ai); 555 } 556 } 557 558 if (aiFound.size() > 0) { 559 ArchiveInfo[] result = aiFound.toArray(new ArchiveInfo[aiFound.size()]); 560 Arrays.sort(result); 561 return result; 562 } 563 564 return null; 565 } 566 567 /** 568 * Resolves dependencies on tools. 569 * 570 * A platform or an extra package can both have a min-tools-rev, in which case it 571 * depends on having a tools package of the requested revision. 572 * Finds the tools dependency. If found, add it to the list of things to install. 573 * Returns the archive info dependency, if any. 574 */ findToolsDependency( IMinToolsDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)575 protected ArchiveInfo findToolsDependency( 576 IMinToolsDependency pkg, 577 Collection<ArchiveInfo> outArchives, 578 Collection<Archive> selectedArchives, 579 Collection<Package> remotePkgs, 580 SdkSource[] remoteSources, 581 ArchiveInfo[] localArchives) { 582 // This is the requirement to match. 583 int rev = pkg.getMinToolsRevision(); 584 585 if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) { 586 // Well actually there's no requirement. 587 return null; 588 } 589 590 // First look in locally installed packages. 591 for (ArchiveInfo ai : localArchives) { 592 Archive a = ai.getNewArchive(); 593 if (a != null) { 594 Package p = a.getParentPackage(); 595 if (p instanceof ToolPackage) { 596 if (((ToolPackage) p).getRevision() >= rev) { 597 // We found one already installed. 598 return null; 599 } 600 } 601 } 602 } 603 604 // Look in archives already scheduled for install 605 for (ArchiveInfo ai : outArchives) { 606 Archive a = ai.getNewArchive(); 607 if (a != null) { 608 Package p = a.getParentPackage(); 609 if (p instanceof ToolPackage) { 610 if (((ToolPackage) p).getRevision() >= rev) { 611 // The dependency is already scheduled for install, nothing else to do. 612 return ai; 613 } 614 } 615 } 616 } 617 618 // Otherwise look in the selected archives. 619 if (selectedArchives != null) { 620 for (Archive a : selectedArchives) { 621 Package p = a.getParentPackage(); 622 if (p instanceof ToolPackage) { 623 if (((ToolPackage) p).getRevision() >= rev) { 624 // It's not already in the list of things to install, so add it now 625 return insertArchive(a, 626 outArchives, 627 selectedArchives, 628 remotePkgs, 629 remoteSources, 630 localArchives, 631 true /*automated*/); 632 } 633 } 634 } 635 } 636 637 // Finally nothing matched, so let's look at all available remote packages 638 fetchRemotePackages(remotePkgs, remoteSources); 639 for (Package p : remotePkgs) { 640 if (p instanceof ToolPackage) { 641 if (((ToolPackage) p).getRevision() >= rev) { 642 // It's not already in the list of things to install, so add the 643 // first compatible archive we can find. 644 for (Archive a : p.getArchives()) { 645 if (a.isCompatible()) { 646 return insertArchive(a, 647 outArchives, 648 selectedArchives, 649 remotePkgs, 650 remoteSources, 651 localArchives, 652 true /*automated*/); 653 } 654 } 655 } 656 } 657 } 658 659 // We end up here if nothing matches. We don't have a good platform to match. 660 // We need to indicate this extra depends on a missing platform archive 661 // so that it can be impossible to install later on. 662 return new MissingArchiveInfo(MissingArchiveInfo.TITLE_TOOL, rev); 663 } 664 665 /** 666 * Resolves dependencies on platform-tools. 667 * 668 * A tool package can have a min-platform-tools-rev, in which case it depends on 669 * having a platform-tool package of the requested revision. 670 * Finds the platform-tool dependency. If found, add it to the list of things to install. 671 * Returns the archive info dependency, if any. 672 */ findPlatformToolsDependency( IMinPlatformToolsDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)673 protected ArchiveInfo findPlatformToolsDependency( 674 IMinPlatformToolsDependency pkg, 675 Collection<ArchiveInfo> outArchives, 676 Collection<Archive> selectedArchives, 677 Collection<Package> remotePkgs, 678 SdkSource[] remoteSources, 679 ArchiveInfo[] localArchives) { 680 // This is the requirement to match. 681 int rev = pkg.getMinPlatformToolsRevision(); 682 boolean findMax = false; 683 ArchiveInfo aiMax = null; 684 Archive aMax = null; 685 686 if (rev == IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID) { 687 // The requirement is invalid, which is not supposed to happen since this 688 // property is mandatory. However in a typical upgrade scenario we can end 689 // up with the previous updater managing a new package and not dealing 690 // correctly with the new unknown property. 691 // So instead we parse all the existing and remote packages and try to find 692 // the max available revision and we'll use it. 693 findMax = true; 694 } 695 696 // First look in locally installed packages. 697 for (ArchiveInfo ai : localArchives) { 698 Archive a = ai.getNewArchive(); 699 if (a != null) { 700 Package p = a.getParentPackage(); 701 if (p instanceof PlatformToolPackage) { 702 int r = ((PlatformToolPackage) p).getRevision(); 703 if (findMax && r > rev) { 704 rev = r; 705 aiMax = ai; 706 } else if (!findMax && r >= rev) { 707 // We found one already installed. 708 return null; 709 } 710 } 711 } 712 } 713 714 // Look in archives already scheduled for install 715 for (ArchiveInfo ai : outArchives) { 716 Archive a = ai.getNewArchive(); 717 if (a != null) { 718 Package p = a.getParentPackage(); 719 if (p instanceof PlatformToolPackage) { 720 int r = ((PlatformToolPackage) p).getRevision(); 721 if (findMax && r > rev) { 722 rev = r; 723 aiMax = ai; 724 } else if (!findMax && r >= rev) { 725 // The dependency is already scheduled for install, nothing else to do. 726 return ai; 727 } 728 } 729 } 730 } 731 732 // Otherwise look in the selected archives. 733 if (selectedArchives != null) { 734 for (Archive a : selectedArchives) { 735 Package p = a.getParentPackage(); 736 if (p instanceof PlatformToolPackage) { 737 int r = ((PlatformToolPackage) p).getRevision(); 738 if (findMax && r > rev) { 739 rev = r; 740 aiMax = null; 741 aMax = a; 742 } else if (!findMax && r >= rev) { 743 // It's not already in the list of things to install, so add it now 744 return insertArchive(a, 745 outArchives, 746 selectedArchives, 747 remotePkgs, 748 remoteSources, 749 localArchives, 750 true /*automated*/); 751 } 752 } 753 } 754 } 755 756 // Finally nothing matched, so let's look at all available remote packages 757 fetchRemotePackages(remotePkgs, remoteSources); 758 for (Package p : remotePkgs) { 759 if (p instanceof PlatformToolPackage) { 760 int r = ((PlatformToolPackage) p).getRevision(); 761 if (r >= rev) { 762 // Make sure there's at least one valid archive here 763 for (Archive a : p.getArchives()) { 764 if (a.isCompatible()) { 765 if (findMax && r > rev) { 766 rev = r; 767 aiMax = null; 768 aMax = a; 769 } else if (!findMax && r >= rev) { 770 // It's not already in the list of things to install, so add the 771 // first compatible archive we can find. 772 return insertArchive(a, 773 outArchives, 774 selectedArchives, 775 remotePkgs, 776 remoteSources, 777 localArchives, 778 true /*automated*/); 779 } 780 } 781 } 782 } 783 } 784 } 785 786 if (findMax) { 787 if (aMax != null) { 788 return insertArchive(aMax, 789 outArchives, 790 selectedArchives, 791 remotePkgs, 792 remoteSources, 793 localArchives, 794 true /*automated*/); 795 } else if (aiMax != null) { 796 return aiMax; 797 } 798 } 799 800 // We end up here if nothing matches. We don't have a good platform to match. 801 // We need to indicate this package depends on a missing platform archive 802 // so that it can be impossible to install later on. 803 return new MissingArchiveInfo(MissingArchiveInfo.TITLE_PLATFORM_TOOL, rev); 804 } 805 806 /** 807 * Resolves dependencies on platform for an addon. 808 * 809 * An addon depends on having a platform with the same API level. 810 * 811 * Finds the platform dependency. If found, add it to the list of things to install. 812 * Returns the archive info dependency, if any. 813 */ findPlatformDependency( IPlatformDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)814 protected ArchiveInfo findPlatformDependency( 815 IPlatformDependency pkg, 816 Collection<ArchiveInfo> outArchives, 817 Collection<Archive> selectedArchives, 818 Collection<Package> remotePkgs, 819 SdkSource[] remoteSources, 820 ArchiveInfo[] localArchives) { 821 // This is the requirement to match. 822 AndroidVersion v = pkg.getVersion(); 823 824 // Find a platform that would satisfy the requirement. 825 826 // First look in locally installed packages. 827 for (ArchiveInfo ai : localArchives) { 828 Archive a = ai.getNewArchive(); 829 if (a != null) { 830 Package p = a.getParentPackage(); 831 if (p instanceof PlatformPackage) { 832 if (v.equals(((PlatformPackage) p).getVersion())) { 833 // We found one already installed. 834 return null; 835 } 836 } 837 } 838 } 839 840 // Look in archives already scheduled for install 841 for (ArchiveInfo ai : outArchives) { 842 Archive a = ai.getNewArchive(); 843 if (a != null) { 844 Package p = a.getParentPackage(); 845 if (p instanceof PlatformPackage) { 846 if (v.equals(((PlatformPackage) p).getVersion())) { 847 // The dependency is already scheduled for install, nothing else to do. 848 return ai; 849 } 850 } 851 } 852 } 853 854 // Otherwise look in the selected archives. 855 if (selectedArchives != null) { 856 for (Archive a : selectedArchives) { 857 Package p = a.getParentPackage(); 858 if (p instanceof PlatformPackage) { 859 if (v.equals(((PlatformPackage) p).getVersion())) { 860 // It's not already in the list of things to install, so add it now 861 return insertArchive(a, 862 outArchives, 863 selectedArchives, 864 remotePkgs, 865 remoteSources, 866 localArchives, 867 true /*automated*/); 868 } 869 } 870 } 871 } 872 873 // Finally nothing matched, so let's look at all available remote packages 874 fetchRemotePackages(remotePkgs, remoteSources); 875 for (Package p : remotePkgs) { 876 if (p instanceof PlatformPackage) { 877 if (v.equals(((PlatformPackage) p).getVersion())) { 878 // It's not already in the list of things to install, so add the 879 // first compatible archive we can find. 880 for (Archive a : p.getArchives()) { 881 if (a.isCompatible()) { 882 return insertArchive(a, 883 outArchives, 884 selectedArchives, 885 remotePkgs, 886 remoteSources, 887 localArchives, 888 true /*automated*/); 889 } 890 } 891 } 892 } 893 } 894 895 // We end up here if nothing matches. We don't have a good platform to match. 896 // We need to indicate this addon depends on a missing platform archive 897 // so that it can be impossible to install later on. 898 return new MissingPlatformArchiveInfo(pkg.getVersion()); 899 } 900 901 /** 902 * Resolves platform dependencies for extras. 903 * An extra depends on having a platform with a minimun API level. 904 * 905 * We try to return the highest API level available above the specified minimum. 906 * Note that installed packages have priority so if one installed platform satisfies 907 * the dependency, we'll use it even if there's a higher API platform available but 908 * not installed yet. 909 * 910 * Finds the platform dependency. If found, add it to the list of things to install. 911 * Returns the archive info dependency, if any. 912 */ findMinApiLevelDependency( IMinApiLevelDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)913 protected ArchiveInfo findMinApiLevelDependency( 914 IMinApiLevelDependency pkg, 915 Collection<ArchiveInfo> outArchives, 916 Collection<Archive> selectedArchives, 917 Collection<Package> remotePkgs, 918 SdkSource[] remoteSources, 919 ArchiveInfo[] localArchives) { 920 921 int api = pkg.getMinApiLevel(); 922 923 if (api == IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED) { 924 return null; 925 } 926 927 // Find a platform that would satisfy the requirement. 928 929 // First look in locally installed packages. 930 for (ArchiveInfo ai : localArchives) { 931 Archive a = ai.getNewArchive(); 932 if (a != null) { 933 Package p = a.getParentPackage(); 934 if (p instanceof PlatformPackage) { 935 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 936 // We found one already installed. 937 return null; 938 } 939 } 940 } 941 } 942 943 // Look in archives already scheduled for install 944 int foundApi = 0; 945 ArchiveInfo foundAi = null; 946 947 for (ArchiveInfo ai : outArchives) { 948 Archive a = ai.getNewArchive(); 949 if (a != null) { 950 Package p = a.getParentPackage(); 951 if (p instanceof PlatformPackage) { 952 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 953 if (api > foundApi) { 954 foundApi = api; 955 foundAi = ai; 956 } 957 } 958 } 959 } 960 } 961 962 if (foundAi != null) { 963 // The dependency is already scheduled for install, nothing else to do. 964 return foundAi; 965 } 966 967 // Otherwise look in the selected archives *or* available remote packages 968 // and takes the best out of the two sets. 969 foundApi = 0; 970 Archive foundArchive = null; 971 if (selectedArchives != null) { 972 for (Archive a : selectedArchives) { 973 Package p = a.getParentPackage(); 974 if (p instanceof PlatformPackage) { 975 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 976 if (api > foundApi) { 977 foundApi = api; 978 foundArchive = a; 979 } 980 } 981 } 982 } 983 } 984 985 // Finally nothing matched, so let's look at all available remote packages 986 fetchRemotePackages(remotePkgs, remoteSources); 987 for (Package p : remotePkgs) { 988 if (p instanceof PlatformPackage) { 989 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 990 if (api > foundApi) { 991 // It's not already in the list of things to install, so add the 992 // first compatible archive we can find. 993 for (Archive a : p.getArchives()) { 994 if (a.isCompatible()) { 995 foundApi = api; 996 foundArchive = a; 997 } 998 } 999 } 1000 } 1001 } 1002 } 1003 1004 if (foundArchive != null) { 1005 // It's not already in the list of things to install, so add it now 1006 return insertArchive(foundArchive, 1007 outArchives, 1008 selectedArchives, 1009 remotePkgs, 1010 remoteSources, 1011 localArchives, 1012 true /*automated*/); 1013 } 1014 1015 // We end up here if nothing matches. We don't have a good platform to match. 1016 // We need to indicate this extra depends on a missing platform archive 1017 // so that it can be impossible to install later on. 1018 return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/)); 1019 } 1020 1021 /** 1022 * Resolves platform dependencies for add-ons. 1023 * An add-ons depends on having a platform with an exact specific API level. 1024 * 1025 * Finds the platform dependency. If found, add it to the list of things to install. 1026 * Returns the archive info dependency, if any. 1027 */ findExactApiLevelDependency( IExactApiLevelDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)1028 protected ArchiveInfo findExactApiLevelDependency( 1029 IExactApiLevelDependency pkg, 1030 Collection<ArchiveInfo> outArchives, 1031 Collection<Archive> selectedArchives, 1032 Collection<Package> remotePkgs, 1033 SdkSource[] remoteSources, 1034 ArchiveInfo[] localArchives) { 1035 1036 int api = pkg.getExactApiLevel(); 1037 1038 if (api == IExactApiLevelDependency.API_LEVEL_INVALID) { 1039 return null; 1040 } 1041 1042 // Find a platform that would satisfy the requirement. 1043 1044 // First look in locally installed packages. 1045 for (ArchiveInfo ai : localArchives) { 1046 Archive a = ai.getNewArchive(); 1047 if (a != null) { 1048 Package p = a.getParentPackage(); 1049 if (p instanceof PlatformPackage) { 1050 if (((PlatformPackage) p).getVersion().equals(api)) { 1051 // We found one already installed. 1052 return null; 1053 } 1054 } 1055 } 1056 } 1057 1058 // Look in archives already scheduled for install 1059 1060 for (ArchiveInfo ai : outArchives) { 1061 Archive a = ai.getNewArchive(); 1062 if (a != null) { 1063 Package p = a.getParentPackage(); 1064 if (p instanceof PlatformPackage) { 1065 if (((PlatformPackage) p).getVersion().equals(api)) { 1066 return ai; 1067 } 1068 } 1069 } 1070 } 1071 1072 // Otherwise look in the selected archives. 1073 if (selectedArchives != null) { 1074 for (Archive a : selectedArchives) { 1075 Package p = a.getParentPackage(); 1076 if (p instanceof PlatformPackage) { 1077 if (((PlatformPackage) p).getVersion().equals(api)) { 1078 // It's not already in the list of things to install, so add it now 1079 return insertArchive(a, 1080 outArchives, 1081 selectedArchives, 1082 remotePkgs, 1083 remoteSources, 1084 localArchives, 1085 true /*automated*/); 1086 } 1087 } 1088 } 1089 } 1090 1091 // Finally nothing matched, so let's look at all available remote packages 1092 fetchRemotePackages(remotePkgs, remoteSources); 1093 for (Package p : remotePkgs) { 1094 if (p instanceof PlatformPackage) { 1095 if (((PlatformPackage) p).getVersion().equals(api)) { 1096 // It's not already in the list of things to install, so add the 1097 // first compatible archive we can find. 1098 for (Archive a : p.getArchives()) { 1099 if (a.isCompatible()) { 1100 return insertArchive(a, 1101 outArchives, 1102 selectedArchives, 1103 remotePkgs, 1104 remoteSources, 1105 localArchives, 1106 true /*automated*/); 1107 } 1108 } 1109 } 1110 } 1111 } 1112 1113 // We end up here if nothing matches. We don't have a good platform to match. 1114 // We need to indicate this extra depends on a missing platform archive 1115 // so that it can be impossible to install later on. 1116 return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/)); 1117 } 1118 1119 /** 1120 * Fetch all remote packages only if really needed. 1121 * <p/> 1122 * This method takes a list of sources. Each source is only fetched once -- that is each 1123 * source keeps the list of packages that we fetched from the remote XML file. If the list 1124 * is null, it means this source has never been fetched so we'll do it once here. Otherwise 1125 * we rely on the cached list of packages from this source. 1126 * <p/> 1127 * This method also takes a remote package list as input, which it will fill out. 1128 * If a source has already been fetched, we'll add its packages to the remote package list 1129 * if they are not already present. Otherwise, the source will be fetched and the packages 1130 * added to the list. 1131 * 1132 * @param remotePkgs An in-out list of packages available from remote sources. 1133 * This list must not be null. 1134 * It can be empty or already contain some packages. 1135 * @param remoteSources A list of available remote sources to fetch from. 1136 */ fetchRemotePackages( final Collection<Package> remotePkgs, final SdkSource[] remoteSources)1137 protected void fetchRemotePackages( 1138 final Collection<Package> remotePkgs, 1139 final SdkSource[] remoteSources) { 1140 if (remotePkgs.size() > 0) { 1141 return; 1142 } 1143 1144 // First check if there's any remote source we need to fetch. 1145 // This will bring the task window, so we rather not display it unless 1146 // necessary. 1147 boolean needsFetch = false; 1148 for (final SdkSource remoteSrc : remoteSources) { 1149 Package[] pkgs = remoteSrc.getPackages(); 1150 if (pkgs == null) { 1151 // This source has never been fetched. We'll do it below. 1152 needsFetch = true; 1153 } else { 1154 // This source has already been fetched and we know its package list. 1155 // We still need to make sure all of its packages are present in the 1156 // remotePkgs list. 1157 1158 nextPackage: for (Package pkg : pkgs) { 1159 for (Archive a : pkg.getArchives()) { 1160 // Only add a package if it contains at least one compatible archive 1161 // and is not already in the remote package list. 1162 if (a.isCompatible()) { 1163 if (!remotePkgs.contains(pkg)) { 1164 remotePkgs.add(pkg); 1165 continue nextPackage; 1166 } 1167 } 1168 } 1169 } 1170 } 1171 } 1172 1173 if (!needsFetch) { 1174 return; 1175 } 1176 1177 final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); 1178 1179 mUpdaterData.getTaskFactory().start("Refresh Sources", new ITask() { 1180 public void run(ITaskMonitor monitor) { 1181 for (SdkSource remoteSrc : remoteSources) { 1182 Package[] pkgs = remoteSrc.getPackages(); 1183 1184 if (pkgs == null) { 1185 remoteSrc.load(monitor, forceHttp); 1186 pkgs = remoteSrc.getPackages(); 1187 } 1188 1189 if (pkgs != null) { 1190 nextPackage: for (Package pkg : pkgs) { 1191 for (Archive a : pkg.getArchives()) { 1192 // Only add a package if it contains at least one compatible archive 1193 // and is not already in the remote package list. 1194 if (a.isCompatible()) { 1195 if (!remotePkgs.contains(pkg)) { 1196 remotePkgs.add(pkg); 1197 continue nextPackage; 1198 } 1199 } 1200 } 1201 } 1202 } 1203 } 1204 } 1205 }); 1206 } 1207 1208 1209 /** 1210 * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed 1211 * "local" package/archive. 1212 * <p/> 1213 * In this case, the "new Archive" is still expected to be non null and the 1214 * "replaced Archive" is null. Installed archives are always accepted and never 1215 * rejected. 1216 * <p/> 1217 * Dependencies are not set. 1218 */ 1219 private static class LocalArchiveInfo extends ArchiveInfo { 1220 LocalArchiveInfo(Archive localArchive)1221 public LocalArchiveInfo(Archive localArchive) { 1222 super(localArchive, null /*replaced*/, null /*dependsOn*/); 1223 } 1224 1225 /** Installed archives are always accepted. */ 1226 @Override isAccepted()1227 public boolean isAccepted() { 1228 return true; 1229 } 1230 1231 /** Installed archives are never rejected. */ 1232 @Override isRejected()1233 public boolean isRejected() { 1234 return false; 1235 } 1236 } 1237 1238 /** 1239 * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a 1240 * package/archive that we <em>really</em> need as a dependency but that we don't have. 1241 * <p/> 1242 * This is currently used for addons and extras in case we can't find a matching base platform. 1243 * <p/> 1244 * This kind of archive has specific properties: the new archive to install is null, 1245 * there are no dependencies and no archive is being replaced. The info can never be 1246 * accepted and is always rejected. 1247 */ 1248 private static class MissingPlatformArchiveInfo extends ArchiveInfo { 1249 1250 private final AndroidVersion mVersion; 1251 1252 /** 1253 * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the 1254 * given platform version is missing. 1255 */ MissingPlatformArchiveInfo(AndroidVersion version)1256 public MissingPlatformArchiveInfo(AndroidVersion version) { 1257 super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/); 1258 mVersion = version; 1259 } 1260 1261 /** Missing archives are never accepted. */ 1262 @Override isAccepted()1263 public boolean isAccepted() { 1264 return false; 1265 } 1266 1267 /** Missing archives are always rejected. */ 1268 @Override isRejected()1269 public boolean isRejected() { 1270 return true; 1271 } 1272 1273 @Override getShortDescription()1274 public String getShortDescription() { 1275 return String.format("Missing SDK Platform Android%1$s, API %2$d", 1276 mVersion.isPreview() ? " Preview" : "", 1277 mVersion.getApiLevel()); 1278 } 1279 } 1280 1281 /** 1282 * A {@link MissingArchiveInfo} is an {@link ArchiveInfo} that represents a 1283 * package/archive that we <em>really</em> need as a dependency but that we don't have. 1284 * <p/> 1285 * This is currently used for extras in case we can't find a matching tool revision 1286 * or when a platform-tool is missing. 1287 * <p/> 1288 * This kind of archive has specific properties: the new archive to install is null, 1289 * there are no dependencies and no archive is being replaced. The info can never be 1290 * accepted and is always rejected. 1291 */ 1292 private static class MissingArchiveInfo extends ArchiveInfo { 1293 1294 private final int mRevision; 1295 private final String mTitle; 1296 1297 public static final String TITLE_TOOL = "Tools"; 1298 public static final String TITLE_PLATFORM_TOOL = "Platform-tools"; 1299 1300 /** 1301 * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the 1302 * given platform version is missing. 1303 * 1304 * @param title Typically "Tools" or "Platform-tools". 1305 * @param revision The required revision. 1306 */ MissingArchiveInfo(String title, int revision)1307 public MissingArchiveInfo(String title, int revision) { 1308 super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/); 1309 mTitle = title; 1310 mRevision = revision; 1311 } 1312 1313 /** Missing archives are never accepted. */ 1314 @Override isAccepted()1315 public boolean isAccepted() { 1316 return false; 1317 } 1318 1319 /** Missing archives are always rejected. */ 1320 @Override isRejected()1321 public boolean isRejected() { 1322 return true; 1323 } 1324 1325 @Override getShortDescription()1326 public String getShortDescription() { 1327 return String.format("Missing Android SDK %1$s, revision %2$d", mTitle, mRevision); 1328 } 1329 } 1330 } 1331