| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.sdkuilib.internal.repository.core; |
| |
| import com.android.SdkConstants; |
| import com.android.sdklib.AndroidVersion; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.internal.repository.packages.BuildToolPackage; |
| import com.android.sdklib.internal.repository.packages.ExtraPackage; |
| import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider; |
| import com.android.sdklib.internal.repository.packages.IFullRevisionProvider; |
| import com.android.sdklib.internal.repository.packages.Package; |
| import com.android.sdklib.internal.repository.packages.Package.UpdateInfo; |
| import com.android.sdklib.internal.repository.packages.PlatformPackage; |
| import com.android.sdklib.internal.repository.packages.PlatformToolPackage; |
| import com.android.sdklib.internal.repository.packages.SystemImagePackage; |
| import com.android.sdklib.internal.repository.packages.ToolPackage; |
| import com.android.sdklib.internal.repository.sources.SdkSource; |
| import com.android.sdklib.internal.repository.updater.PkgItem; |
| import com.android.sdklib.internal.repository.updater.PkgItem.PkgState; |
| import com.android.sdklib.repository.FullRevision; |
| import com.android.sdklib.repository.FullRevision.PreviewComparison; |
| import com.android.sdkuilib.internal.repository.SwtUpdaterData; |
| import com.android.sdkuilib.internal.repository.ui.PackagesPageIcons; |
| import com.android.utils.Pair; |
| import com.android.utils.SparseArray; |
| import com.google.common.collect.Maps; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Helper class that separates the logic of package management from the UI |
| * so that we can test it using head-less unit tests. |
| */ |
| public class PackagesDiffLogic { |
| private final SwtUpdaterData mUpdaterData; |
| private boolean mFirstLoadComplete = true; |
| |
| public PackagesDiffLogic(SwtUpdaterData swtUpdaterData) { |
| mUpdaterData = swtUpdaterData; |
| } |
| |
| /** |
| * Removes all the internal state and resets the object. |
| * Useful for testing. |
| */ |
| public void clear() { |
| mFirstLoadComplete = true; |
| mOpApi.clear(); |
| mOpSource.clear(); |
| } |
| |
| /** Return mFirstLoadComplete and resets it to false. |
| * All following calls will returns false. */ |
| public boolean isFirstLoadComplete() { |
| boolean b = mFirstLoadComplete; |
| mFirstLoadComplete = false; |
| return b; |
| } |
| |
| /** |
| * Mark all new and update PkgItems as checked. |
| * |
| * @param selectNew If true, select all new packages (except the rc/preview ones). |
| * @param selectUpdates If true, select all update packages. |
| * @param selectTop If true, select the top platform. |
| * If the top platform has nothing installed, select all items in it (except the rc/preview); |
| * If it is partially installed, at least select the platform and system images if none of |
| * the system images are installed. |
| * @param currentPlatform The {@link SdkConstants#currentPlatform()} value. |
| */ |
| public void checkNewUpdateItems( |
| boolean selectNew, |
| boolean selectUpdates, |
| boolean selectTop, |
| int currentPlatform) { |
| int maxApi = 0; |
| Set<Integer> installedPlatforms = new HashSet<Integer>(); |
| SparseArray<List<PkgItem>> platformItems = new SparseArray<List<PkgItem>>(); |
| |
| boolean hasTools = false; |
| Map<Class<?>, Pair<PkgItem, FullRevision>> toolsCandidates = Maps.newHashMap(); |
| toolsCandidates.put(PlatformToolPackage.class, Pair.of((PkgItem)null, (FullRevision)null)); |
| toolsCandidates.put(BuildToolPackage.class, Pair.of((PkgItem)null, (FullRevision)null)); |
| |
| // sort items in platforms... directly deal with new/update items |
| List<PkgItem> allItems = getAllPkgItems(true /*byApi*/, true /*bySource*/); |
| for (PkgItem item : allItems) { |
| if (!item.hasCompatibleArchive()) { |
| // Ignore items that have no archive compatible with the current platform. |
| continue; |
| } |
| |
| // Get the main package's API level. We don't need to look at the updates |
| // since by definition they should target the same API level. |
| int api = 0; |
| Package p = item.getMainPackage(); |
| if (p instanceof IAndroidVersionProvider) { |
| api = ((IAndroidVersionProvider) p).getAndroidVersion().getApiLevel(); |
| } |
| |
| if (selectTop && api > 0) { |
| // Keep track of the max api seen |
| maxApi = Math.max(maxApi, api); |
| |
| // keep track of what platform is currently installed (that is, has at least |
| // one thing installed.) |
| if (item.getState() == PkgState.INSTALLED) { |
| installedPlatforms.add(api); |
| } |
| |
| // for each platform, collect all its related item for later use below. |
| List<PkgItem> items = platformItems.get(api); |
| if (items == null) { |
| platformItems.put(api, items = new ArrayList<PkgItem>()); |
| } |
| items.add(item); |
| } |
| |
| if ((selectUpdates || selectNew) && |
| item.getState() == PkgState.NEW && |
| !item.getRevision().isPreview()) { |
| boolean sameFound = false; |
| Package newPkg = item.getMainPackage(); |
| if (newPkg instanceof IFullRevisionProvider) { |
| // We have a potential new non-preview package; but this kind of package |
| // supports having previews, which means we want to make sure we're not |
| // offering an older "new" non-preview if there's a newer preview installed. |
| // |
| // We should get into this odd situation only when updating an RC/preview |
| // by a final release pkg. |
| |
| IFullRevisionProvider newPkg2 = (IFullRevisionProvider) newPkg; |
| for (PkgItem item2 : allItems) { |
| if (item2.getState() == PkgState.INSTALLED) { |
| Package installed = item2.getMainPackage(); |
| |
| if (installed.getRevision().isPreview() && |
| newPkg2.sameItemAs(installed, PreviewComparison.IGNORE)) { |
| sameFound = true; |
| |
| if (installed.canBeUpdatedBy(newPkg) == UpdateInfo.UPDATE) { |
| item.setChecked(true); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| if (selectNew && !sameFound) { |
| item.setChecked(true); |
| } |
| |
| } else if (selectUpdates && item.hasUpdatePkg()) { |
| item.setChecked(true); |
| } |
| |
| // Keep track of the tools and offer to auto-select platform-tools/build-tools. |
| if (selectTop) { |
| if (p instanceof ToolPackage && p.isLocal()) { |
| hasTools = true; // main tool package is installed. |
| } else if (p instanceof PlatformToolPackage || p instanceof BuildToolPackage) { |
| for (Class<?> clazz : toolsCandidates.keySet()) { |
| if (clazz.isInstance(p)) { // allow p to be a mock-derived class |
| if (p.isLocal()) { |
| // There's one such package installed, we don't need candidates. |
| toolsCandidates.remove(clazz); |
| } else if (toolsCandidates.containsKey(clazz)) { |
| Pair<PkgItem, FullRevision> val = toolsCandidates.get(clazz); |
| FullRevision rev = p.getRevision(); |
| if (!rev.isPreview()) { |
| // Don't auto-select previews. |
| if (val.getSecond() == null || |
| rev.compareTo(val.getSecond()) > 0) { |
| // No revision: set the first candidate. |
| // Or we found a new higher revision |
| toolsCandidates.put(clazz, Pair.of(item, rev)); |
| } |
| } |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| // Select the top platform/build-tool found above if needed. |
| if (selectTop && hasTools) { |
| for (Pair<PkgItem, FullRevision> candidate : toolsCandidates.values()) { |
| PkgItem item = candidate.getFirst(); |
| if (item != null) { |
| item.setChecked(true); |
| } |
| } |
| } |
| |
| |
| // Select top platform items. |
| |
| List<PkgItem> items = platformItems.get(maxApi); |
| if (selectTop && maxApi > 0 && items != null) { |
| if (!installedPlatforms.contains(maxApi)) { |
| // If the top platform has nothing installed at all, select everything in it |
| for (PkgItem item : items) { |
| if ((item.getState() == PkgState.NEW && !item.getRevision().isPreview()) || |
| item.hasUpdatePkg()) { |
| item.setChecked(true); |
| } |
| } |
| |
| } else { |
| // The top platform has at least one thing installed. |
| |
| // First make sure the platform package itself is installed, or select it. |
| for (PkgItem item : items) { |
| Package p = item.getMainPackage(); |
| if (p instanceof PlatformPackage && |
| item.getState() == PkgState.NEW && !item.getRevision().isPreview()) { |
| item.setChecked(true); |
| break; |
| } |
| } |
| |
| // Check we have at least one system image installed, otherwise select them |
| boolean hasSysImg = false; |
| for (PkgItem item : items) { |
| Package p = item.getMainPackage(); |
| if (p instanceof PlatformPackage && item.getState() == PkgState.INSTALLED) { |
| if (item.hasUpdatePkg() && item.isChecked()) { |
| // If the installed platform is scheduled for update, look for the |
| // system image in the update package, not the current one. |
| p = item.getUpdatePkg(); |
| if (p instanceof PlatformPackage) { |
| hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null; |
| } |
| } else { |
| // Otherwise look into the currently installed platform |
| hasSysImg = ((PlatformPackage) p).getIncludedAbi() != null; |
| } |
| if (hasSysImg) { |
| break; |
| } |
| } |
| if (p instanceof SystemImagePackage && |
| ((SystemImagePackage) p).isPlatform() && |
| item.getState() == PkgState.INSTALLED) { |
| hasSysImg = true; |
| break; |
| } |
| } |
| if (!hasSysImg) { |
| // No system image installed. |
| // Try whether the current platform or its update would bring one. |
| |
| for (PkgItem item : items) { |
| Package p = item.getMainPackage(); |
| if (p instanceof PlatformPackage) { |
| if (item.getState() == PkgState.NEW && |
| !item.getRevision().isPreview() && |
| ((PlatformPackage) p).getIncludedAbi() != null) { |
| item.setChecked(true); |
| hasSysImg = true; |
| } else if (item.hasUpdatePkg()) { |
| p = item.getUpdatePkg(); |
| if (p instanceof PlatformPackage && |
| ((PlatformPackage) p).getIncludedAbi() != null) { |
| item.setChecked(true); |
| hasSysImg = true; |
| } |
| } |
| } |
| } |
| } |
| if (!hasSysImg) { |
| // No system image in the platform, try a system image package |
| for (PkgItem item : items) { |
| Package p = item.getMainPackage(); |
| if (p instanceof SystemImagePackage && |
| ((SystemImagePackage) p).isPlatform() && |
| item.getState() == PkgState.NEW) { |
| item.setChecked(true); |
| } |
| } |
| } |
| } |
| } |
| |
| if (selectTop) { |
| for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) { |
| Package p = item.getMainPackage(); |
| if (p instanceof ExtraPackage && |
| item.getState() == PkgState.NEW && |
| !item.getRevision().isPreview()) { |
| ExtraPackage ep = (ExtraPackage) p; |
| |
| // On Windows, we'll also auto-select the USB driver |
| if (currentPlatform == SdkConstants.PLATFORM_WINDOWS) { |
| if (ep.getVendorId().equals("google") && //$NON-NLS-1$ |
| ep.getPath().equals("usb_driver")) { //$NON-NLS-1$ |
| item.setChecked(true); |
| continue; |
| } |
| } |
| |
| // On all platforms, we'll auto-select the support library. |
| if (ep.getVendorId().equals("android") && //$NON-NLS-1$ |
| ep.getPath().equals("support")) { //$NON-NLS-1$ |
| item.setChecked(true); |
| continue; |
| } |
| |
| } |
| } |
| } |
| } |
| |
| /** |
| * Mark all PkgItems as not checked. |
| */ |
| public void uncheckAllItems() { |
| for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) { |
| item.setChecked(false); |
| } |
| } |
| |
| /** |
| * An update operation, customized to either sort by API or sort by source. |
| */ |
| abstract class UpdateOp { |
| private final Set<SdkSource> mVisitedSources = new HashSet<SdkSource>(); |
| private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>(); |
| private final Set<PkgCategory> mCatsToRemove = new HashSet<PkgCategory>(); |
| private final Set<PkgItem> mItemsToRemove = new HashSet<PkgItem>(); |
| private final Map<Package, PkgItem> mUpdatesToRemove = new HashMap<Package, PkgItem>(); |
| |
| /** Removes all internal state. */ |
| public void clear() { |
| mVisitedSources.clear(); |
| mCategories.clear(); |
| } |
| |
| /** Retrieve the sorted category list. */ |
| public List<PkgCategory> getCategories() { |
| return mCategories; |
| } |
| |
| /** Retrieve the category key for the given package, either local or remote. */ |
| public abstract Object getCategoryKey(Package pkg); |
| |
| /** Modified {@code currentCategories} to add default categories. */ |
| public abstract void addDefaultCategories(); |
| |
| /** Creates the category for the given key and returns it. */ |
| public abstract PkgCategory createCategory(Object catKey); |
| /** Adjust attributes of an existing category. */ |
| public abstract void adjustCategory(PkgCategory cat, Object catKey); |
| |
| /** Sorts the category list (but not the items within the categories.) */ |
| public abstract void sortCategoryList(); |
| |
| /** Called after items of a given category have changed. Used to sort the |
| * items and/or adjust the category name. */ |
| public abstract void postCategoryItemsChanged(); |
| |
| public void updateStart() { |
| mVisitedSources.clear(); |
| |
| // Note that default categories are created after the unused ones so that |
| // the callback can decide whether they should be marked as unused or not. |
| mCatsToRemove.clear(); |
| mItemsToRemove.clear(); |
| mUpdatesToRemove.clear(); |
| for (PkgCategory cat : mCategories) { |
| mCatsToRemove.add(cat); |
| List<PkgItem> items = cat.getItems(); |
| mItemsToRemove.addAll(items); |
| for (PkgItem item : items) { |
| if (item.hasUpdatePkg()) { |
| mUpdatesToRemove.put(item.getUpdatePkg(), item); |
| } |
| } |
| } |
| |
| addDefaultCategories(); |
| } |
| |
| public boolean updateSourcePackages(SdkSource source, Package[] newPackages) { |
| mVisitedSources.add(source); |
| if (source == null) { |
| return processLocals(this, newPackages); |
| } else { |
| return processSource(this, source, newPackages); |
| } |
| } |
| |
| public boolean updateEnd() { |
| boolean hasChanged = false; |
| |
| // Remove unused categories & items at the end of the update |
| synchronized (mCategories) { |
| for (PkgCategory unusedCat : mCatsToRemove) { |
| if (mCategories.remove(unusedCat)) { |
| hasChanged = true; |
| } |
| } |
| } |
| |
| for (PkgCategory cat : mCategories) { |
| for (Iterator<PkgItem> itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) { |
| PkgItem item = itemIt.next(); |
| if (mItemsToRemove.contains(item)) { |
| itemIt.remove(); |
| hasChanged = true; |
| } else if (item.hasUpdatePkg() && |
| mUpdatesToRemove.containsKey(item.getUpdatePkg())) { |
| item.removeUpdate(); |
| hasChanged = true; |
| } |
| } |
| } |
| |
| mCatsToRemove.clear(); |
| mItemsToRemove.clear(); |
| mUpdatesToRemove.clear(); |
| |
| return hasChanged; |
| } |
| |
| public boolean isKeep(PkgItem item) { |
| return !mItemsToRemove.contains(item); |
| } |
| |
| public void keep(Package pkg) { |
| mUpdatesToRemove.remove(pkg); |
| } |
| |
| public void keep(PkgItem item) { |
| mItemsToRemove.remove(item); |
| } |
| |
| public void keep(PkgCategory cat) { |
| mCatsToRemove.remove(cat); |
| } |
| |
| public void dontKeep(PkgItem item) { |
| mItemsToRemove.add(item); |
| } |
| |
| public void dontKeep(PkgCategory cat) { |
| mCatsToRemove.add(cat); |
| } |
| } |
| |
| private final UpdateOpApi mOpApi = new UpdateOpApi(); |
| private final UpdateOpSource mOpSource = new UpdateOpSource(); |
| |
| public List<PkgCategory> getCategories(boolean displayIsSortByApi) { |
| return displayIsSortByApi ? mOpApi.getCategories() : mOpSource.getCategories(); |
| } |
| |
| public List<PkgItem> getAllPkgItems(boolean byApi, boolean bySource) { |
| List<PkgItem> items = new ArrayList<PkgItem>(); |
| |
| if (byApi) { |
| List<PkgCategory> cats = getCategories(true /*displayIsSortByApi*/); |
| synchronized (cats) { |
| for (PkgCategory cat : cats) { |
| items.addAll(cat.getItems()); |
| } |
| } |
| } |
| |
| if (bySource) { |
| List<PkgCategory> cats = getCategories(false /*displayIsSortByApi*/); |
| synchronized (cats) { |
| for (PkgCategory cat : cats) { |
| items.addAll(cat.getItems()); |
| } |
| } |
| } |
| |
| return items; |
| } |
| |
| public void updateStart() { |
| mOpApi.updateStart(); |
| mOpSource.updateStart(); |
| } |
| |
| public boolean updateSourcePackages( |
| boolean displayIsSortByApi, |
| SdkSource source, |
| Package[] newPackages) { |
| |
| boolean apiListChanged = mOpApi.updateSourcePackages(source, newPackages); |
| boolean sourceListChanged = mOpSource.updateSourcePackages(source, newPackages); |
| return displayIsSortByApi ? apiListChanged : sourceListChanged; |
| } |
| |
| public boolean updateEnd(boolean displayIsSortByApi) { |
| boolean apiListChanged = mOpApi.updateEnd(); |
| boolean sourceListChanged = mOpSource.updateEnd(); |
| return displayIsSortByApi ? apiListChanged : sourceListChanged; |
| } |
| |
| |
| /** Process all local packages. Returns true if something changed. */ |
| private boolean processLocals(UpdateOp op, Package[] packages) { |
| boolean hasChanged = false; |
| List<PkgCategory> cats = op.getCategories(); |
| Set<PkgItem> keep = new HashSet<PkgItem>(); |
| |
| // For all locally installed packages, check they are either listed |
| // as installed or create new installed items for them. |
| |
| nextPkg: for (Package localPkg : packages) { |
| // Check to see if we already have the exact same package |
| // (type & revision) marked as installed. |
| for (PkgCategory cat : cats) { |
| for (PkgItem currItem : cat.getItems()) { |
| if (currItem.getState() == PkgState.INSTALLED && |
| currItem.isSameMainPackageAs(localPkg)) { |
| // This package is already listed as installed. |
| op.keep(currItem); |
| op.keep(cat); |
| keep.add(currItem); |
| continue nextPkg; |
| } |
| } |
| } |
| |
| // If not found, create a new installed package item |
| keep.add(addNewItem(op, localPkg, PkgState.INSTALLED)); |
| hasChanged = true; |
| } |
| |
| // Remove installed items that we don't want to keep anymore. They would normally be |
| // cleanup up in UpdateOp.updateEnd(); however it's easier to remove them before we |
| // run processSource() to avoid merging updates in items that would be removed later. |
| |
| for (PkgCategory cat : cats) { |
| for (Iterator<PkgItem> itemIt = cat.getItems().iterator(); itemIt.hasNext(); ) { |
| PkgItem item = itemIt.next(); |
| if (item.getState() == PkgState.INSTALLED && !keep.contains(item)) { |
| itemIt.remove(); |
| hasChanged = true; |
| } |
| } |
| } |
| |
| if (hasChanged) { |
| op.postCategoryItemsChanged(); |
| } |
| |
| return hasChanged; |
| } |
| |
| /** |
| * {@link PkgState}s to check in {@link #processSource(UpdateOp, SdkSource, Package[])}. |
| * The order matters. |
| * When installing the diff will have both the new and the installed item and we |
| * need to merge with the installed one before the new one. |
| */ |
| private final static PkgState[] PKG_STATES = { PkgState.INSTALLED, PkgState.NEW }; |
| |
| /** Process all remote packages. Returns true if something changed. */ |
| private boolean processSource(UpdateOp op, SdkSource source, Package[] packages) { |
| boolean hasChanged = false; |
| List<PkgCategory> cats = op.getCategories(); |
| |
| boolean enablePreviews = |
| mUpdaterData.getSettingsController().getSettings().getEnablePreviews(); |
| |
| nextPkg: for (Package newPkg : packages) { |
| |
| if (!enablePreviews && newPkg.getRevision().isPreview()) { |
| // This is a preview and previews are not enabled. Ignore the package. |
| // Starting with Tools 23, we explicitly allows Build-Tools RC packages to |
| // always be visible so only RCs for Tools & Platform-Tools will be hidden. |
| if (!(newPkg instanceof BuildToolPackage)) { |
| continue nextPkg; |
| } |
| } |
| |
| for (PkgCategory cat : cats) { |
| for (PkgState state : PKG_STATES) { |
| for (Iterator<PkgItem> currItemIt = cat.getItems().iterator(); |
| currItemIt.hasNext(); ) { |
| PkgItem currItem = currItemIt.next(); |
| // We need to merge with installed items first. When installing |
| // the diff will have both the new and the installed item and we |
| // need to merge with the installed one before the new one. |
| if (currItem.getState() != state) { |
| continue; |
| } |
| // Only process current items if they represent the same item (but |
| // with a different revision number) than the new package. |
| Package mainPkg = currItem.getMainPackage(); |
| if (!mainPkg.sameItemAs(newPkg)) { |
| continue; |
| } |
| |
| // Check to see if we already have the exact same package |
| // (type & revision) marked as main or update package. |
| if (currItem.isSameMainPackageAs(newPkg)) { |
| op.keep(currItem); |
| op.keep(cat); |
| continue nextPkg; |
| } else if (currItem.hasUpdatePkg() && |
| currItem.isSameUpdatePackageAs(newPkg)) { |
| op.keep(currItem.getUpdatePkg()); |
| op.keep(cat); |
| continue nextPkg; |
| } |
| |
| switch (currItem.getState()) { |
| case NEW: |
| if (newPkg.getRevision().compareTo(mainPkg.getRevision()) < 0) { |
| if (!op.isKeep(currItem)) { |
| // The new item has a lower revision than the current one, |
| // but the current one hasn't been marked as being kept so |
| // it's ok to downgrade it. |
| currItemIt.remove(); |
| addNewItem(op, newPkg, PkgState.NEW); |
| hasChanged = true; |
| } |
| } else if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) { |
| // We have a more recent new version, remove the current one |
| // and replace by a new one |
| currItemIt.remove(); |
| addNewItem(op, newPkg, PkgState.NEW); |
| hasChanged = true; |
| } |
| break; |
| case INSTALLED: |
| // if newPkg.revision<=mainPkg.revision: it's already installed, ignore. |
| if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) { |
| // This is a new update for the main package. |
| if (currItem.mergeUpdate(newPkg)) { |
| op.keep(currItem.getUpdatePkg()); |
| op.keep(cat); |
| hasChanged = true; |
| } |
| } |
| break; |
| } |
| continue nextPkg; |
| } |
| } |
| } |
| // If not found, create a new package item |
| addNewItem(op, newPkg, PkgState.NEW); |
| hasChanged = true; |
| } |
| |
| if (hasChanged) { |
| op.postCategoryItemsChanged(); |
| } |
| |
| return hasChanged; |
| } |
| |
| private PkgItem addNewItem(UpdateOp op, Package pkg, PkgState state) { |
| List<PkgCategory> cats = op.getCategories(); |
| Object catKey = op.getCategoryKey(pkg); |
| PkgCategory cat = findCurrentCategory(cats, catKey); |
| |
| if (cat == null) { |
| // This is a new category. Create it and add it to the list. |
| cat = op.createCategory(catKey); |
| synchronized (cats) { |
| cats.add(cat); |
| } |
| op.sortCategoryList(); |
| } else { |
| // Not a new category. Give op a chance to adjust the category attributes |
| op.adjustCategory(cat, catKey); |
| } |
| |
| PkgItem item = new PkgItem(pkg, state); |
| op.keep(item); |
| cat.getItems().add(item); |
| op.keep(cat); |
| return item; |
| } |
| |
| private PkgCategory findCurrentCategory( |
| List<PkgCategory> currentCategories, |
| Object categoryKey) { |
| for (PkgCategory cat : currentCategories) { |
| if (cat.getKey().equals(categoryKey)) { |
| return cat; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * {@link UpdateOp} describing the Sort-by-API operation. |
| */ |
| private class UpdateOpApi extends UpdateOp { |
| @Override |
| public Object getCategoryKey(Package pkg) { |
| // Sort by API |
| |
| if (pkg instanceof IAndroidVersionProvider) { |
| return ((IAndroidVersionProvider) pkg).getAndroidVersion(); |
| |
| } else if (pkg instanceof ToolPackage || |
| pkg instanceof PlatformToolPackage || |
| pkg instanceof BuildToolPackage) { |
| if (pkg.getRevision().isPreview()) { |
| return PkgCategoryApi.KEY_TOOLS_PREVIEW; |
| } else { |
| return PkgCategoryApi.KEY_TOOLS; |
| } |
| } else { |
| return PkgCategoryApi.KEY_EXTRA; |
| } |
| } |
| |
| @Override |
| public void addDefaultCategories() { |
| boolean needTools = true; |
| boolean needExtras = true; |
| |
| List<PkgCategory> cats = getCategories(); |
| for (PkgCategory cat : cats) { |
| if (cat.getKey().equals(PkgCategoryApi.KEY_TOOLS)) { |
| // Mark them as not unused to prevent their removal in updateEnd(). |
| keep(cat); |
| needTools = false; |
| } else if (cat.getKey().equals(PkgCategoryApi.KEY_EXTRA)) { |
| keep(cat); |
| needExtras = false; |
| } |
| } |
| |
| // Always add the tools & extras categories, even if empty (unlikely anyway) |
| if (needTools) { |
| PkgCategoryApi acat = new PkgCategoryApi( |
| PkgCategoryApi.KEY_TOOLS, |
| null, |
| mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_OTHER)); |
| synchronized (cats) { |
| cats.add(acat); |
| } |
| } |
| |
| if (needExtras) { |
| PkgCategoryApi acat = new PkgCategoryApi( |
| PkgCategoryApi.KEY_EXTRA, |
| null, |
| mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_OTHER)); |
| synchronized (cats) { |
| cats.add(acat); |
| } |
| } |
| } |
| |
| @Override |
| public PkgCategory createCategory(Object catKey) { |
| // Create API category. |
| PkgCategory cat = null; |
| |
| assert catKey instanceof AndroidVersion; |
| AndroidVersion key = (AndroidVersion) catKey; |
| |
| // We should not be trying to recreate the tools or extra categories. |
| assert !key.equals(PkgCategoryApi.KEY_TOOLS) && !key.equals(PkgCategoryApi.KEY_EXTRA); |
| |
| // We need a label for the category. |
| // If we have an API level, try to get the info from the SDK Manager. |
| // If we don't (e.g. when installing a new platform that isn't yet available |
| // locally in the SDK Manager), it's OK we'll try to find the first platform |
| // package available. |
| String platformName = null; |
| for (IAndroidTarget target : |
| mUpdaterData.getSdkManager().getTargets()) { |
| if (target.isPlatform() && key.equals(target.getVersion())) { |
| platformName = target.getVersionName(); |
| break; |
| } |
| } |
| |
| cat = new PkgCategoryApi( |
| key, |
| platformName, |
| mUpdaterData.getImageFactory().getImageByName(PackagesPageIcons.ICON_CAT_PLATFORM)); |
| |
| return cat; |
| } |
| |
| @Override |
| public void adjustCategory(PkgCategory cat, Object catKey) { |
| // Pass. Nothing to do for API-sorted categories |
| } |
| |
| @Override |
| public void sortCategoryList() { |
| // Sort the categories list. |
| // We always want categories in order tools..platforms..extras. |
| // For platform, we compare in descending order (o2-o1). |
| // This order is achieved by having the category keys ordered as |
| // needed for the sort to just do what we expect. |
| |
| synchronized (getCategories()) { |
| Collections.sort(getCategories(), new Comparator<PkgCategory>() { |
| @Override |
| public int compare(PkgCategory cat1, PkgCategory cat2) { |
| assert cat1 instanceof PkgCategoryApi; |
| assert cat2 instanceof PkgCategoryApi; |
| assert cat1.getKey() instanceof AndroidVersion; |
| assert cat2.getKey() instanceof AndroidVersion; |
| AndroidVersion v1 = (AndroidVersion) cat1.getKey(); |
| AndroidVersion v2 = (AndroidVersion) cat2.getKey(); |
| return v2.compareTo(v1); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void postCategoryItemsChanged() { |
| // Sort the items |
| for (PkgCategory cat : getCategories()) { |
| Collections.sort(cat.getItems()); |
| |
| // When sorting by API, we can't always get the platform name |
| // from the package manager. In this case at the very end we |
| // look for a potential platform package we can use to extract |
| // the platform version name (e.g. '1.5') from the first suitable |
| // platform package we can find. |
| |
| assert cat instanceof PkgCategoryApi; |
| PkgCategoryApi pac = (PkgCategoryApi) cat; |
| if (pac.getPlatformName() == null) { |
| // Check whether we can get the actual platform version name (e.g. "1.5") |
| // from the first Platform package we find in this category. |
| |
| for (PkgItem item : cat.getItems()) { |
| Package p = item.getMainPackage(); |
| if (p instanceof PlatformPackage) { |
| String platformName = ((PlatformPackage) p).getVersionName(); |
| if (platformName != null) { |
| pac.setPlatformName(platformName); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| } |
| |
| /** |
| * {@link UpdateOp} describing the Sort-by-Source operation. |
| */ |
| private class UpdateOpSource extends UpdateOp { |
| |
| @Override |
| public boolean updateSourcePackages(SdkSource source, Package[] newPackages) { |
| // When displaying the repo by source, we want to create all the |
| // categories so that they can appear on the UI even if empty. |
| if (source != null) { |
| List<PkgCategory> cats = getCategories(); |
| Object catKey = source; |
| PkgCategory cat = findCurrentCategory(cats, catKey); |
| |
| if (cat == null) { |
| // This is a new category. Create it and add it to the list. |
| cat = createCategory(catKey); |
| synchronized (cats) { |
| cats.add(cat); |
| } |
| sortCategoryList(); |
| } |
| |
| keep(cat); |
| } |
| |
| return super.updateSourcePackages(source, newPackages); |
| } |
| |
| @Override |
| public Object getCategoryKey(Package pkg) { |
| // Sort by source |
| SdkSource source = pkg.getParentSource(); |
| if (source == null) { |
| return PkgCategorySource.UNKNOWN_SOURCE; |
| } |
| return source; |
| } |
| |
| @Override |
| public void addDefaultCategories() { |
| List<PkgCategory> cats = getCategories(); |
| for (PkgCategory cat : cats) { |
| if (cat.getKey().equals(PkgCategorySource.UNKNOWN_SOURCE)) { |
| // Already present. |
| return; |
| } |
| } |
| |
| // Always add the local categories, even if empty (unlikely anyway) |
| PkgCategorySource cat = new PkgCategorySource( |
| PkgCategorySource.UNKNOWN_SOURCE, |
| mUpdaterData); |
| // Mark it so that it can be cleared in updateEnd() if not used. |
| dontKeep(cat); |
| synchronized (cats) { |
| cats.add(cat); |
| } |
| } |
| |
| /** |
| * Create a new source category. |
| * <p/> |
| * One issue is that local archives are processed first and we don't have the |
| * full source information on them (e.g. we know the referral URL but not |
| * the referral name of the site). |
| * In this case this will just create {@link PkgCategorySource} where the label isn't |
| * known yet. |
| */ |
| @Override |
| public PkgCategory createCategory(Object catKey) { |
| assert catKey instanceof SdkSource; |
| PkgCategory cat = new PkgCategorySource((SdkSource) catKey, mUpdaterData); |
| return cat; |
| } |
| |
| /** |
| * Checks whether the category needs to be adjust. |
| * As mentioned in {@link #createCategory(Object)}, local archives are processed |
| * first and result in a {@link PkgCategorySource} where the label isn't known. |
| * Once we process the external source with the actual name, we'll update it. |
| */ |
| @Override |
| public void adjustCategory(PkgCategory cat, Object catKey) { |
| assert cat instanceof PkgCategorySource; |
| assert catKey instanceof SdkSource; |
| if (cat instanceof PkgCategorySource) { |
| ((PkgCategorySource) cat).adjustLabel((SdkSource) catKey); |
| } |
| } |
| |
| @Override |
| public void sortCategoryList() { |
| // Sort the sources in ascending source name order, |
| // with the local packages always first. |
| |
| synchronized (getCategories()) { |
| Collections.sort(getCategories(), new Comparator<PkgCategory>() { |
| @Override |
| public int compare(PkgCategory cat1, PkgCategory cat2) { |
| assert cat1 instanceof PkgCategorySource; |
| assert cat2 instanceof PkgCategorySource; |
| |
| SdkSource src1 = ((PkgCategorySource) cat1).getSource(); |
| SdkSource src2 = ((PkgCategorySource) cat2).getSource(); |
| |
| if (src1 == src2) { |
| return 0; |
| } else if (src1 == PkgCategorySource.UNKNOWN_SOURCE) { |
| return -1; |
| } else if (src2 == PkgCategorySource.UNKNOWN_SOURCE) { |
| return 1; |
| } |
| assert src1 != null; // true because LOCAL_SOURCE==null |
| assert src2 != null; |
| return src1.toString().compareTo(src2.toString()); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void postCategoryItemsChanged() { |
| // Sort the items |
| for (PkgCategory cat : getCategories()) { |
| Collections.sort(cat.getItems()); |
| } |
| } |
| } |
| } |