import com.android.sdklib.AndroidVersion;\r
import com.android.sdklib.internal.repository.AddonPackage;\r
import com.android.sdklib.internal.repository.Archive;\r
+import com.android.sdklib.internal.repository.DocPackage;\r
+import com.android.sdklib.internal.repository.ExtraPackage;\r
+import com.android.sdklib.internal.repository.IMinApiLevelDependency;\r
+import com.android.sdklib.internal.repository.IMinToolsDependency;\r
+import com.android.sdklib.internal.repository.IPackageVersion;\r
+import com.android.sdklib.internal.repository.IPlatformDependency;\r
+import com.android.sdklib.internal.repository.MinToolsPackage;\r
import com.android.sdklib.internal.repository.Package;\r
import com.android.sdklib.internal.repository.PlatformPackage;\r
import com.android.sdklib.internal.repository.RepoSource;\r
import com.android.sdklib.internal.repository.RepoSources;\r
+import com.android.sdklib.internal.repository.SamplePackage;\r
import com.android.sdklib.internal.repository.ToolPackage;\r
import com.android.sdklib.internal.repository.Package.UpdateInfo;\r
\r
import java.util.ArrayList;\r
import java.util.Collection;\r
+import java.util.HashMap;\r
+\r
+/**\r
+ * The logic to compute which packages to install, based on the choices\r
+ * made by the user. This adds dependent packages as needed.\r
+ * <p/>\r
+ * When the user doesn't provide a selection, looks at local package to find\r
+ * those that can be updated and compute dependencies too.\r
+ */\r
+class UpdaterLogic {\r
\r
-public /* public for continuous tests */ class UpdaterLogic {\r
-\r
- private RepoSources mSources;\r
-\r
+ /**\r
+ * Compute which packages to install by taking the user selection\r
+ * and adding dependent packages as needed.\r
+ *\r
+ * When the user doesn't provide a selection, looks at local packages to find\r
+ * those that can be updated and compute dependencies too.\r
+ */\r
public ArrayList<ArchiveInfo> computeUpdates(\r
Collection<Archive> selectedArchives,\r
RepoSources sources,\r
Package[] localPkgs) {\r
\r
- mSources = sources;\r
ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();\r
ArrayList<Package> remotePkgs = new ArrayList<Package>();\r
+ RepoSource[] remoteSources = sources.getSources();\r
+\r
+ // Create ArchiveInfos out of local (installed) packages.\r
+ ArchiveInfo[] localArchives = createLocalArchives(localPkgs);\r
\r
if (selectedArchives == null) {\r
- selectedArchives = findUpdates(localPkgs, remotePkgs);\r
+ selectedArchives = findUpdates(localArchives, remotePkgs, remoteSources);\r
}\r
\r
for (Archive a : selectedArchives) {\r
- insertArchive(a, archives, selectedArchives, remotePkgs, localPkgs, false);\r
+ insertArchive(a,\r
+ archives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ false /*automated*/);\r
}\r
\r
return archives;\r
}\r
\r
+ /**\r
+ * Finds new packages that the user does not have in his/her local SDK\r
+ * and adds them to the list of archives to install.\r
+ */\r
+ public void addNewPlatforms(ArrayList<ArchiveInfo> archives,\r
+ RepoSources sources,\r
+ Package[] localPkgs) {\r
+\r
+ // Create ArchiveInfos out of local (installed) packages.\r
+ ArchiveInfo[] localArchives = createLocalArchives(localPkgs);\r
+\r
+ // Find the highest platform installed\r
+ float currentPlatformScore = 0;\r
+ float currentSampleScore = 0;\r
+ float currentAddonScore = 0;\r
+ float currentDocScore = 0;\r
+ HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();\r
+ for (Package p : localPkgs) {\r
+ int rev = p.getRevision();\r
+ int api = 0;\r
+ boolean isPreview = false;\r
+ if (p instanceof IPackageVersion) {\r
+ AndroidVersion vers = ((IPackageVersion) p).getVersion();\r
+ api = vers.getApiLevel();\r
+ isPreview = vers.isPreview();\r
+ }\r
+\r
+ // The score is 10*api + (1 if preview) + rev/100\r
+ // This allows previews to rank above a non-preview and\r
+ // allows revisions to rank appropriately.\r
+ float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;\r
+\r
+ if (p instanceof PlatformPackage) {\r
+ currentPlatformScore = Math.max(currentPlatformScore, score);\r
+ } else if (p instanceof SamplePackage) {\r
+ currentSampleScore = Math.max(currentSampleScore, score);\r
+ } else if (p instanceof AddonPackage) {\r
+ currentAddonScore = Math.max(currentAddonScore, score);\r
+ } else if (p instanceof ExtraPackage) {\r
+ currentExtraScore.put(((ExtraPackage) p).getPath(), score);\r
+ } else if (p instanceof DocPackage) {\r
+ currentDocScore = Math.max(currentDocScore, score);\r
+ }\r
+ }\r
+\r
+ RepoSource[] remoteSources = sources.getSources();\r
+ ArrayList<Package> remotePkgs = new ArrayList<Package>();\r
+ fetchRemotePackages(remotePkgs, remoteSources);\r
+\r
+ Package suggestedDoc = null;\r
+\r
+ for (Package p : remotePkgs) {\r
+ int rev = p.getRevision();\r
+ int api = 0;\r
+ boolean isPreview = false;\r
+ if (p instanceof IPackageVersion) {\r
+ AndroidVersion vers = ((IPackageVersion) p).getVersion();\r
+ api = vers.getApiLevel();\r
+ isPreview = vers.isPreview();\r
+ }\r
+\r
+ float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;\r
+\r
+ boolean shouldAdd = false;\r
+ if (p instanceof PlatformPackage) {\r
+ shouldAdd = score > currentPlatformScore;\r
+ } else if (p instanceof SamplePackage) {\r
+ shouldAdd = score > currentSampleScore;\r
+ } else if (p instanceof AddonPackage) {\r
+ shouldAdd = score > currentAddonScore;\r
+ } else if (p instanceof ExtraPackage) {\r
+ String key = ((ExtraPackage) p).getPath();\r
+ shouldAdd = !currentExtraScore.containsKey(key) ||\r
+ score > currentExtraScore.get(key).floatValue();\r
+ } else if (p instanceof DocPackage) {\r
+ // We don't want all the doc, only the most recent one\r
+ if (score > currentDocScore) {\r
+ suggestedDoc = p;\r
+ currentDocScore = score;\r
+ }\r
+ }\r
+\r
+ if (shouldAdd) {\r
+ // We should suggest this package for installation.\r
+ for (Archive a : p.getArchives()) {\r
+ if (a.isCompatible()) {\r
+ insertArchive(a,\r
+ archives,\r
+ null /*selectedArchives*/,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (suggestedDoc != null) {\r
+ // We should suggest this package for installation.\r
+ for (Archive a : suggestedDoc.getArchives()) {\r
+ if (a.isCompatible()) {\r
+ insertArchive(a,\r
+ archives,\r
+ null /*selectedArchives*/,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Create a array of {@link ArchiveInfo} based on all local (already installed)\r
+ * packages. The array is always non-null but may be empty.\r
+ * <p/>\r
+ * The local {@link ArchiveInfo} are guaranteed to have one non-null archive\r
+ * that you can retrieve using {@link ArchiveInfo#getNewArchive()}.\r
+ */\r
+ protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) {\r
+\r
+ if (localPkgs != null) {\r
+ ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();\r
+ for (Package p : localPkgs) {\r
+ // Only accept packages that have one compatible archive.\r
+ // Local package should have 1 and only 1 compatible archive anyway.\r
+ for (Archive a : p.getArchives()) {\r
+ if (a != null && a.isCompatible()) {\r
+ // We create an "installed" archive info to wrap the local package.\r
+ // Note that dependencies are not computed since right now we don't\r
+ // deal with more than one level of dependencies and installed archives\r
+ // are deemed implicitly accepted anyway.\r
+ list.add(new LocalArchiveInfo(a));\r
+ }\r
+ }\r
+ }\r
+\r
+ return list.toArray(new ArchiveInfo[list.size()]);\r
+ }\r
+\r
+ return new ArchiveInfo[0];\r
+ }\r
\r
/**\r
* Find suitable updates to all current local packages.\r
*/\r
- private Collection<Archive> findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs) {\r
+ private Collection<Archive> findUpdates(ArchiveInfo[] localArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ RepoSource[] remoteSources) {\r
ArrayList<Archive> updates = new ArrayList<Archive>();\r
\r
- fetchRemotePackages(remotePkgs);\r
+ fetchRemotePackages(remotePkgs, remoteSources);\r
+\r
+ for (ArchiveInfo ai : localArchives) {\r
+ Archive na = ai.getNewArchive();\r
+ if (na == null) {\r
+ continue;\r
+ }\r
+ Package localPkg = na.getParentPackage();\r
\r
- for (Package localPkg : localPkgs) {\r
for (Package remotePkg : remotePkgs) {\r
if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {\r
// Found a suitable update. Only accept the remote package\r
ArrayList<ArchiveInfo> outArchives,\r
Collection<Archive> selectedArchives,\r
ArrayList<Package> remotePkgs,\r
- Package[] localPkgs,\r
+ RepoSource[] remoteSources,\r
+ ArchiveInfo[] localArchives,\r
boolean automated) {\r
Package p = archive.getParentPackage();\r
\r
// Is this an update?\r
Archive updatedArchive = null;\r
- for (Package lp : localPkgs) {\r
- assert lp.getArchives().length == 1;\r
- if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {\r
- updatedArchive = lp.getArchives()[0];\r
+ for (ArchiveInfo ai : localArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package lp = a.getParentPackage();\r
+\r
+ if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {\r
+ updatedArchive = a;\r
+ }\r
}\r
}\r
\r
- // find dependencies\r
- ArchiveInfo dep = findDependency(p, outArchives, selectedArchives, remotePkgs, localPkgs);\r
+ // Find dependencies\r
+ ArchiveInfo[] deps = findDependency(p,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives);\r
+\r
+ // Make sure it's not a dup\r
+ ArchiveInfo ai = null;\r
+\r
+ for (ArchiveInfo ai2 : outArchives) {\r
+ Archive a2 = ai2.getNewArchive();\r
+ if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {\r
+ ai = ai2;\r
+ break;\r
+ }\r
+ }\r
\r
- ArchiveInfo ai = new ArchiveInfo(\r
- archive, //newArchive\r
+ if (ai == null) {\r
+ ai = new ArchiveInfo(\r
+ archive, //newArchive\r
updatedArchive, //replaced\r
- dep //dependsOn\r
+ deps //dependsOn\r
);\r
+ outArchives.add(ai);\r
+ }\r
\r
- outArchives.add(ai);\r
- if (dep != null) {\r
- dep.addDependencyFor(ai);\r
+ if (deps != null) {\r
+ for (ArchiveInfo d : deps) {\r
+ d.addDependencyFor(ai);\r
+ }\r
}\r
\r
return ai;\r
}\r
\r
- private ArchiveInfo findDependency(Package pkg,\r
+ /**\r
+ * Resolves dependencies for a given package.\r
+ *\r
+ * Returns null if no dependencies were found.\r
+ * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have\r
+ * at least size 1 and contain no null elements.\r
+ */\r
+ private ArchiveInfo[] findDependency(Package pkg,\r
ArrayList<ArchiveInfo> outArchives,\r
Collection<Archive> selectedArchives,\r
ArrayList<Package> remotePkgs,\r
- Package[] localPkgs) {\r
+ RepoSource[] remoteSources,\r
+ ArchiveInfo[] localArchives) {\r
\r
// Current dependencies can be:\r
// - addon: *always* depends on platform of same API level\r
// - platform: *might* depends on tools of rev >= min-tools-rev\r
+ // - extra: *might* depends on platform with api >= min-api-level\r
\r
- if (pkg instanceof AddonPackage) {\r
- AddonPackage addon = (AddonPackage) pkg;\r
+ ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();\r
+\r
+ if (pkg instanceof IPlatformDependency) {\r
+ ArchiveInfo ai = findPlatformDependency(\r
+ (IPlatformDependency) pkg,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives);\r
+\r
+ if (ai != null) {\r
+ list.add(ai);\r
+ }\r
+ }\r
\r
- return findAddonDependency(\r
- addon, outArchives, selectedArchives, remotePkgs, localPkgs);\r
+ if (pkg instanceof IMinToolsDependency) {\r
\r
- } else if (pkg instanceof PlatformPackage) {\r
- PlatformPackage platform = (PlatformPackage) pkg;\r
+ ArchiveInfo ai = findToolsDependency(\r
+ (IMinToolsDependency) pkg,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives);\r
\r
- return findPlatformDependency(\r
- platform, outArchives, selectedArchives, remotePkgs, localPkgs);\r
+ if (ai != null) {\r
+ list.add(ai);\r
+ }\r
+ }\r
+\r
+ if (pkg instanceof IMinApiLevelDependency) {\r
+\r
+ ArchiveInfo ai = findMinApiLevelDependency(\r
+ (IMinApiLevelDependency) pkg,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives);\r
+\r
+ if (ai != null) {\r
+ list.add(ai);\r
+ }\r
+ }\r
+\r
+ if (list.size() > 0) {\r
+ return list.toArray(new ArchiveInfo[list.size()]);\r
}\r
\r
return null;\r
}\r
\r
/**\r
- * A platform can have a min-tools-rev, in which case it depends on having a tools package\r
- * of the requested revision.\r
+ * Resolves dependencies on tools.\r
+ *\r
+ * A platform or an extra package can both have a min-tools-rev, in which case it\r
+ * depends on having a tools package of the requested revision.\r
* Finds the tools dependency. If found, add it to the list of things to install.\r
* Returns the archive info dependency, if any.\r
*/\r
- protected ArchiveInfo findPlatformDependency(PlatformPackage platform,\r
+ protected ArchiveInfo findToolsDependency(\r
+ IMinToolsDependency pkg,\r
ArrayList<ArchiveInfo> outArchives,\r
Collection<Archive> selectedArchives,\r
ArrayList<Package> remotePkgs,\r
- Package[] localPkgs) {\r
+ RepoSource[] remoteSources,\r
+ ArchiveInfo[] localArchives) {\r
// This is the requirement to match.\r
- int rev = platform.getMinToolsRevision();\r
+ int rev = pkg.getMinToolsRevision();\r
\r
- if (rev == PlatformPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {\r
+ if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {\r
// Well actually there's no requirement.\r
return null;\r
}\r
\r
- // First look in local packages.\r
- for (Package p : localPkgs) {\r
- if (p instanceof ToolPackage) {\r
- if (((ToolPackage) p).getRevision() >= rev) {\r
- // We found one already installed. We don't report this dependency\r
- // as the UI only cares about resolving "newly added dependencies".\r
- return null;\r
+ // First look in locally installed packages.\r
+ for (ArchiveInfo ai : localArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof ToolPackage) {\r
+ if (((ToolPackage) p).getRevision() >= rev) {\r
+ // We found one already installed.\r
+ return null;\r
+ }\r
}\r
}\r
}\r
\r
// Look in archives already scheduled for install\r
for (ArchiveInfo ai : outArchives) {\r
- Package p = ai.getNewArchive().getParentPackage();\r
- if (p instanceof PlatformPackage) {\r
- if (((ToolPackage) p).getRevision() >= rev) {\r
- // The dependency is already scheduled for install, nothing else to do.\r
- return ai;\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof ToolPackage) {\r
+ if (((ToolPackage) p).getRevision() >= rev) {\r
+ // The dependency is already scheduled for install, nothing else to do.\r
+ return ai;\r
+ }\r
}\r
}\r
}\r
\r
// Otherwise look in the selected archives.\r
- for (Archive a : selectedArchives) {\r
- Package p = a.getParentPackage();\r
- if (p instanceof ToolPackage) {\r
- if (((ToolPackage) p).getRevision() >= rev) {\r
- // It's not already in the list of things to install, so add it now\r
- return insertArchive(a, outArchives,\r
- selectedArchives, remotePkgs, localPkgs,\r
- true);\r
+ if (selectedArchives != null) {\r
+ for (Archive a : selectedArchives) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof ToolPackage) {\r
+ if (((ToolPackage) p).getRevision() >= rev) {\r
+ // It's not already in the list of things to install, so add it now\r
+ return insertArchive(a,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
+ }\r
}\r
}\r
}\r
\r
// Finally nothing matched, so let's look at all available remote packages\r
- fetchRemotePackages(remotePkgs);\r
+ fetchRemotePackages(remotePkgs, remoteSources);\r
for (Package p : remotePkgs) {\r
if (p instanceof ToolPackage) {\r
if (((ToolPackage) p).getRevision() >= rev) {\r
// first compatible archive we can find.\r
for (Archive a : p.getArchives()) {\r
if (a.isCompatible()) {\r
- return insertArchive(a, outArchives,\r
- selectedArchives, remotePkgs, localPkgs,\r
- true);\r
+ return insertArchive(a,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
}\r
}\r
}\r
}\r
}\r
\r
- // We end up here if nothing matches. We don't have a good tools to match.\r
- // Seriously, that can't happens unless we totally screwed our repo manifest.\r
- // We'll let this one go through anyway.\r
- return null;\r
+ // We end up here if nothing matches. We don't have a good platform to match.\r
+ // We need to indicate this extra depends on a missing platform archive\r
+ // so that it can be impossible to install later on.\r
+ return new MissingToolArchiveInfo(rev);\r
}\r
\r
/**\r
- * An addon depends on having a platform with the same API version.\r
+ * Resolves dependencies on platform for an addon.\r
+ *\r
+ * An addon depends on having a platform with the same API level.\r
+ *\r
* Finds the platform dependency. If found, add it to the list of things to install.\r
* Returns the archive info dependency, if any.\r
*/\r
- protected ArchiveInfo findAddonDependency(AddonPackage addon,\r
+ protected ArchiveInfo findPlatformDependency(\r
+ IPlatformDependency pkg,\r
ArrayList<ArchiveInfo> outArchives,\r
Collection<Archive> selectedArchives,\r
ArrayList<Package> remotePkgs,\r
- Package[] localPkgs) {\r
+ RepoSource[] remoteSources,\r
+ ArchiveInfo[] localArchives) {\r
// This is the requirement to match.\r
- AndroidVersion v = addon.getVersion();\r
+ AndroidVersion v = pkg.getVersion();\r
\r
// Find a platform that would satisfy the requirement.\r
\r
- // First look in local packages.\r
- for (Package p : localPkgs) {\r
- if (p instanceof PlatformPackage) {\r
- if (v.equals(((PlatformPackage) p).getVersion())) {\r
- // We found one already installed. We don't report this dependency\r
- // as the UI only cares about resolving "newly added dependencies".\r
- return null;\r
+ // First look in locally installed packages.\r
+ for (ArchiveInfo ai : localArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (v.equals(((PlatformPackage) p).getVersion())) {\r
+ // We found one already installed.\r
+ return null;\r
+ }\r
}\r
}\r
}\r
\r
// Look in archives already scheduled for install\r
for (ArchiveInfo ai : outArchives) {\r
- Package p = ai.getNewArchive().getParentPackage();\r
- if (p instanceof PlatformPackage) {\r
- if (v.equals(((PlatformPackage) p).getVersion())) {\r
- // The dependency is already scheduled for install, nothing else to do.\r
- return ai;\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (v.equals(((PlatformPackage) p).getVersion())) {\r
+ // The dependency is already scheduled for install, nothing else to do.\r
+ return ai;\r
+ }\r
}\r
}\r
}\r
\r
// Otherwise look in the selected archives.\r
- for (Archive a : selectedArchives) {\r
- Package p = a.getParentPackage();\r
- if (p instanceof PlatformPackage) {\r
- if (v.equals(((PlatformPackage) p).getVersion())) {\r
- // It's not already in the list of things to install, so add it now\r
- return insertArchive(a, outArchives,\r
- selectedArchives, remotePkgs, localPkgs,\r
- true);\r
+ if (selectedArchives != null) {\r
+ for (Archive a : selectedArchives) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (v.equals(((PlatformPackage) p).getVersion())) {\r
+ // It's not already in the list of things to install, so add it now\r
+ return insertArchive(a,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
+ }\r
}\r
}\r
}\r
\r
// Finally nothing matched, so let's look at all available remote packages\r
- fetchRemotePackages(remotePkgs);\r
+ fetchRemotePackages(remotePkgs, remoteSources);\r
for (Package p : remotePkgs) {\r
if (p instanceof PlatformPackage) {\r
if (v.equals(((PlatformPackage) p).getVersion())) {\r
// first compatible archive we can find.\r
for (Archive a : p.getArchives()) {\r
if (a.isCompatible()) {\r
- return insertArchive(a, outArchives,\r
- selectedArchives, remotePkgs, localPkgs,\r
- true);\r
+ return insertArchive(a,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
}\r
}\r
}\r
}\r
\r
// We end up here if nothing matches. We don't have a good platform to match.\r
- // Seriously, that can't happens unless the repository contains a bogus addon\r
- // entry that does not match any existing platform API level.\r
- // It's conceivable that a 3rd part addon repo might have error, in which case\r
- // we'll let this one go through anyway.\r
- return null;\r
+ // We need to indicate this addon depends on a missing platform archive\r
+ // so that it can be impossible to install later on.\r
+ return new MissingPlatformArchiveInfo(pkg.getVersion());\r
+ }\r
+\r
+ /**\r
+ * Resolves platform dependencies for extras.\r
+ * An extra depends on having a platform with a minimun API level.\r
+ *\r
+ * We try to return the highest API level available above the specified minimum.\r
+ * Note that installed packages have priority so if one installed platform satisfies\r
+ * the dependency, we'll use it even if there's a higher API platform available but\r
+ * not installed yet.\r
+ *\r
+ * Finds the platform dependency. If found, add it to the list of things to install.\r
+ * Returns the archive info dependency, if any.\r
+ */\r
+ protected ArchiveInfo findMinApiLevelDependency(\r
+ IMinApiLevelDependency pkg,\r
+ ArrayList<ArchiveInfo> outArchives,\r
+ Collection<Archive> selectedArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ RepoSource[] remoteSources,\r
+ ArchiveInfo[] localArchives) {\r
+\r
+ int api = pkg.getMinApiLevel();\r
+\r
+ if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) {\r
+ return null;\r
+ }\r
+\r
+ // Find a platform that would satisfy the requirement.\r
+\r
+ // First look in locally installed packages.\r
+ for (ArchiveInfo ai : localArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {\r
+ // We found one already installed.\r
+ return null;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Look in archives already scheduled for install\r
+ int foundApi = 0;\r
+ ArchiveInfo foundAi = null;\r
+\r
+ for (ArchiveInfo ai : outArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ if (a != null) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {\r
+ if (api > foundApi) {\r
+ foundApi = api;\r
+ foundAi = ai;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (foundAi != null) {\r
+ // The dependency is already scheduled for install, nothing else to do.\r
+ return foundAi;\r
+ }\r
+\r
+ // Otherwise look in the selected archives *or* available remote packages\r
+ // and takes the best out of the two sets.\r
+ foundApi = 0;\r
+ Archive foundArchive = null;\r
+ if (selectedArchives != null) {\r
+ for (Archive a : selectedArchives) {\r
+ Package p = a.getParentPackage();\r
+ if (p instanceof PlatformPackage) {\r
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {\r
+ if (api > foundApi) {\r
+ foundApi = api;\r
+ foundArchive = a;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Finally nothing matched, so let's look at all available remote packages\r
+ fetchRemotePackages(remotePkgs, remoteSources);\r
+ for (Package p : remotePkgs) {\r
+ if (p instanceof PlatformPackage) {\r
+ if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {\r
+ if (api > foundApi) {\r
+ // It's not already in the list of things to install, so add the\r
+ // first compatible archive we can find.\r
+ for (Archive a : p.getArchives()) {\r
+ if (a.isCompatible()) {\r
+ foundApi = api;\r
+ foundArchive = a;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (foundArchive != null) {\r
+ // It's not already in the list of things to install, so add it now\r
+ return insertArchive(foundArchive,\r
+ outArchives,\r
+ selectedArchives,\r
+ remotePkgs,\r
+ remoteSources,\r
+ localArchives,\r
+ true /*automated*/);\r
+ }\r
+\r
+ // We end up here if nothing matches. We don't have a good platform to match.\r
+ // We need to indicate this extra depends on a missing platform archive\r
+ // so that it can be impossible to install later on.\r
+ return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));\r
}\r
\r
/** Fetch all remote packages only if really needed. */\r
- protected void fetchRemotePackages(ArrayList<Package> remotePkgs) {\r
+ protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) {\r
if (remotePkgs.size() > 0) {\r
return;\r
}\r
\r
- // Get all the available packages from all loaded sources\r
- RepoSource[] remoteSources = mSources.getSources();\r
-\r
for (RepoSource remoteSrc : remoteSources) {\r
Package[] pkgs = remoteSrc.getPackages();\r
if (pkgs != null) {\r
}\r
}\r
\r
+\r
+ /**\r
+ * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed\r
+ * "local" package/archive.\r
+ * <p/>\r
+ * In this case, the "new Archive" is still expected to be non null and the\r
+ * "replaced Archive" isnull. Installed archives are always accepted and never\r
+ * rejected.\r
+ * <p/>\r
+ * Dependencies are not set.\r
+ */\r
+ private static class LocalArchiveInfo extends ArchiveInfo {\r
+\r
+ public LocalArchiveInfo(Archive localArchive) {\r
+ super(localArchive, null /*replaced*/, null /*dependsOn*/);\r
+ }\r
+\r
+ /** Installed archives are always accepted. */\r
+ @Override\r
+ public boolean isAccepted() {\r
+ return true;\r
+ }\r
+\r
+ /** Installed archives are never rejected. */\r
+ @Override\r
+ public boolean isRejected() {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a\r
+ * package/archive that we <em>really</em> need as a dependency but that we don't have.\r
+ * <p/>\r
+ * This is currently used for addons and extras in case we can't find a matching base platform.\r
+ * <p/>\r
+ * This kind of archive has specific properties: the new archive to install is null,\r
+ * there are no dependencies and no archive is being replaced. The info can never be\r
+ * accepted and is always rejected.\r
+ */\r
+ private static class MissingPlatformArchiveInfo extends ArchiveInfo {\r
+\r
+ private final AndroidVersion mVersion;\r
+\r
+ /**\r
+ * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the\r
+ * given platform version is missing.\r
+ */\r
+ public MissingPlatformArchiveInfo(AndroidVersion version) {\r
+ super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);\r
+ mVersion = version;\r
+ }\r
+\r
+ /** Missing archives are never accepted. */\r
+ @Override\r
+ public boolean isAccepted() {\r
+ return false;\r
+ }\r
+\r
+ /** Missing archives are always rejected. */\r
+ @Override\r
+ public boolean isRejected() {\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public String getShortDescription() {\r
+ return String.format("Missing SDK Platform Android%1$s, API %2$d",\r
+ mVersion.isPreview() ? " Preview" : "",\r
+ mVersion.getApiLevel());\r
+ }\r
+ }\r
+\r
+ /**\r
+ * A {@link MissingToolArchiveInfo} is an {@link ArchiveInfo} that represents a\r
+ * package/archive that we <em>really</em> need as a dependency but that we don't have.\r
+ * <p/>\r
+ * This is currently used for extras in case we can't find a matching tool revision.\r
+ * <p/>\r
+ * This kind of archive has specific properties: the new archive to install is null,\r
+ * there are no dependencies and no archive is being replaced. The info can never be\r
+ * accepted and is always rejected.\r
+ */\r
+ private static class MissingToolArchiveInfo extends ArchiveInfo {\r
+\r
+ private final int mRevision;\r
+\r
+ /**\r
+ * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the\r
+ * given platform version is missing.\r
+ */\r
+ public MissingToolArchiveInfo(int revision) {\r
+ super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);\r
+ mRevision = revision;\r
+ }\r
+\r
+ /** Missing archives are never accepted. */\r
+ @Override\r
+ public boolean isAccepted() {\r
+ return false;\r
+ }\r
+\r
+ /** Missing archives are always rejected. */\r
+ @Override\r
+ public boolean isRejected() {\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public String getShortDescription() {\r
+ return String.format("Missing Android SDK Tools, revision %1$d", mRevision);\r
+ }\r
+ }\r
}\r