1 /* 2 * Copyright (C) 2011 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.sdkman2; 18 19 import com.android.sdklib.AndroidVersion; 20 import com.android.sdklib.IAndroidTarget; 21 import com.android.sdklib.SdkConstants; 22 import com.android.sdklib.internal.repository.packages.ExtraPackage; 23 import com.android.sdklib.internal.repository.packages.IPackageVersion; 24 import com.android.sdklib.internal.repository.packages.Package; 25 import com.android.sdklib.internal.repository.packages.PlatformPackage; 26 import com.android.sdklib.internal.repository.packages.PlatformToolPackage; 27 import com.android.sdklib.internal.repository.packages.SystemImagePackage; 28 import com.android.sdklib.internal.repository.packages.ToolPackage; 29 import com.android.sdklib.internal.repository.sources.SdkSource; 30 import com.android.sdklib.util.SparseArray; 31 import com.android.sdkuilib.internal.repository.UpdaterData; 32 import com.android.sdkuilib.internal.repository.sdkman2.PkgItem.PkgState; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Helper class that separates the logic of package management from the UI 46 * so that we can test it using head-less unit tests. 47 */ 48 class PackagesDiffLogic { 49 private final PackageLoader mPackageLoader; 50 private final UpdaterData mUpdaterData; 51 private boolean mFirstLoadComplete = true; 52 53 public PackagesDiffLogic(UpdaterData updaterData) { 54 mUpdaterData = updaterData; 55 mPackageLoader = new PackageLoader(updaterData); 56 } 57 58 public PackageLoader getPackageLoader() { 59 return mPackageLoader; 60 } 61 62 /** 63 * Removes all the internal state and resets the object. 64 * Useful for testing. 65 */ 66 public void clear() { 67 mFirstLoadComplete = true; 68 mOpApi.clear(); 69 mOpSource.clear(); 70 } 71 72 /** Return mFirstLoadComplete and resets it to false. 73 * All following calls will returns false. */ 74 public boolean isFirstLoadComplete() { 75 boolean b = mFirstLoadComplete; 76 mFirstLoadComplete = false; 77 return b; 78 } 79 80 /** 81 * Mark all new and update PkgItems as checked. 82 * 83 * @param selectNew If true, select all new packages 84 * @param selectUpdates If true, select all update packages 85 * @param selectTop If true, select the top platform. If the top platform has nothing installed, 86 * select all items in it; if it is partially installed, at least select the platform and 87 * system images if none of the system images are installed. 88 * @param currentPlatform The {@link SdkConstants#currentPlatform()} value. 89 */ 90 public void checkNewUpdateItems( 91 boolean selectNew, 92 boolean selectUpdates, 93 boolean selectTop, 94 int currentPlatform) { 95 int maxApi = 0; 96 Set<Integer> installedPlatforms = new HashSet<Integer>(); 97 SparseArray<List<PkgItem>> platformItems = new SparseArray<List<PkgItem>>(); 98 99 // sort items in platforms... directly deal with new/update items 100 for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) { 101 if (!item.hasCompatibleArchive()) { 102 // Ignore items that have no archive compatible with the current platform. 103 continue; 104 } 105 106 // Get the main package's API level. We don't need to look at the updates 107 // since by definition they should target the same API level. 108 int api = 0; 109 Package p = item.getMainPackage(); 110 if (p instanceof IPackageVersion) { 111 api = ((IPackageVersion) p).getVersion().getApiLevel(); 112 } 113 114 if (selectTop && api > 0) { 115 // Keep track of the max api seen 116 maxApi = Math.max(maxApi, api); 117 118 // keep track of what platform is currently installed (that is, has at least 119 // one thing installed.) 120 if (item.getState() == PkgState.INSTALLED) { 121 installedPlatforms.add(api); 122 } 123 124 // for each platform, collect all its related item for later use below. 125 List<PkgItem> items = platformItems.get(api); 126 if (items == null) { 127 platformItems.put(api, items = new ArrayList<PkgItem>()); 128 } 129 items.add(item); 130 } 131 132 if ((selectNew && item.getState() == PkgState.NEW) || 133 (selectUpdates && item.hasUpdatePkg())) { 134 item.setChecked(true); 135 } 136 } 137 138 List<PkgItem> items = platformItems.get(maxApi); 139 if (selectTop && maxApi > 0 && items != null) { 140 if (!installedPlatforms.contains(maxApi)) { 141 // If the top platform has nothing installed at all, select everything in it 142 for (PkgItem item : items) { 143 if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) { 144 item.setChecked(true); 145 } 146 } 147 148 } else { 149 // The top platform has at least one thing installed. 150 151 // First make sure the platform package itself is installed, or select it. 152 for (PkgItem item : items) { 153 Package p = item.getMainPackage(); 154 if (p instanceof PlatformPackage && item.getState() == PkgState.NEW) { 155 item.setChecked(true); 156 break; 157 } 158 } 159 160 // Check we have at least one system image installed, otherwise select them 161 boolean hasSysImg = false; 162 for (PkgItem item : items) { 163 Package p = item.getMainPackage(); 164 if (p instanceof PlatformPackage && item.getState() == PkgState.INSTALLED) { 165 if (item.hasUpdatePkg() && item.isChecked()) { 166 // If the installed platform is schedule for update, look for the 167 // system image in the update package, not the current one. 168 p = item.getUpdatePkg(); 169 if (p instanceof PlatformPackage) { 170 hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null; 171 } 172 } else { 173 // Otherwise look into the currently installed platform 174 hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null; 175 } 176 if (hasSysImg) { 177 break; 178 } 179 } 180 if (p instanceof SystemImagePackage && item.getState() == PkgState.INSTALLED) { 181 hasSysImg = true; 182 break; 183 } 184 } 185 if (!hasSysImg) { 186 // No system image installed. 187 // Try whether the current platform or its update would bring one. 188 189 for (PkgItem item : items) { 190 Package p = item.getMainPackage(); 191 if (p instanceof PlatformPackage) { 192 if (item.getState() == PkgState.NEW && 193 ((PlatformPackage) p).getIncludedAbi() != null) { 194 item.setChecked(true); 195 hasSysImg = true; 196 } else if (item.hasUpdatePkg()) { 197 p = item.getUpdatePkg(); 198 if (p instanceof PlatformPackage && 199 ((PlatformPackage) p).getIncludedAbi() != null) { 200 item.setChecked(true); 201 hasSysImg = true; 202 } 203 } 204 } 205 } 206 } 207 if (!hasSysImg) { 208 // No system image in the platform, try a system image package 209 for (PkgItem item : items) { 210 Package p = item.getMainPackage(); 211 if (p instanceof SystemImagePackage && item.getState() == PkgState.NEW) { 212 item.setChecked(true); 213 } 214 } 215 } 216 } 217 } 218 219 if (selectTop && currentPlatform == SdkConstants.PLATFORM_WINDOWS) { 220 // On Windows, we'll also auto-select the USB driver 221 for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) { 222 Package p = item.getMainPackage(); 223 if (p instanceof ExtraPackage && item.getState() == PkgState.NEW) { 224 ExtraPackage ep = (ExtraPackage) p; 225 if (ep.getVendorId().equals("google") && //$NON-NLS-1$ 226 ep.getPath().equals("usb_driver")) { //$NON-NLS-1$ 227 item.setChecked(true); 228 } 229 } 230 } 231 } 232 } 233 234 /** 235 * Mark all PkgItems as not checked. 236 */ 237 public void uncheckAllItems() { 238 for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) { 239 item.setChecked(false); 240 } 241 } 242 243 /** 244 * An update operation, customized to either sort by API or sort by source. 245 */ 246 abstract class UpdateOp { 247 private final Set<SdkSource> mVisitedSources = new HashSet<SdkSource>(); 248 private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>(); 249 private final Set<PkgCategory> mCatsToRemove = new HashSet<PkgCategory>(); 250 private final Set<PkgItem> mItemsToRemove = new HashSet<PkgItem>(); 251 private final Map<Package, PkgItem> mUpdatesToRemove = new HashMap<Package, PkgItem>(); 252 253 /** Removes all internal state. */ 254 public void clear() { 255 mVisitedSources.clear(); 256 mCategories.clear(); 257 } 258 259 /** Retrieve the sorted category list. */ 260 public List<PkgCategory> getCategories() { 261 return mCategories; 262 } 263 264 /** Retrieve the category key for the given package, either local or remote. */ 265 public abstract Object getCategoryKey(Package pkg); 266 267 /** Modified {@code currentCategories} to add default categories. */ 268 public abstract void addDefaultCategories(); 269 270 /** Creates the category for the given key and returns it. */ 271 public abstract PkgCategory createCategory(Object catKey); 272 /** Adjust attributes of an existing category. */ 273 public abstract void adjustCategory(PkgCategory cat, Object catKey); 274 275 /** Sorts the category list (but not the items within the categories.) */ 276 public abstract void sortCategoryList(); 277 278 /** Called after items of a given category have changed. Used to sort the 279 * items and/or adjust the category name. */ 280 public abstract void postCategoryItemsChanged(); 281 282 public void updateStart() { 283 mVisitedSources.clear(); 284 285 // Note that default categories are created after the unused ones so that 286 // the callback can decide whether they should be marked as unused or not. 287 mCatsToRemove.clear(); 288 mItemsToRemove.clear(); 289 mUpdatesToRemove.clear(); 290 for (PkgCategory cat : mCategories) { 291 mCatsToRemove.add(cat); 292 List<PkgItem> items = cat.getItems(); 293 mItemsToRemove.addAll(items); 294 for (PkgItem item : items) { 295 if (item.hasUpdatePkg()) { 296 mUpdatesToRemove.put(item.getUpdatePkg(), item); 297 } 298 } 299 } 300 301 addDefaultCategories(); 302 } 303 304 public boolean updateSourcePackages(SdkSource source, Package[] newPackages) { 305 mVisitedSources.add(source); 306 if (source == null) { 307 return processLocals(this, newPackages); 308 } else { 309 return processSource(this, source, newPackages); 310 } 311 } 312 313 public boolean updateEnd() { 314 boolean hasChanged = false; 315 316 // Remove unused categories & items at the end of the update 317 synchronized (mCategories) { 318 for (PkgCategory unusedCat : mCatsToRemove) { 319 if (mCategories.remove(unusedCat)) { 320 hasChanged = true; 321 } 322 } 323 } 324 325 for (PkgCategory cat : mCategories) { 326 for (Iterator<PkgItem> itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) { 327 PkgItem item = itemIt.next(); 328 if (mItemsToRemove.contains(item)) { 329 itemIt.remove(); 330 hasChanged = true; 331 } else if (item.hasUpdatePkg() && 332 mUpdatesToRemove.containsKey(item.getUpdatePkg())) { 333 item.removeUpdate(); 334 hasChanged = true; 335 } 336 } 337 } 338 339 mCatsToRemove.clear(); 340 mItemsToRemove.clear(); 341 mUpdatesToRemove.clear(); 342 343 return hasChanged; 344 } 345 346 public boolean isKeep(PkgItem item) { 347 return !mItemsToRemove.contains(item); 348 } 349 350 public void keep(Package pkg) { 351 mUpdatesToRemove.remove(pkg); 352 } 353 354 public void keep(PkgItem item) { 355 mItemsToRemove.remove(item); 356 } 357 358 public void keep(PkgCategory cat) { 359 mCatsToRemove.remove(cat); 360 } 361 362 public void dontKeep(PkgItem item) { 363 mItemsToRemove.add(item); 364 } 365 366 public void dontKeep(PkgCategory cat) { 367 mCatsToRemove.add(cat); 368 } 369 } 370 371 private final UpdateOpApi mOpApi = new UpdateOpApi(); 372 private final UpdateOpSource mOpSource = new UpdateOpSource(); 373 374 public List<PkgCategory> getCategories(boolean displayIsSortByApi) { 375 return displayIsSortByApi ? mOpApi.getCategories() : mOpSource.getCategories(); 376 } 377 378 public List<PkgItem> getAllPkgItems(boolean byApi, boolean bySource) { 379 List<PkgItem> items = new ArrayList<PkgItem>(); 380 381 if (byApi) { 382 List<PkgCategory> cats = getCategories(true /*displayIsSortByApi*/); 383 synchronized (cats) { 384 for (PkgCategory cat : cats) { 385 items.addAll(cat.getItems()); 386 } 387 } 388 } 389 390 if (bySource) { 391 List<PkgCategory> cats = getCategories(false /*displayIsSortByApi*/); 392 synchronized (cats) { 393 for (PkgCategory cat : cats) { 394 items.addAll(cat.getItems()); 395 } 396 } 397 } 398 399 return items; 400 } 401 402 public void updateStart() { 403 mOpApi.updateStart(); 404 mOpSource.updateStart(); 405 } 406 407 public boolean updateSourcePackages( 408 boolean displayIsSortByApi, 409 SdkSource source, 410 Package[] newPackages) { 411 412 boolean apiListChanged = mOpApi.updateSourcePackages(source, newPackages); 413 boolean sourceListChanged = mOpSource.updateSourcePackages(source, newPackages); 414 return displayIsSortByApi ? apiListChanged : sourceListChanged; 415 } 416 417 public boolean updateEnd(boolean displayIsSortByApi) { 418 boolean apiListChanged = mOpApi.updateEnd(); 419 boolean sourceListChanged = mOpSource.updateEnd(); 420 return displayIsSortByApi ? apiListChanged : sourceListChanged; 421 } 422 423 424 /** Process all local packages. Returns true if something changed. */ 425 private boolean processLocals(UpdateOp op, Package[] packages) { 426 boolean hasChanged = false; 427 List<PkgCategory> cats = op.getCategories(); 428 Set<PkgItem> keep = new HashSet<PkgItem>(); 429 430 // For all locally installed packages, check they are either listed 431 // as installed or create new installed items for them. 432 433 nextPkg: for (Package localPkg : packages) { 434 // Check to see if we already have the exact same package 435 // (type & revision) marked as installed. 436 for (PkgCategory cat : cats) { 437 for (PkgItem currItem : cat.getItems()) { 438 if (currItem.getState() == PkgState.INSTALLED && 439 currItem.isSameMainPackageAs(localPkg)) { 440 // This package is already listed as installed. 441 op.keep(currItem); 442 op.keep(cat); 443 keep.add(currItem); 444 continue nextPkg; 445 } 446 } 447 } 448 449 // If not found, create a new installed package item 450 keep.add(addNewItem(op, localPkg, PkgState.INSTALLED)); 451 hasChanged = true; 452 } 453 454 // Remove installed items that we don't want to keep anymore. They would normally be 455 // cleanup up in UpdateOp.updateEnd(); however it's easier to remove them before we 456 // run processSource() to avoid merging updates in items that would be removed later. 457 458 for (PkgCategory cat : cats) { 459 for (Iterator<PkgItem> itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) { 460 PkgItem item = itemIt.next(); 461 if (item.getState() == PkgState.INSTALLED && !keep.contains(item)) { 462 itemIt.remove(); 463 hasChanged = true; 464 } 465 } 466 } 467 468 if (hasChanged) { 469 op.postCategoryItemsChanged(); 470 } 471 472 return hasChanged; 473 } 474 475 /** 476 * {@link PkgState}s to check in {@link #processSource(UpdateOp, SdkSource, Package[])}. 477 * The order matters. 478 * When installing the diff will have both the new and the installed item and we 479 * need to merge with the installed one before the new one. 480 */ 481 private final static PkgState[] PKG_STATES = { PkgState.INSTALLED, PkgState.NEW }; 482 483 /** Process all remote packages. Returns true if something changed. */ 484 private boolean processSource(UpdateOp op, SdkSource source, Package[] packages) { 485 boolean hasChanged = false; 486 List<PkgCategory> cats = op.getCategories(); 487 488 nextPkg: for (Package newPkg : packages) { 489 for (PkgCategory cat : cats) { 490 for (PkgState state : PKG_STATES) { 491 for (Iterator<PkgItem> currItemIt = cat.getItems().iterator(); 492 currItemIt.hasNext(); ) { 493 PkgItem currItem = currItemIt.next(); 494 // We need to merge with installed items first. When installing 495 // the diff will have both the new and the installed item and we 496 // need to merge with the installed one before the new one. 497 if (currItem.getState() != state) { 498 continue; 499 } 500 // Only process current items if they represent the same item (but 501 // with a different revision number) than the new package. 502 Package mainPkg = currItem.getMainPackage(); 503 if (!mainPkg.sameItemAs(newPkg)) { 504 continue; 505 } 506 507 // Check to see if we already have the exact same package 508 // (type & revision) marked as main or update package. 509 if (currItem.isSameMainPackageAs(newPkg)) { 510 op.keep(currItem); 511 op.keep(cat); 512 continue nextPkg; 513 } else if (currItem.hasUpdatePkg() && 514 currItem.isSameUpdatePackageAs(newPkg)) { 515 op.keep(currItem.getUpdatePkg()); 516 op.keep(cat); 517 continue nextPkg; 518 } 519 520 switch (currItem.getState()) { 521 case NEW: 522 if (newPkg.getRevision() < mainPkg.getRevision()) { 523 if (!op.isKeep(currItem)) { 524 // The new item has a lower revision than the current one, 525 // but the current one hasn't been marked as being kept so 526 // it's ok to downgrade it. 527 currItemIt.remove(); 528 addNewItem(op, newPkg, PkgState.NEW); 529 hasChanged = true; 530 } 531 } else if (newPkg.getRevision() > mainPkg.getRevision()) { 532 // We have a more recent new version, remove the current one 533 // and replace by a new one 534 currItemIt.remove(); 535 addNewItem(op, newPkg, PkgState.NEW); 536 hasChanged = true; 537 } 538 break; 539 case INSTALLED: 540 // if newPkg.revision<=mainPkg.revision: it's already installed, ignore. 541 if (newPkg.getRevision() > mainPkg.getRevision()) { 542 // This is a new update for the main package. 543 if (currItem.mergeUpdate(newPkg)) { 544 op.keep(currItem.getUpdatePkg()); 545 op.keep(cat); 546 hasChanged = true; 547 } 548 } 549 break; 550 } 551 continue nextPkg; 552 } 553 } 554 } 555 // If not found, create a new package item 556 addNewItem(op, newPkg, PkgState.NEW); 557 hasChanged = true; 558 } 559 560 if (hasChanged) { 561 op.postCategoryItemsChanged(); 562 } 563 564 return hasChanged; 565 } 566 567 private PkgItem addNewItem(UpdateOp op, Package pkg, PkgState state) { 568 List<PkgCategory> cats = op.getCategories(); 569 Object catKey = op.getCategoryKey(pkg); 570 PkgCategory cat = findCurrentCategory(cats, catKey); 571 572 if (cat == null) { 573 // This is a new category. Create it and add it to the list. 574 cat = op.createCategory(catKey); 575 synchronized (cats) { 576 cats.add(cat); 577 } 578 op.sortCategoryList(); 579 } else { 580 // Not a new category. Give op a chance to adjust the category attributes 581 op.adjustCategory(cat, catKey); 582 } 583 584 PkgItem item = new PkgItem(pkg, state); 585 op.keep(item); 586 cat.getItems().add(item); 587 op.keep(cat); 588 return item; 589 } 590 591 private PkgCategory findCurrentCategory( 592 List<PkgCategory> currentCategories, 593 Object categoryKey) { 594 for (PkgCategory cat : currentCategories) { 595 if (cat.getKey().equals(categoryKey)) { 596 return cat; 597 } 598 } 599 return null; 600 } 601 602 /** 603 * {@link UpdateOp} describing the Sort-by-API operation. 604 */ 605 private class UpdateOpApi extends UpdateOp { 606 @Override 607 public Object getCategoryKey(Package pkg) { 608 // Sort by API 609 610 if (pkg instanceof IPackageVersion) { 611 return ((IPackageVersion) pkg).getVersion(); 612 613 } else if (pkg instanceof ToolPackage || pkg instanceof PlatformToolPackage) { 614 return PkgCategoryApi.KEY_TOOLS; 615 616 } else { 617 return PkgCategoryApi.KEY_EXTRA; 618 } 619 } 620 621 @Override 622 public void addDefaultCategories() { 623 boolean needTools = true; 624 boolean needExtras = true; 625 626 List<PkgCategory> cats = getCategories(); 627 for (PkgCategory cat : cats) { 628 if (cat.getKey().equals(PkgCategoryApi.KEY_TOOLS)) { 629 // Mark them as no unused to prevent their removal in updateEnd(). 630 keep(cat); 631 needTools = false; 632 } else if (cat.getKey().equals(PkgCategoryApi.KEY_EXTRA)) { 633 keep(cat); 634 needExtras = false; 635 } 636 } 637 638 // Always add the tools & extras categories, even if empty (unlikely anyway) 639 if (needTools) { 640 PkgCategoryApi acat = new PkgCategoryApi( 641 PkgCategoryApi.KEY_TOOLS, 642 null, 643 mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_OTHER)); 644 synchronized (cats) { 645 cats.add(acat); 646 } 647 } 648 649 if (needExtras) { 650 PkgCategoryApi acat = new PkgCategoryApi( 651 PkgCategoryApi.KEY_EXTRA, 652 null, 653 mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_OTHER)); 654 synchronized (cats) { 655 cats.add(acat); 656 } 657 } 658 } 659 660 @Override 661 public PkgCategory createCategory(Object catKey) { 662 // Create API category. 663 PkgCategory cat = null; 664 665 assert catKey instanceof AndroidVersion; 666 AndroidVersion key = (AndroidVersion) catKey; 667 668 // We should not be trying to recreate the tools or extra categories. 669 assert !key.equals(PkgCategoryApi.KEY_TOOLS) && !key.equals(PkgCategoryApi.KEY_EXTRA); 670 671 // We need a label for the category. 672 // If we have an API level, try to get the info from the SDK Manager. 673 // If we don't (e.g. when installing a new platform that isn't yet available 674 // locally in the SDK Manager), it's OK we'll try to find the first platform 675 // package available. 676 String platformName = null; 677 for (IAndroidTarget target : 678 mUpdaterData.getSdkManager().getTargets()) { 679 if (target.isPlatform() && key.equals(target.getVersion())) { 680 platformName = target.getVersionName(); 681 break; 682 } 683 } 684 685 cat = new PkgCategoryApi( 686 key, 687 platformName, 688 mUpdaterData.getImageFactory().getImageByName(PackagesPage.ICON_CAT_PLATFORM)); 689 690 return cat; 691 } 692 693 @Override 694 public void adjustCategory(PkgCategory cat, Object catKey) { 695 // Pass. Nothing to do for API-sorted categories 696 } 697 698 @Override 699 public void sortCategoryList() { 700 // Sort the categories list. 701 // We always want categories in order tools..platforms..extras. 702 // For platform, we compare in descending order (o2-o1). 703 // This order is achieved by having the category keys ordered as 704 // needed for the sort to just do what we expect. 705 706 synchronized (getCategories()) { 707 Collections.sort(getCategories(), new Comparator<PkgCategory>() { 708 @Override 709 public int compare(PkgCategory cat1, PkgCategory cat2) { 710 assert cat1 instanceof PkgCategoryApi; 711 assert cat2 instanceof PkgCategoryApi; 712 assert cat1.getKey() instanceof AndroidVersion; 713 assert cat2.getKey() instanceof AndroidVersion; 714 AndroidVersion v1 = (AndroidVersion) cat1.getKey(); 715 AndroidVersion v2 = (AndroidVersion) cat2.getKey(); 716 return v2.compareTo(v1); 717 } 718 }); 719 } 720 } 721 722 @Override 723 public void postCategoryItemsChanged() { 724 // Sort the items 725 for (PkgCategory cat : getCategories()) { 726 Collections.sort(cat.getItems()); 727 728 // When sorting by API, we can't always get the platform name 729 // from the package manager. In this case at the very end we 730 // look for a potential platform package we can use to extract 731 // the platform version name (e.g. '1.5') from the first suitable 732 // platform package we can find. 733 734 assert cat instanceof PkgCategoryApi; 735 PkgCategoryApi pac = (PkgCategoryApi) cat; 736 if (pac.getPlatformName() == null) { 737 // Check whether we can get the actual platform version name (e.g. "1.5") 738 // from the first Platform package we find in this category. 739 740 for (PkgItem item : cat.getItems()) { 741 Package p = item.getMainPackage(); 742 if (p instanceof PlatformPackage) { 743 String platformName = ((PlatformPackage) p).getVersionName(); 744 if (platformName != null) { 745 pac.setPlatformName(platformName); 746 break; 747 } 748 } 749 } 750 } 751 } 752 753 } 754 } 755 756 /** 757 * {@link UpdateOp} describing the Sort-by-Source operation. 758 */ 759 private class UpdateOpSource extends UpdateOp { 760 761 @Override 762 public boolean updateSourcePackages(SdkSource source, Package[] newPackages) { 763 // When displaying the repo by source, we want to create all the 764 // categories so that they can appear on the UI even if empty. 765 if (source != null) { 766 List<PkgCategory> cats = getCategories(); 767 Object catKey = source; 768 PkgCategory cat = findCurrentCategory(cats, catKey); 769 770 if (cat == null) { 771 // This is a new category. Create it and add it to the list. 772 cat = createCategory(catKey); 773 synchronized (cats) { 774 cats.add(cat); 775 } 776 sortCategoryList(); 777 } 778 779 keep(cat); 780 } 781 782 return super.updateSourcePackages(source, newPackages); 783 } 784 785 @Override 786 public Object getCategoryKey(Package pkg) { 787 // Sort by source 788 SdkSource source = pkg.getParentSource(); 789 if (source == null) { 790 return PkgCategorySource.UNKNOWN_SOURCE; 791 } 792 return source; 793 } 794 795 @Override 796 public void addDefaultCategories() { 797 List<PkgCategory> cats = getCategories(); 798 for (PkgCategory cat : cats) { 799 if (cat.getKey().equals(PkgCategorySource.UNKNOWN_SOURCE)) { 800 // Already present. 801 return; 802 } 803 } 804 805 // Always add the local categories, even if empty (unlikely anyway) 806 PkgCategorySource cat = new PkgCategorySource( 807 PkgCategorySource.UNKNOWN_SOURCE, 808 mUpdaterData); 809 // Mark it so that it can be cleared in updateEnd() if not used. 810 dontKeep(cat); 811 synchronized (cats) { 812 cats.add(cat); 813 } 814 } 815 816 /** 817 * Create a new source category. 818 * <p/> 819 * One issue is that local archives are processed first and we don't have the 820 * full source information on them (e.g. we know the referral URL but not 821 * the referral name of the site). 822 * In this case this will just create {@link PkgCategorySource} where the label isn't 823 * known yet. 824 */ 825 @Override 826 public PkgCategory createCategory(Object catKey) { 827 assert catKey instanceof SdkSource; 828 PkgCategory cat = new PkgCategorySource((SdkSource) catKey, mUpdaterData); 829 return cat; 830 } 831 832 /** 833 * Checks whether the category needs to be adjust. 834 * As mentioned in {@link #createCategory(Object)}, local archives are processed 835 * first and result in a {@link PkgCategorySource} where the label isn't known. 836 * Once we process the external source with the actual name, we'll update it. 837 */ 838 @Override 839 public void adjustCategory(PkgCategory cat, Object catKey) { 840 assert cat instanceof PkgCategorySource; 841 assert catKey instanceof SdkSource; 842 if (cat instanceof PkgCategorySource) { 843 ((PkgCategorySource) cat).adjustLabel((SdkSource) catKey); 844 } 845 } 846 847 @Override 848 public void sortCategoryList() { 849 // Sort the sources in ascending source name order, 850 // with the local packages always first. 851 852 synchronized (getCategories()) { 853 Collections.sort(getCategories(), new Comparator<PkgCategory>() { 854 @Override 855 public int compare(PkgCategory cat1, PkgCategory cat2) { 856 assert cat1 instanceof PkgCategorySource; 857 assert cat2 instanceof PkgCategorySource; 858 859 SdkSource src1 = ((PkgCategorySource) cat1).getSource(); 860 SdkSource src2 = ((PkgCategorySource) cat2).getSource(); 861 862 if (src1 == src2) { 863 return 0; 864 } else if (src1 == PkgCategorySource.UNKNOWN_SOURCE) { 865 return -1; 866 } else if (src2 == PkgCategorySource.UNKNOWN_SOURCE) { 867 return 1; 868 } 869 assert src1 != null; // true because LOCAL_SOURCE==null 870 assert src2 != null; 871 return src1.toString().compareTo(src2.toString()); 872 } 873 }); 874 } 875 } 876 877 @Override 878 public void postCategoryItemsChanged() { 879 // Sort the items 880 for (PkgCategory cat : getCategories()) { 881 Collections.sort(cat.getItems()); 882 } 883 } 884 } 885 } 886