blob: ff2000facbccaa580e3d77661dcf5e5a830516a3 [file] [log] [blame]
/*
* Copyright (C) 2009 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.sdklib.internal.repository.updater;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.AddonPackage;
import com.android.sdklib.internal.repository.packages.BuildToolPackage;
import com.android.sdklib.internal.repository.packages.DocPackage;
import com.android.sdklib.internal.repository.packages.ExtraPackage;
import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
import com.android.sdklib.internal.repository.packages.IExactApiLevelDependency;
import com.android.sdklib.internal.repository.packages.IMinApiLevelDependency;
import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;
import com.android.sdklib.internal.repository.packages.IMinToolsDependency;
import com.android.sdklib.internal.repository.packages.IPlatformDependency;
import com.android.sdklib.internal.repository.packages.MinToolsPackage;
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.SamplePackage;
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.sources.SdkSources;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.descriptors.IdDisplay;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The logic to compute which packages to install, based on the choices
* made by the user. This adds required packages as needed.
* <p/>
* When the user doesn't provide a selection, looks at local package to find
* those that can be updated and compute dependencies too.
*
* @deprecated
* com.android.sdklib.internal.repository has moved into Studio as
* com.android.tools.idea.sdk.remote.internal.
*/
@Deprecated
public class SdkUpdaterLogic {
private final IUpdaterData mUpdaterData;
public SdkUpdaterLogic(IUpdaterData updaterData) {
mUpdaterData = updaterData;
}
/**
* Retrieves an unfiltered list of all remote archives.
* The archives are guaranteed to be compatible with the current platform.
*/
public List<ArchiveInfo> getAllRemoteArchives(
SdkSources sources,
Package[] localPkgs,
boolean includeAll) {
List<Package> remotePkgs = new ArrayList<Package>();
SdkSource[] remoteSources = sources.getAllSources();
fetchRemotePackages(remotePkgs, remoteSources);
ArrayList<Archive> archives = new ArrayList<Archive>();
for (Package remotePkg : remotePkgs) {
// Only look for non-obsolete updates unless requested to include them
if (includeAll || !remotePkg.isObsolete()) {
// Found a suitable update. Only accept the remote package
// if it provides at least one compatible archive
addArchives:
for (Archive a : remotePkg.getArchives()) {
if (a.isCompatible()) {
// If we're trying to add a package for revision N,
// make sure we don't also have a package for revision N-1.
for (int i = archives.size() - 1; i >= 0; i--) {
Package pkgFound = archives.get(i).getParentPackage();
if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
// This package can update one we selected earlier.
// Remove the one that can be updated by this new one.
archives.remove(i);
} else if (remotePkg.canBeUpdatedBy(pkgFound) == UpdateInfo.UPDATE) {
// There is a package in the list that is already better
// than the one we want to add, so don't add it.
break addArchives;
}
}
archives.add(a);
break;
}
}
}
}
ArrayList<ArchiveInfo> result = new ArrayList<ArchiveInfo>();
ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
for (Archive a : archives) {
insertArchive(a,
result,
archives,
remotePkgs,
remoteSources,
localArchives,
false /*automated*/);
}
return result;
}
/**
* Compute which packages to install by taking the user selection
* and adding required packages as needed.
*
* When the user doesn't provide a selection, looks at local packages to find
* those that can be updated and compute dependencies too.
*/
public List<ArchiveInfo> computeUpdates(
Collection<Archive> selectedArchives,
SdkSources sources,
Package[] localPkgs,
boolean includeAll) {
List<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
List<Package> remotePkgs = new ArrayList<Package>();
SdkSource[] remoteSources = sources.getAllSources();
// Create ArchiveInfos out of local (installed) packages.
ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
// If we do not have a specific list of archives to install (that is the user
// selected "update all" rather than request specific packages), then we try to
// find updates based on the *existing* packages.
if (selectedArchives == null) {
selectedArchives = findUpdates(
localArchives,
remotePkgs,
remoteSources,
includeAll);
}
// Once we have a list of packages to install, we try to solve all their
// dependencies by automatically adding them to the list of things to install.
// This works on the list provided either by the user directly or the list
// computed from potential updates.
for (Archive a : selectedArchives) {
insertArchive(a,
archives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
false /*automated*/);
}
// Finally we need to look at *existing* packages which are not being updated
// and check if they have any missing dependencies and suggest how to fix
// these dependencies.
fixMissingLocalDependencies(
archives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
return archives;
}
private double getRevisionRank(FullRevision rev) {
int p = rev.isPreview() ? 999 : 999 - rev.getPreview();
return rev.getMajor() +
rev.getMinor() / 1000.d +
rev.getMicro() / 1000000.d +
p / 1000000000.d;
}
/**
* Finds new packages that the user does not have in his/her local SDK
* and adds them to the list of archives to install.
* <p/>
* The default is to only find "new" platforms, that is anything more
* recent than the highest platform currently installed.
* A side effect is that for an empty SDK install this will list *all*
* platforms available (since there's no "highest" installed platform.)
* <p/>
* This also adds "silent" dependencies. For example the user probably
* needs to have at least one version of the build-tools package although
* there is nothing that directly depends on it. So if the user doesn't have
* any installed or selected version, selected the most recent one as a
* candidate for install.
*
* @param archives The in-out list of archives to install. Typically the
* list is not empty at first as it should contain any archives that is
* already scheduled for install. This method will add to the list.
* @param sources The list of all sources, to fetch them as necessary.
* @param localPkgs The list of all currently installed packages.
* @param includeAll When true, this will list all platforms.
* (included these lower than the highest installed one) as well as
* all obsolete packages of these platforms.
*/
public void addNewPlatforms(
Collection<ArchiveInfo> archives,
SdkSources sources,
Package[] localPkgs,
boolean includeAll) {
// Create ArchiveInfos out of local (installed) packages.
ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
// Find the highest platform installed
double currentBuildToolScore = 0;
double currentPlatformScore = 0;
double currentSampleScore = 0;
double currentAddonScore = 0;
double currentDocScore = 0;
HashMap<String, Double> currentExtraScore = new HashMap<String, Double>();
if (!includeAll) {
if (localPkgs != null) {
for (Package p : localPkgs) {
double rev = getRevisionRank(p.getRevision());
int api = 0;
boolean isPreview = false;
if (p instanceof IAndroidVersionProvider) {
AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
// The score is 1000*api + (999 if preview) + rev
// This allows previews to rank above a non-preview and
// allows revisions to rank appropriately.
double score = api * 1000 + (isPreview ? 999 : 0) + rev;
if (p instanceof BuildToolPackage) {
currentBuildToolScore = Math.max(currentBuildToolScore, score);
} else if (p instanceof PlatformPackage) {
currentPlatformScore = Math.max(currentPlatformScore, score);
} else if (p instanceof SamplePackage) {
currentSampleScore = Math.max(currentSampleScore, score);
} else if (p instanceof AddonPackage) {
currentAddonScore = Math.max(currentAddonScore, score);
} else if (p instanceof ExtraPackage) {
currentExtraScore.put(((ExtraPackage) p).getPath(), score);
} else if (p instanceof DocPackage) {
currentDocScore = Math.max(currentDocScore, score);
}
}
}
}
SdkSource[] remoteSources = sources.getAllSources();
ArrayList<Package> remotePkgs = new ArrayList<Package>();
fetchRemotePackages(remotePkgs, remoteSources);
Package suggestedDoc = null;
Package suggestedBuildTool = null;
for (Package p : remotePkgs) {
// Skip obsolete packages unless requested to include them.
if (p.isObsolete() && !includeAll) {
continue;
}
double rev = getRevisionRank(p.getRevision());
int api = 0;
boolean isPreview = false;
if (p instanceof IAndroidVersionProvider) {
AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
double score = api * 1000 + (isPreview ? 999 : 0) + rev;
boolean shouldAdd = false;
if (p instanceof BuildToolPackage) {
// We don't want all the build-tool packages, only the most recent one
// if not is currently installed.
if (currentBuildToolScore == 0 && score > currentBuildToolScore) {
suggestedBuildTool = p;
currentBuildToolScore = score;
}
} else if (p instanceof PlatformPackage) {
shouldAdd = score > currentPlatformScore;
} else if (p instanceof SamplePackage) {
shouldAdd = score > currentSampleScore;
} else if (p instanceof AddonPackage) {
shouldAdd = score > currentAddonScore;
} else if (p instanceof ExtraPackage) {
String key = ((ExtraPackage) p).getPath();
shouldAdd = !currentExtraScore.containsKey(key) ||
score > currentExtraScore.get(key).doubleValue();
} else if (p instanceof DocPackage) {
// We don't want all the doc, only the most recent one
if (score > currentDocScore) {
suggestedDoc = p;
currentDocScore = score;
}
}
if (shouldAdd) {
// We should suggest this package for installation.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
if (p instanceof PlatformPackage && (score >= currentPlatformScore)) {
// We just added a new platform *or* we are visiting the highest currently
// installed platform. In either case we want to make sure it either has
// its own system image or that we provide one by default.
PlatformPackage pp = (PlatformPackage) p;
if (pp.getIncludedAbi() == null) {
for (Package p2 : remotePkgs) {
if (!(p2 instanceof SystemImagePackage) ||
((SystemImagePackage)p2).isPlatform() ||
(p2.isObsolete() && !includeAll)) {
continue;
}
SystemImagePackage sip = (SystemImagePackage) p2;
if (sip.getAndroidVersion().equals(pp.getAndroidVersion())) {
for (Archive a : sip.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
}
}
}
if (suggestedDoc != null) {
// We should suggest this package for installation.
for (Archive a : suggestedDoc.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
if (suggestedBuildTool != null) {
// We should suggest this package for installation.
for (Archive a : suggestedBuildTool.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
/**
* Create a array of {@link ArchiveInfo} based on all local (already installed)
* packages. The array is always non-null but may be empty.
* <p/>
* The local {@link ArchiveInfo} are guaranteed to have one non-null archive
* that you can retrieve using {@link ArchiveInfo#getNewArchive()}.
*/
public ArchiveInfo[] createLocalArchives(Package[] localPkgs) {
if (localPkgs != null) {
ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
for (Package p : localPkgs) {
// Only accept packages that have one compatible archive.
// Local package should have 1 and only 1 compatible archive anyway.
for (Archive a : p.getArchives()) {
if (a != null && a.isCompatible()) {
// We create an "installed" archive info to wrap the local package.
// Note that dependencies are not computed since right now we don't
// deal with more than one level of dependencies and installed archives
// are deemed implicitly accepted anyway.
list.add(new LocalArchiveInfo(a));
}
}
}
return list.toArray(new ArchiveInfo[list.size()]);
}
return new ArchiveInfo[0];
}
/**
* Find suitable updates to all current local packages.
* <p/>
* Returns a list of potential updates for *existing* packages. This does NOT solve
* dependencies for the new packages.
* <p/>
* Always returns a non-null collection, which can be empty.
*/
private Collection<Archive> findUpdates(
ArchiveInfo[] localArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
boolean includeAll) {
ArrayList<Archive> updates = new ArrayList<Archive>();
fetchRemotePackages(remotePkgs, remoteSources);
for (ArchiveInfo ai : localArchives) {
Archive na = ai.getNewArchive();
if (na == null) {
continue;
}
Package localPkg = na.getParentPackage();
for (Package remotePkg : remotePkgs) {
// Only look for non-obsolete updates unless requested to include them
if ((includeAll || !remotePkg.isObsolete()) &&
localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
// Found a suitable update. Only accept the remote package
// if it provides at least one compatible archive
addArchives:
for (Archive a : remotePkg.getArchives()) {
if (a.isCompatible()) {
// If we're trying to add a package for revision N,
// make sure we don't also have a package for revision N-1.
for (int i = updates.size() - 1; i >= 0; i--) {
Package pkgFound = updates.get(i).getParentPackage();
if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
// This package can update one we selected earlier.
// Remove the one that can be updated by this new one.
updates.remove(i);
} else if (remotePkg.canBeUpdatedBy(pkgFound) ==
UpdateInfo.UPDATE) {
// There is a package in the list that is already better
// than the one we want to add, so don't add it.
break addArchives;
}
}
updates.add(a);
break;
}
}
}
}
}
return updates;
}
/**
* Check all local archives which are NOT being updated and see if they
* miss any dependency. If they do, try to fix that dependency by selecting
* an appropriate package.
*/
private void fixMissingLocalDependencies(
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
nextLocalArchive: for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
Package p = a == null ? null : a.getParentPackage();
if (p == null) {
continue;
}
// Is this local archive being updated?
for (ArchiveInfo ai2 : outArchives) {
if (ai2.getReplaced() == a) {
// this new archive will replace the current local one,
// so we don't have to care about fixing dependencies (since the
// new archive should already have had its dependencies resolved)
continue nextLocalArchive;
}
}
// find dependencies for the local archive and add them as needed
// to the outArchives collection.
ArchiveInfo[] deps = findDependency(p,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (deps != null) {
// The already installed archive has a missing dependency, which we
// just selected for install. Make sure we remember the dependency
// so that we can enforce it later in the UI.
for (ArchiveInfo aid : deps) {
aid.addDependencyFor(ai);
}
}
}
}
private ArchiveInfo insertArchive(Archive archive,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives,
boolean automated) {
Package p = archive.getParentPackage();
// Is this an update?
Archive updatedArchive = null;
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package lp = a.getParentPackage();
if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
updatedArchive = a;
}
}
}
// Find dependencies and adds them as needed to outArchives
ArchiveInfo[] deps = findDependency(p,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
// Make sure it's not a dup
ArchiveInfo ai = null;
for (ArchiveInfo ai2 : outArchives) {
Archive a2 = ai2.getNewArchive();
if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {
ai = ai2;
break;
}
}
if (ai == null) {
ai = new ArchiveInfo(
archive, //newArchive
updatedArchive, //replaced
deps //dependsOn
);
outArchives.add(ai);
}
if (deps != null) {
for (ArchiveInfo d : deps) {
d.addDependencyFor(ai);
}
}
return ai;
}
/**
* Resolves dependencies for a given package.
*
* Returns null if no dependencies were found.
* Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have
* at least size 1 and contain no null elements.
*/
private ArchiveInfo[] findDependency(Package pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// Current dependencies can be:
// - addon: *always* depends on platform of same API level
// - platform: *might* depends on tools of rev >= min-tools-rev
// - extra: *might* depends on platform with api >= min-api-level
Set<ArchiveInfo> aiFound = new HashSet<ArchiveInfo>();
if (pkg instanceof IPlatformDependency) {
ArchiveInfo ai = findPlatformDependency(
(IPlatformDependency) pkg,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (ai != null) {
aiFound.add(ai);
}
}
if (pkg instanceof IMinToolsDependency) {
ArchiveInfo ai = findToolsDependency(
(IMinToolsDependency) pkg,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (ai != null) {
aiFound.add(ai);
}
}
if (pkg instanceof IMinPlatformToolsDependency) {
ArchiveInfo ai = findPlatformToolsDependency(
(IMinPlatformToolsDependency) pkg,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (ai != null) {
aiFound.add(ai);
}
}
if (pkg instanceof IMinApiLevelDependency) {
ArchiveInfo ai = findMinApiLevelDependency(
(IMinApiLevelDependency) pkg,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (ai != null) {
aiFound.add(ai);
}
}
if (pkg instanceof IExactApiLevelDependency) {
ArchiveInfo ai = findExactApiLevelDependency(
(IExactApiLevelDependency) pkg,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives);
if (ai != null) {
aiFound.add(ai);
}
}
if (!aiFound.isEmpty()) {
ArchiveInfo[] result = aiFound.toArray(new ArchiveInfo[aiFound.size()]);
Arrays.sort(result);
return result;
}
return null;
}
/**
* Resolves dependencies on tools.
*
* A platform or an extra package can both have a min-tools-rev, in which case it
* depends on having a tools package of the requested revision.
* Finds the tools dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
public ArchiveInfo findToolsDependency(
IMinToolsDependency pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
FullRevision rev = pkg.getMinToolsRevision();
if (rev.equals(MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED)) {
// Well actually there's no requirement.
return null;
}
// First look in locally installed packages.
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// We found one already installed.
return null;
}
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
}
}
}
// Otherwise look in the selected archives.
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
FullRevision localRev = rev;
Archive localArch = null;
for (Package p : remotePkgs) {
if (p instanceof ToolPackage) {
FullRevision r = ((ToolPackage) p).getRevision();
if (r.compareTo(localRev) >= 0) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
localRev = r;
localArch = a;
break;
}
}
}
}
}
if (localArch != null) {
return insertArchive(localArch,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this extra depends on a missing platform archive
// so that it can be impossible to install later on.
return new MissingArchiveInfo(MissingArchiveInfo.TITLE_TOOL, rev);
}
/**
* Resolves dependencies on platform-tools.
*
* A tool package can have a min-platform-tools-rev, in which case it depends on
* having a platform-tool package of the requested revision.
* Finds the platform-tool dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
public ArchiveInfo findPlatformToolsDependency(
IMinPlatformToolsDependency pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
FullRevision rev = pkg.getMinPlatformToolsRevision();
boolean findMax = false;
int compareThreshold = 0;
ArchiveInfo aiMax = null;
Archive aMax = null;
if (rev.equals(IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID)) {
// The requirement is invalid, which is not supposed to happen since this
// property is mandatory. However in a typical upgrade scenario we can end
// up with the previous updater managing a new package and not dealing
// correctly with the new unknown property.
// So instead we parse all the existing and remote packages and try to find
// the max available revision and we'll use it.
findMax = true;
// When findMax is false, we want r.compareTo(rev) >= 0.
// When findMax is true, we want r.compareTo(rev) > 0 (so >= 1).
compareThreshold = 1;
}
// First look in locally installed packages.
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
FullRevision r = ((PlatformToolPackage) p).getRevision();
if (findMax && r.compareTo(rev) > compareThreshold) {
rev = r;
aiMax = ai;
} else if (!findMax && r.compareTo(rev) >= compareThreshold) {
// We found one already installed.
return null;
}
}
}
}
// Because of previews, we can have more than 1 choice, so get the local max.
FullRevision localRev = rev;
ArchiveInfo localAiMax = null;
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
FullRevision r = ((PlatformToolPackage) p).getRevision();
// If computing dependencies for a non-preview package, don't offer preview dependencies
if (r.isPreview() && !rev.isPreview()) {
continue;
}
if (r.compareTo(localRev) >= compareThreshold) {
localRev = r;
localAiMax = ai;
}
}
}
}
if (localAiMax != null) {
if (findMax) {
rev = localRev;
aiMax = localAiMax;
} else {
// The dependency is already scheduled for install, nothing else to do.
return localAiMax;
}
}
// Otherwise look in the selected archives.
localRev = rev;
Archive localAMax = null;
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformToolPackage) {
FullRevision r = ((PlatformToolPackage) p).getRevision();
// If computing dependencies for a non-preview package, don't offer preview dependencies
if (r.isPreview() && !rev.isPreview()) {
continue;
}
if (r.compareTo(localRev) >= compareThreshold) {
localRev = r;
localAiMax = null;
localAMax = a;
}
}
}
if (localAMax != null) {
if (findMax) {
rev = localRev;
aiMax = null;
aMax = localAMax;
} else {
// It's not already in the list of things to install, so add it now
return insertArchive(localAMax,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
localRev = rev;
localAMax = null;
for (Package p : remotePkgs) {
if (p instanceof PlatformToolPackage) {
FullRevision r = ((PlatformToolPackage) p).getRevision();
// If computing dependencies for a non-preview package, don't offer preview dependencies
if (r.isPreview() && !rev.isPreview()) {
continue;
}
if (r.compareTo(rev) >= 0) {
// Make sure there's at least one valid archive here
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
if (r.compareTo(localRev) >= compareThreshold) {
localRev = r;
localAiMax = null;
localAMax = a;
break;
}
}
}
}
}
}
if (localAMax != null) {
if (findMax) {
rev = localRev;
aiMax = null;
aMax = localAMax;
} else {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
return insertArchive(localAMax,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
if (findMax) {
if (aMax != null) {
return insertArchive(aMax,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
} else if (aiMax != null) {
return aiMax;
}
}
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this package depends on a missing platform archive
// so that it can be impossible to install later on.
return new MissingArchiveInfo(MissingArchiveInfo.TITLE_PLATFORM_TOOL, rev);
}
/**
* Resolves dependencies on platform for an add-on.
* Resolves dependencies on a system-image on its base platform or add-on.
*
* An add-on depends on having a platform with the same API level.
*
* Finds the platform dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
public ArchiveInfo findPlatformDependency(
IPlatformDependency pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
// This is the requirement to match.
AndroidVersion v = pkg.getAndroidVersion();
// The dependency package must be a PlatformPackage or an AddonPackage.
// For an add-on, we also need to have the same vendor and name-id.
Class<? extends Package> expectedClass = PlatformPackage.class;
IdDisplay addonVendor = null;
IdDisplay addonTag = null;
if (pkg instanceof SystemImagePackage && !((SystemImagePackage) pkg).isPlatform()) {
expectedClass = AddonPackage.class;
addonVendor = ((SystemImagePackage) pkg).getAddonVendor();
addonTag = ((SystemImagePackage) pkg).getTag();
}
// Find a platform or addon that would satisfy the requirement.
// First look in locally installed packages.
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (expectedClass.isInstance(p)) {
if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
!((AddonPackage) p).getNameId().equals(addonTag.getId())) {
continue;
}
}
// We found one already installed.
return null;
}
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (expectedClass.isInstance(p)) {
if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
!((AddonPackage) p).getNameId().equals(addonTag.getId())) {
continue;
}
}
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
}
}
}
// Otherwise look in the selected archives.
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (expectedClass.isInstance(p)) {
if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
!((AddonPackage) p).getNameId().equals(addonTag.getId())) {
continue;
}
}
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (expectedClass.isInstance(p)) {
if (v.equals(((IAndroidVersionProvider) p).getAndroidVersion())) {
if (addonVendor != null && addonTag != null && p instanceof AddonPackage) {
if (!((AddonPackage) p).getVendorId().equals(addonVendor.getId()) ||
!((AddonPackage) p).getNameId().equals(addonTag.getId())) {
continue;
}
}
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
return insertArchive(a,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
}
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this addon depends on a missing platform archive
// so that it can be impossible to install later on.
return new MissingPlatformArchiveInfo(pkg.getAndroidVersion());
}
/**
* Resolves platform dependencies for extras.
* An extra depends on having a platform with a minimun API level.
*
* We try to return the highest API level available above the specified minimum.
* Note that installed packages have priority so if one installed platform satisfies
* the dependency, we'll use it even if there's a higher API platform available but
* not installed yet.
*
* Finds the platform dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
protected ArchiveInfo findMinApiLevelDependency(
IMinApiLevelDependency pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
int api = pkg.getMinApiLevel();
if (api == IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED) {
return null;
}
// Find a platform that would satisfy the requirement.
// First look in locally installed packages.
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
// We found one already installed.
return null;
}
}
}
}
// Look in archives already scheduled for install
int foundApi = 0;
ArchiveInfo foundAi = null;
for (ArchiveInfo ai : outArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
foundApi = api;
foundAi = ai;
}
}
}
}
}
if (foundAi != null) {
// The dependency is already scheduled for install, nothing else to do.
return foundAi;
}
// Otherwise look in the selected archives *or* available remote packages
// and takes the best out of the two sets.
foundApi = 0;
Archive foundArchive = null;
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
foundApi = api;
foundArchive = a;
}
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {
if (api > foundApi) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
foundApi = api;
foundArchive = a;
}
}
}
}
}
}
if (foundArchive != null) {
// It's not already in the list of things to install, so add it now
return insertArchive(foundArchive,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this extra depends on a missing platform archive
// so that it can be impossible to install later on.
return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
}
/**
* Resolves platform dependencies for add-ons.
* An add-ons depends on having a platform with an exact specific API level.
*
* Finds the platform dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
public ArchiveInfo findExactApiLevelDependency(
IExactApiLevelDependency pkg,
Collection<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
Collection<Package> remotePkgs,
SdkSource[] remoteSources,
ArchiveInfo[] localArchives) {
int api = pkg.getExactApiLevel();
if (api == IExactApiLevelDependency.API_LEVEL_INVALID) {
return null;
}
// Find a platform that would satisfy the requirement.
// First look in locally installed packages.
for (ArchiveInfo ai : localArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// We found one already installed.
return null;
}
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
return ai;
}
}
}
}
// Otherwise look in the selected archives.
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// It's not already in the list of things to install, so add it now
return insertArchive(a,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
if (((PlatformPackage) p).getAndroidVersion().equals(api)) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
return insertArchive(a,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localArchives,
true /*automated*/);
}
}
}
}
}
// We end up here if nothing matches. We don't have a good platform to match.
// We need to indicate this extra depends on a missing platform archive
// so that it can be impossible to install later on.
return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
}
/**
* Fetch all remote packages only if really needed.
* <p/>
* This method takes a list of sources. Each source is only fetched once -- that is each
* source keeps the list of packages that we fetched from the remote XML file. If the list
* is null, it means this source has never been fetched so we'll do it once here. Otherwise
* we rely on the cached list of packages from this source.
* <p/>
* This method also takes a remote package list as input, which it will fill out.
* If a source has already been fetched, we'll add its packages to the remote package list
* if they are not already present. Otherwise, the source will be fetched and the packages
* added to the list.
*
* @param remotePkgs An in-out list of packages available from remote sources.
* This list must not be null.
* It can be empty or already contain some packages.
* @param remoteSources A list of available remote sources to fetch from.
*/
protected void fetchRemotePackages(
final Collection<Package> remotePkgs,
final SdkSource[] remoteSources) {
if (!remotePkgs.isEmpty()) {
return;
}
// First check if there's any remote source we need to fetch.
// This will bring the task window, so we rather not display it unless
// necessary.
boolean needsFetch = false;
for (final SdkSource remoteSrc : remoteSources) {
Package[] pkgs = remoteSrc.getPackages();
if (pkgs == null) {
// This source has never been fetched. We'll do it below.
needsFetch = true;
} else {
// This source has already been fetched and we know its package list.
// We still need to make sure all of its packages are present in the
// remotePkgs list.
nextPackage: for (Package pkg : pkgs) {
for (Archive a : pkg.getArchives()) {
// Only add a package if it contains at least one compatible archive
// and is not already in the remote package list.
if (a.isCompatible()) {
if (!remotePkgs.contains(pkg)) {
remotePkgs.add(pkg);
continue nextPackage;
}
}
}
}
}
}
if (!needsFetch) {
return;
}
final boolean forceHttp = mUpdaterData.getSettingsController().getSettings().getForceHttp();
mUpdaterData.getTaskFactory().start("Refresh Sources", new ITask() {
@Override
public void run(ITaskMonitor monitor) {
for (SdkSource remoteSrc : remoteSources) {
Package[] pkgs = remoteSrc.getPackages();
if (pkgs == null) {
remoteSrc.load(mUpdaterData.getDownloadCache(), monitor, forceHttp);
pkgs = remoteSrc.getPackages();
}
if (pkgs != null) {
nextPackage: for (Package pkg : pkgs) {
for (Archive a : pkg.getArchives()) {
// Only add a package if it contains at least one compatible archive
// and is not already in the remote package list.
if (a.isCompatible()) {
if (!remotePkgs.contains(pkg)) {
remotePkgs.add(pkg);
continue nextPackage;
}
}
}
}
}
}
}
});
}
/**
* A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed
* "local" package/archive.
* <p/>
* In this case, the "new Archive" is still expected to be non null and the
* "replaced Archive" is null. Installed archives are always accepted and never
* rejected.
* <p/>
* Dependencies are not set.
*/
private static class LocalArchiveInfo extends ArchiveInfo {
public LocalArchiveInfo(Archive localArchive) {
super(localArchive, null /*replaced*/, null /*dependsOn*/);
}
/** Installed archives are always accepted. */
@Override
public boolean isAccepted() {
return true;
}
/** Installed archives are never rejected. */
@Override
public boolean isRejected() {
return false;
}
}
/**
* A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a
* package/archive that we <em>really</em> need as a dependency but that we don't have.
* <p/>
* This is currently used for addons and extras in case we can't find a matching base platform.
* <p/>
* This kind of archive has specific properties: the new archive to install is null,
* there are no dependencies and no archive is being replaced. The info can never be
* accepted and is always rejected.
*/
private static class MissingPlatformArchiveInfo extends ArchiveInfo {
private final AndroidVersion mVersion;
/**
* Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
* given platform version is missing.
*/
public MissingPlatformArchiveInfo(AndroidVersion version) {
super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
mVersion = version;
}
/** Missing archives are never accepted. */
@Override
public boolean isAccepted() {
return false;
}
/** Missing archives are always rejected. */
@Override
public boolean isRejected() {
return true;
}
@Override
public String getShortDescription() {
return String.format("Missing SDK Platform Android%1$s, API %2$d",
mVersion.isPreview() ? " Preview" : "",
mVersion.getApiLevel());
}
}
/**
* A {@link MissingArchiveInfo} is an {@link ArchiveInfo} that represents a
* package/archive that we <em>really</em> need as a dependency but that we don't have.
* <p/>
* This is currently used for extras in case we can't find a matching tool revision
* or when a platform-tool is missing.
* <p/>
* This kind of archive has specific properties: the new archive to install is null,
* there are no dependencies and no archive is being replaced. The info can never be
* accepted and is always rejected.
*/
private static class MissingArchiveInfo extends ArchiveInfo {
private final FullRevision mRevision;
private final String mTitle;
public static final String TITLE_TOOL = "Tools";
public static final String TITLE_PLATFORM_TOOL = "Platform-tools";
/**
* Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
* given platform version is missing.
*
* @param title Typically "Tools" or "Platform-tools".
* @param revision The required revision.
*/
public MissingArchiveInfo(String title, FullRevision revision) {
super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
mTitle = title;
mRevision = revision;
}
/** Missing archives are never accepted. */
@Override
public boolean isAccepted() {
return false;
}
/** Missing archives are always rejected. */
@Override
public boolean isRejected() {
return true;
}
@Override
public String getShortDescription() {
return String.format("Missing Android SDK %1$s, revision %2$s",
mTitle,
mRevision.toShortString());
}
}
}