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.IMinApiLevelDependency; 25 import com.android.sdklib.internal.repository.IMinToolsDependency; 26 import com.android.sdklib.internal.repository.IPackageVersion; 27 import com.android.sdklib.internal.repository.IPlatformDependency; 28 import com.android.sdklib.internal.repository.MinToolsPackage; 29 import com.android.sdklib.internal.repository.Package; 30 import com.android.sdklib.internal.repository.PlatformPackage; 31 import com.android.sdklib.internal.repository.RepoSource; 32 import com.android.sdklib.internal.repository.RepoSources; 33 import com.android.sdklib.internal.repository.SamplePackage; 34 import com.android.sdklib.internal.repository.ToolPackage; 35 import com.android.sdklib.internal.repository.Package.UpdateInfo; 36 37 import java.util.ArrayList; 38 import java.util.Collection; 39 import java.util.HashMap; 40 41 /** 42 * The logic to compute which packages to install, based on the choices 43 * made by the user. This adds dependent packages as needed. 44 * <p/> 45 * When the user doesn't provide a selection, looks at local package to find 46 * those that can be updated and compute dependencies too. 47 */ 48 class UpdaterLogic { 49 50 /** 51 * Compute which packages to install by taking the user selection 52 * and adding dependent packages as needed. 53 * 54 * When the user doesn't provide a selection, looks at local packages to find 55 * those that can be updated and compute dependencies too. 56 */ 57 public ArrayList<ArchiveInfo> computeUpdates( 58 Collection<Archive> selectedArchives, 59 RepoSources sources, 60 Package[] localPkgs) { 61 62 ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>(); 63 ArrayList<Package> remotePkgs = new ArrayList<Package>(); 64 RepoSource[] remoteSources = sources.getSources(); 65 66 // Create ArchiveInfos out of local (installed) packages. 67 ArchiveInfo[] localArchives = createLocalArchives(localPkgs); 68 69 if (selectedArchives == null) { 70 selectedArchives = findUpdates(localArchives, remotePkgs, remoteSources); 71 } 72 73 for (Archive a : selectedArchives) { 74 insertArchive(a, 75 archives, 76 selectedArchives, 77 remotePkgs, 78 remoteSources, 79 localArchives, 80 false /*automated*/); 81 } 82 83 return archives; 84 } 85 86 /** 87 * Finds new packages that the user does not have in his/her local SDK 88 * and adds them to the list of archives to install. 89 */ 90 public void addNewPlatforms(ArrayList<ArchiveInfo> archives, 91 RepoSources sources, 92 Package[] localPkgs) { 93 94 // Create ArchiveInfos out of local (installed) packages. 95 ArchiveInfo[] localArchives = createLocalArchives(localPkgs); 96 97 // Find the highest platform installed 98 float currentPlatformScore = 0; 99 float currentSampleScore = 0; 100 float currentAddonScore = 0; 101 float currentDocScore = 0; 102 HashMap<String, Float> currentExtraScore = new HashMap<String, Float>(); 103 for (Package p : localPkgs) { 104 int rev = p.getRevision(); 105 int api = 0; 106 boolean isPreview = false; 107 if (p instanceof IPackageVersion) { 108 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 109 api = vers.getApiLevel(); 110 isPreview = vers.isPreview(); 111 } 112 113 // The score is 10*api + (1 if preview) + rev/100 114 // This allows previews to rank above a non-preview and 115 // allows revisions to rank appropriately. 116 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 117 118 if (p instanceof PlatformPackage) { 119 currentPlatformScore = Math.max(currentPlatformScore, score); 120 } else if (p instanceof SamplePackage) { 121 currentSampleScore = Math.max(currentSampleScore, score); 122 } else if (p instanceof AddonPackage) { 123 currentAddonScore = Math.max(currentAddonScore, score); 124 } else if (p instanceof ExtraPackage) { 125 currentExtraScore.put(((ExtraPackage) p).getPath(), score); 126 } else if (p instanceof DocPackage) { 127 currentDocScore = Math.max(currentDocScore, score); 128 } 129 } 130 131 RepoSource[] remoteSources = sources.getSources(); 132 ArrayList<Package> remotePkgs = new ArrayList<Package>(); 133 fetchRemotePackages(remotePkgs, remoteSources); 134 135 Package suggestedDoc = null; 136 137 for (Package p : remotePkgs) { 138 int rev = p.getRevision(); 139 int api = 0; 140 boolean isPreview = false; 141 if (p instanceof IPackageVersion) { 142 AndroidVersion vers = ((IPackageVersion) p).getVersion(); 143 api = vers.getApiLevel(); 144 isPreview = vers.isPreview(); 145 } 146 147 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f; 148 149 boolean shouldAdd = false; 150 if (p instanceof PlatformPackage) { 151 shouldAdd = score > currentPlatformScore; 152 } else if (p instanceof SamplePackage) { 153 shouldAdd = score > currentSampleScore; 154 } else if (p instanceof AddonPackage) { 155 shouldAdd = score > currentAddonScore; 156 } else if (p instanceof ExtraPackage) { 157 String key = ((ExtraPackage) p).getPath(); 158 shouldAdd = !currentExtraScore.containsKey(key) || 159 score > currentExtraScore.get(key).floatValue(); 160 } else if (p instanceof DocPackage) { 161 // We don't want all the doc, only the most recent one 162 if (score > currentDocScore) { 163 suggestedDoc = p; 164 currentDocScore = score; 165 } 166 } 167 168 if (shouldAdd) { 169 // We should suggest this package for installation. 170 for (Archive a : p.getArchives()) { 171 if (a.isCompatible()) { 172 insertArchive(a, 173 archives, 174 null /*selectedArchives*/, 175 remotePkgs, 176 remoteSources, 177 localArchives, 178 true /*automated*/); 179 } 180 } 181 } 182 } 183 184 if (suggestedDoc != null) { 185 // We should suggest this package for installation. 186 for (Archive a : suggestedDoc.getArchives()) { 187 if (a.isCompatible()) { 188 insertArchive(a, 189 archives, 190 null /*selectedArchives*/, 191 remotePkgs, 192 remoteSources, 193 localArchives, 194 true /*automated*/); 195 } 196 } 197 } 198 } 199 200 /** 201 * Create a array of {@link ArchiveInfo} based on all local (already installed) 202 * packages. The array is always non-null but may be empty. 203 * <p/> 204 * The local {@link ArchiveInfo} are guaranteed to have one non-null archive 205 * that you can retrieve using {@link ArchiveInfo#getNewArchive()}. 206 */ 207 protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) { 208 209 if (localPkgs != null) { 210 ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>(); 211 for (Package p : localPkgs) { 212 // Only accept packages that have one compatible archive. 213 // Local package should have 1 and only 1 compatible archive anyway. 214 for (Archive a : p.getArchives()) { 215 if (a != null && a.isCompatible()) { 216 // We create an "installed" archive info to wrap the local package. 217 // Note that dependencies are not computed since right now we don't 218 // deal with more than one level of dependencies and installed archives 219 // are deemed implicitly accepted anyway. 220 list.add(new LocalArchiveInfo(a)); 221 } 222 } 223 } 224 225 return list.toArray(new ArchiveInfo[list.size()]); 226 } 227 228 return new ArchiveInfo[0]; 229 } 230 231 /** 232 * Find suitable updates to all current local packages. 233 */ 234 private Collection<Archive> findUpdates(ArchiveInfo[] localArchives, 235 ArrayList<Package> remotePkgs, 236 RepoSource[] remoteSources) { 237 ArrayList<Archive> updates = new ArrayList<Archive>(); 238 239 fetchRemotePackages(remotePkgs, remoteSources); 240 241 for (ArchiveInfo ai : localArchives) { 242 Archive na = ai.getNewArchive(); 243 if (na == null) { 244 continue; 245 } 246 Package localPkg = na.getParentPackage(); 247 248 for (Package remotePkg : remotePkgs) { 249 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) { 250 // Found a suitable update. Only accept the remote package 251 // if it provides at least one compatible archive. 252 253 for (Archive a : remotePkg.getArchives()) { 254 if (a.isCompatible()) { 255 updates.add(a); 256 break; 257 } 258 } 259 } 260 } 261 } 262 263 return updates; 264 } 265 266 private ArchiveInfo insertArchive(Archive archive, 267 ArrayList<ArchiveInfo> outArchives, 268 Collection<Archive> selectedArchives, 269 ArrayList<Package> remotePkgs, 270 RepoSource[] remoteSources, 271 ArchiveInfo[] localArchives, 272 boolean automated) { 273 Package p = archive.getParentPackage(); 274 275 // Is this an update? 276 Archive updatedArchive = null; 277 for (ArchiveInfo ai : localArchives) { 278 Archive a = ai.getNewArchive(); 279 if (a != null) { 280 Package lp = a.getParentPackage(); 281 282 if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) { 283 updatedArchive = a; 284 } 285 } 286 } 287 288 // Find dependencies 289 ArchiveInfo[] deps = findDependency(p, 290 outArchives, 291 selectedArchives, 292 remotePkgs, 293 remoteSources, 294 localArchives); 295 296 // Make sure it's not a dup 297 ArchiveInfo ai = null; 298 299 for (ArchiveInfo ai2 : outArchives) { 300 Archive a2 = ai2.getNewArchive(); 301 if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) { 302 ai = ai2; 303 break; 304 } 305 } 306 307 if (ai == null) { 308 ai = new ArchiveInfo( 309 archive, //newArchive 310 updatedArchive, //replaced 311 deps //dependsOn 312 ); 313 outArchives.add(ai); 314 } 315 316 if (deps != null) { 317 for (ArchiveInfo d : deps) { 318 d.addDependencyFor(ai); 319 } 320 } 321 322 return ai; 323 } 324 325 /** 326 * Resolves dependencies for a given package. 327 * 328 * Returns null if no dependencies were found. 329 * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have 330 * at least size 1 and contain no null elements. 331 */ 332 private ArchiveInfo[] findDependency(Package pkg, 333 ArrayList<ArchiveInfo> outArchives, 334 Collection<Archive> selectedArchives, 335 ArrayList<Package> remotePkgs, 336 RepoSource[] remoteSources, 337 ArchiveInfo[] localArchives) { 338 339 // Current dependencies can be: 340 // - addon: *always* depends on platform of same API level 341 // - platform: *might* depends on tools of rev >= min-tools-rev 342 // - extra: *might* depends on platform with api >= min-api-level 343 344 ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>(); 345 346 if (pkg instanceof IPlatformDependency) { 347 ArchiveInfo ai = findPlatformDependency( 348 (IPlatformDependency) pkg, 349 outArchives, 350 selectedArchives, 351 remotePkgs, 352 remoteSources, 353 localArchives); 354 355 if (ai != null) { 356 list.add(ai); 357 } 358 } 359 360 if (pkg instanceof IMinToolsDependency) { 361 362 ArchiveInfo ai = findToolsDependency( 363 (IMinToolsDependency) pkg, 364 outArchives, 365 selectedArchives, 366 remotePkgs, 367 remoteSources, 368 localArchives); 369 370 if (ai != null) { 371 list.add(ai); 372 } 373 } 374 375 if (pkg instanceof IMinApiLevelDependency) { 376 377 ArchiveInfo ai = findMinApiLevelDependency( 378 (IMinApiLevelDependency) pkg, 379 outArchives, 380 selectedArchives, 381 remotePkgs, 382 remoteSources, 383 localArchives); 384 385 if (ai != null) { 386 list.add(ai); 387 } 388 } 389 390 if (list.size() > 0) { 391 return list.toArray(new ArchiveInfo[list.size()]); 392 } 393 394 return null; 395 } 396 397 /** 398 * Resolves dependencies on tools. 399 * 400 * A platform or an extra package can both have a min-tools-rev, in which case it 401 * depends on having a tools package of the requested revision. 402 * Finds the tools dependency. If found, add it to the list of things to install. 403 * Returns the archive info dependency, if any. 404 */ 405 protected ArchiveInfo findToolsDependency( 406 IMinToolsDependency pkg, 407 ArrayList<ArchiveInfo> outArchives, 408 Collection<Archive> selectedArchives, 409 ArrayList<Package> remotePkgs, 410 RepoSource[] remoteSources, 411 ArchiveInfo[] localArchives) { 412 // This is the requirement to match. 413 int rev = pkg.getMinToolsRevision(); 414 415 if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) { 416 // Well actually there's no requirement. 417 return null; 418 } 419 420 // First look in locally installed packages. 421 for (ArchiveInfo ai : localArchives) { 422 Archive a = ai.getNewArchive(); 423 if (a != null) { 424 Package p = a.getParentPackage(); 425 if (p instanceof ToolPackage) { 426 if (((ToolPackage) p).getRevision() >= rev) { 427 // We found one already installed. 428 return null; 429 } 430 } 431 } 432 } 433 434 // Look in archives already scheduled for install 435 for (ArchiveInfo ai : outArchives) { 436 Archive a = ai.getNewArchive(); 437 if (a != null) { 438 Package p = a.getParentPackage(); 439 if (p instanceof ToolPackage) { 440 if (((ToolPackage) p).getRevision() >= rev) { 441 // The dependency is already scheduled for install, nothing else to do. 442 return ai; 443 } 444 } 445 } 446 } 447 448 // Otherwise look in the selected archives. 449 if (selectedArchives != null) { 450 for (Archive a : selectedArchives) { 451 Package p = a.getParentPackage(); 452 if (p instanceof ToolPackage) { 453 if (((ToolPackage) p).getRevision() >= rev) { 454 // It's not already in the list of things to install, so add it now 455 return insertArchive(a, 456 outArchives, 457 selectedArchives, 458 remotePkgs, 459 remoteSources, 460 localArchives, 461 true /*automated*/); 462 } 463 } 464 } 465 } 466 467 // Finally nothing matched, so let's look at all available remote packages 468 fetchRemotePackages(remotePkgs, remoteSources); 469 for (Package p : remotePkgs) { 470 if (p instanceof ToolPackage) { 471 if (((ToolPackage) p).getRevision() >= rev) { 472 // It's not already in the list of things to install, so add the 473 // first compatible archive we can find. 474 for (Archive a : p.getArchives()) { 475 if (a.isCompatible()) { 476 return insertArchive(a, 477 outArchives, 478 selectedArchives, 479 remotePkgs, 480 remoteSources, 481 localArchives, 482 true /*automated*/); 483 } 484 } 485 } 486 } 487 } 488 489 // We end up here if nothing matches. We don't have a good platform to match. 490 // We need to indicate this extra depends on a missing platform archive 491 // so that it can be impossible to install later on. 492 return new MissingToolArchiveInfo(rev); 493 } 494 495 /** 496 * Resolves dependencies on platform for an addon. 497 * 498 * An addon depends on having a platform with the same API level. 499 * 500 * Finds the platform dependency. If found, add it to the list of things to install. 501 * Returns the archive info dependency, if any. 502 */ 503 protected ArchiveInfo findPlatformDependency( 504 IPlatformDependency pkg, 505 ArrayList<ArchiveInfo> outArchives, 506 Collection<Archive> selectedArchives, 507 ArrayList<Package> remotePkgs, 508 RepoSource[] remoteSources, 509 ArchiveInfo[] localArchives) { 510 // This is the requirement to match. 511 AndroidVersion v = pkg.getVersion(); 512 513 // Find a platform that would satisfy the requirement. 514 515 // First look in locally installed packages. 516 for (ArchiveInfo ai : localArchives) { 517 Archive a = ai.getNewArchive(); 518 if (a != null) { 519 Package p = a.getParentPackage(); 520 if (p instanceof PlatformPackage) { 521 if (v.equals(((PlatformPackage) p).getVersion())) { 522 // We found one already installed. 523 return null; 524 } 525 } 526 } 527 } 528 529 // Look in archives already scheduled for install 530 for (ArchiveInfo ai : outArchives) { 531 Archive a = ai.getNewArchive(); 532 if (a != null) { 533 Package p = a.getParentPackage(); 534 if (p instanceof PlatformPackage) { 535 if (v.equals(((PlatformPackage) p).getVersion())) { 536 // The dependency is already scheduled for install, nothing else to do. 537 return ai; 538 } 539 } 540 } 541 } 542 543 // Otherwise look in the selected archives. 544 if (selectedArchives != null) { 545 for (Archive a : selectedArchives) { 546 Package p = a.getParentPackage(); 547 if (p instanceof PlatformPackage) { 548 if (v.equals(((PlatformPackage) p).getVersion())) { 549 // It's not already in the list of things to install, so add it now 550 return insertArchive(a, 551 outArchives, 552 selectedArchives, 553 remotePkgs, 554 remoteSources, 555 localArchives, 556 true /*automated*/); 557 } 558 } 559 } 560 } 561 562 // Finally nothing matched, so let's look at all available remote packages 563 fetchRemotePackages(remotePkgs, remoteSources); 564 for (Package p : remotePkgs) { 565 if (p instanceof PlatformPackage) { 566 if (v.equals(((PlatformPackage) p).getVersion())) { 567 // It's not already in the list of things to install, so add the 568 // first compatible archive we can find. 569 for (Archive a : p.getArchives()) { 570 if (a.isCompatible()) { 571 return insertArchive(a, 572 outArchives, 573 selectedArchives, 574 remotePkgs, 575 remoteSources, 576 localArchives, 577 true /*automated*/); 578 } 579 } 580 } 581 } 582 } 583 584 // We end up here if nothing matches. We don't have a good platform to match. 585 // We need to indicate this addon depends on a missing platform archive 586 // so that it can be impossible to install later on. 587 return new MissingPlatformArchiveInfo(pkg.getVersion()); 588 } 589 590 /** 591 * Resolves platform dependencies for extras. 592 * An extra depends on having a platform with a minimun API level. 593 * 594 * We try to return the highest API level available above the specified minimum. 595 * Note that installed packages have priority so if one installed platform satisfies 596 * the dependency, we'll use it even if there's a higher API platform available but 597 * not installed yet. 598 * 599 * Finds the platform dependency. If found, add it to the list of things to install. 600 * Returns the archive info dependency, if any. 601 */ 602 protected ArchiveInfo findMinApiLevelDependency( 603 IMinApiLevelDependency pkg, 604 ArrayList<ArchiveInfo> outArchives, 605 Collection<Archive> selectedArchives, 606 ArrayList<Package> remotePkgs, 607 RepoSource[] remoteSources, 608 ArchiveInfo[] localArchives) { 609 610 int api = pkg.getMinApiLevel(); 611 612 if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) { 613 return null; 614 } 615 616 // Find a platform that would satisfy the requirement. 617 618 // First look in locally installed packages. 619 for (ArchiveInfo ai : localArchives) { 620 Archive a = ai.getNewArchive(); 621 if (a != null) { 622 Package p = a.getParentPackage(); 623 if (p instanceof PlatformPackage) { 624 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 625 // We found one already installed. 626 return null; 627 } 628 } 629 } 630 } 631 632 // Look in archives already scheduled for install 633 int foundApi = 0; 634 ArchiveInfo foundAi = null; 635 636 for (ArchiveInfo ai : outArchives) { 637 Archive a = ai.getNewArchive(); 638 if (a != null) { 639 Package p = a.getParentPackage(); 640 if (p instanceof PlatformPackage) { 641 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 642 if (api > foundApi) { 643 foundApi = api; 644 foundAi = ai; 645 } 646 } 647 } 648 } 649 } 650 651 if (foundAi != null) { 652 // The dependency is already scheduled for install, nothing else to do. 653 return foundAi; 654 } 655 656 // Otherwise look in the selected archives *or* available remote packages 657 // and takes the best out of the two sets. 658 foundApi = 0; 659 Archive foundArchive = null; 660 if (selectedArchives != null) { 661 for (Archive a : selectedArchives) { 662 Package p = a.getParentPackage(); 663 if (p instanceof PlatformPackage) { 664 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 665 if (api > foundApi) { 666 foundApi = api; 667 foundArchive = a; 668 } 669 } 670 } 671 } 672 } 673 674 // Finally nothing matched, so let's look at all available remote packages 675 fetchRemotePackages(remotePkgs, remoteSources); 676 for (Package p : remotePkgs) { 677 if (p instanceof PlatformPackage) { 678 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) { 679 if (api > foundApi) { 680 // It's not already in the list of things to install, so add the 681 // first compatible archive we can find. 682 for (Archive a : p.getArchives()) { 683 if (a.isCompatible()) { 684 foundApi = api; 685 foundArchive = a; 686 } 687 } 688 } 689 } 690 } 691 } 692 693 if (foundArchive != null) { 694 // It's not already in the list of things to install, so add it now 695 return insertArchive(foundArchive, 696 outArchives, 697 selectedArchives, 698 remotePkgs, 699 remoteSources, 700 localArchives, 701 true /*automated*/); 702 } 703 704 // We end up here if nothing matches. We don't have a good platform to match. 705 // We need to indicate this extra depends on a missing platform archive 706 // so that it can be impossible to install later on. 707 return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/)); 708 } 709 710 /** Fetch all remote packages only if really needed. */ 711 protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) { 712 if (remotePkgs.size() > 0) { 713 return; 714 } 715 716 for (RepoSource remoteSrc : remoteSources) { 717 Package[] pkgs = remoteSrc.getPackages(); 718 if (pkgs != null) { 719 nextPackage: for (Package pkg : pkgs) { 720 for (Archive a : pkg.getArchives()) { 721 // Only add a package if it contains at least one compatible archive 722 if (a.isCompatible()) { 723 remotePkgs.add(pkg); 724 continue nextPackage; 725 } 726 } 727 } 728 } 729 } 730 } 731 732 733 /** 734 * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed 735 * "local" package/archive. 736 * <p/> 737 * In this case, the "new Archive" is still expected to be non null and the 738 * "replaced Archive" isnull. Installed archives are always accepted and never 739 * rejected. 740 * <p/> 741 * Dependencies are not set. 742 */ 743 private static class LocalArchiveInfo extends ArchiveInfo { 744 745 public LocalArchiveInfo(Archive localArchive) { 746 super(localArchive, null /*replaced*/, null /*dependsOn*/); 747 } 748 749 /** Installed archives are always accepted. */ 750 @Override 751 public boolean isAccepted() { 752 return true; 753 } 754 755 /** Installed archives are never rejected. */ 756 @Override 757 public boolean isRejected() { 758 return false; 759 } 760 } 761 762 /** 763 * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a 764 * package/archive that we <em>really</em> need as a dependency but that we don't have. 765 * <p/> 766 * This is currently used for addons and extras in case we can't find a matching base platform. 767 * <p/> 768 * This kind of archive has specific properties: the new archive to install is null, 769 * there are no dependencies and no archive is being replaced. The info can never be 770 * accepted and is always rejected. 771 */ 772 private static class MissingPlatformArchiveInfo extends ArchiveInfo { 773 774 private final AndroidVersion mVersion; 775 776 /** 777 * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the 778 * given platform version is missing. 779 */ 780 public MissingPlatformArchiveInfo(AndroidVersion version) { 781 super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/); 782 mVersion = version; 783 } 784 785 /** Missing archives are never accepted. */ 786 @Override 787 public boolean isAccepted() { 788 return false; 789 } 790 791 /** Missing archives are always rejected. */ 792 @Override 793 public boolean isRejected() { 794 return true; 795 } 796 797 @Override 798 public String getShortDescription() { 799 return String.format("Missing SDK Platform Android%1$s, API %2$d", 800 mVersion.isPreview() ? " Preview" : "", 801 mVersion.getApiLevel()); 802 } 803 } 804 805 /** 806 * A {@link MissingToolArchiveInfo} is an {@link ArchiveInfo} that represents a 807 * package/archive that we <em>really</em> need as a dependency but that we don't have. 808 * <p/> 809 * This is currently used for extras in case we can't find a matching tool revision. 810 * <p/> 811 * This kind of archive has specific properties: the new archive to install is null, 812 * there are no dependencies and no archive is being replaced. The info can never be 813 * accepted and is always rejected. 814 */ 815 private static class MissingToolArchiveInfo extends ArchiveInfo { 816 817 private final int mRevision; 818 819 /** 820 * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the 821 * given platform version is missing. 822 */ 823 public MissingToolArchiveInfo(int revision) { 824 super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/); 825 mRevision = revision; 826 } 827 828 /** Missing archives are never accepted. */ 829 @Override 830 public boolean isAccepted() { 831 return false; 832 } 833 834 /** Missing archives are always rejected. */ 835 @Override 836 public boolean isRejected() { 837 return true; 838 } 839 840 @Override 841 public String getShortDescription() { 842 return String.format("Missing Android SDK Tools, revision %1$d", mRevision); 843 } 844 } 845 } 846