OSDN Git Service

SDK Manager: fix displayed dependencies at installation.
[android-x86/sdk.git] / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / repository / UpdaterLogic.java
index ff4b9c5..14f3da8 100755 (executable)
@@ -19,50 +19,232 @@ package com.android.sdkuilib.internal.repository;
 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
@@ -85,118 +267,205 @@ public /* public for continuous tests */ class UpdaterLogic {
             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
@@ -204,73 +473,94 @@ public /* public for continuous tests */ class UpdaterLogic {
                     // 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
@@ -278,9 +568,13 @@ public /* public for continuous tests */ class UpdaterLogic {
                     // 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
@@ -288,22 +582,137 @@ public /* public for continuous tests */ class UpdaterLogic {
         }\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
@@ -320,4 +729,117 @@ public /* public for continuous tests */ class UpdaterLogic {
         }\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