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