OSDN Git Service

SdkManager: suggest which platform to install to fix a broken addon.
authorRaphael Moll <ralf@android.com>
Fri, 7 Jan 2011 08:41:14 +0000 (00:41 -0800)
committerRaphael Moll <ralf@android.com>
Fri, 7 Jan 2011 20:50:16 +0000 (12:50 -0800)
The SDK Manager now has the notion of a "broken installed package".
The BrokenPackage can specify that:
- it requires a certain minimal platform to be installed,
and/or:
- it requires a specific exact platform to be installed.

The later constraint is expressed by IExactApiLevelDependency and
allows UpdaterLogic to find which platform would fix an addon which
is missing its base platform.

Change-Id: If429ea39f0ddc19c0cb906bf6766df310de28981

sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IExactApiLevelDependency.java [new file with mode: 0755]
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IMinApiLevelDependency.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java
sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/BrokenPackageTest.java [new file with mode: 0755]
sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockBrokenPackage.java [new file with mode: 0755]
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java
sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java

index 8226a60..ab27561 100755 (executable)
@@ -38,7 +38,7 @@ import java.util.Properties;
  * Represents an add-on XML node in an SDK repository.\r
  */\r
 public class AddonPackage extends Package\r
-    implements IPackageVersion, IPlatformDependency {\r
+    implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency {\r
 \r
     private static final String PROP_NAME      = "Addon.Name";      //$NON-NLS-1$\r
     private static final String PROP_VENDOR    = "Addon.Vendor";    //$NON-NLS-1$\r
@@ -157,15 +157,22 @@ public class AddonPackage extends Package
                 shortDesc,\r
                 error);\r
 \r
-        int minApiLevel = IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED;\r
+        int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;\r
 \r
         try {\r
-            minApiLevel = Integer.parseInt(api);\r
+            apiLevel = Integer.parseInt(api);\r
         } catch(NumberFormatException e) {\r
             // ignore\r
         }\r
 \r
-        return new BrokenPackage(null/*props*/, shortDesc, longDesc, minApiLevel, archiveOsPath);\r
+        return new BrokenPackage(null/*props*/, shortDesc, longDesc,\r
+                IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,\r
+                apiLevel,\r
+                archiveOsPath);\r
+    }\r
+\r
+    public int getExactApiLevel() {\r
+        return mVersion.getApiLevel();\r
     }\r
 \r
     /**\r
index 646172d..1629045 100755 (executable)
@@ -27,14 +27,21 @@ import java.util.Properties;
  * Represents an SDK repository package that is incomplete.\r
  * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.\r
  */\r
-public class BrokenPackage extends Package implements IMinApiLevelDependency {\r
+public class BrokenPackage extends Package\r
+        implements IExactApiLevelDependency, IMinApiLevelDependency {\r
 \r
     /**\r
-     * The minimal API level required by this extra package, if > 0,\r
+     * The minimal API level required by this package, if > 0,\r
      * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.\r
      */\r
     private final int mMinApiLevel;\r
 \r
+    /**\r
+     * The exact API level required by this package, if > 0,\r
+     * or {@link #API_LEVEL_INVALID} if there is no such requirement.\r
+     */\r
+    private final int mExactApiLevel;\r
+\r
     private final String mShortDescription;\r
 \r
     /**\r
@@ -48,6 +55,7 @@ public class BrokenPackage extends Package implements IMinApiLevelDependency {
             String shortDescription,\r
             String longDescription,\r
             int minApiLevel,\r
+            int exactApiLevel,\r
             String archiveOsPath) {\r
         super(  null,                                   //source\r
                 props,                                  //properties\r
@@ -61,6 +69,7 @@ public class BrokenPackage extends Package implements IMinApiLevelDependency {
                 );\r
         mShortDescription = shortDescription;\r
         mMinApiLevel = minApiLevel;\r
+        mExactApiLevel = exactApiLevel;\r
     }\r
 \r
     /**\r
@@ -75,13 +84,21 @@ public class BrokenPackage extends Package implements IMinApiLevelDependency {
     }\r
 \r
     /**\r
-     * Returns the minimal API level required by this extra package, if > 0,\r
+     * Returns the minimal API level required by this package, if > 0,\r
      * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.\r
      */\r
     public int getMinApiLevel() {\r
         return mMinApiLevel;\r
     }\r
 \r
+    /**\r
+     * Returns the exact API level required by this package, if > 0,\r
+     * or {@link #API_LEVEL_INVALID} if the value was missing.\r
+     */\r
+    public int getExactApiLevel() {\r
+        return mExactApiLevel;\r
+    }\r
+\r
     /** Returns a short description for an {@link IDescription}. */\r
     @Override\r
     public String getShortDescription() {\r
index cac622d..9c94800 100755 (executable)
@@ -113,7 +113,9 @@ public class ExtraPackage extends MinToolsPackage
                     ep.getPath());\r
 \r
             BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc,\r
-                    IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED, archiveOsPath);\r
+                    ep.getMinApiLevel(),\r
+                    IExactApiLevelDependency.API_LEVEL_INVALID,\r
+                    archiveOsPath);\r
             return ba;\r
         }\r
     }\r
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IExactApiLevelDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/IExactApiLevelDependency.java
new file mode 100755 (executable)
index 0000000..2a0130c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.sdklib.repository.RepoConstants;
+
+/**
+ * Interface used to decorate a {@link Package} that has a dependency
+ * on a specific API level, e.g. which XML has a {@code <api-level>} element.
+ * <p/>
+ * For example an add-on package requires a platform with an exact API level to be installed
+ * at the same time.
+ * This is not the same as {@link IMinApiLevelDependency} which requests that a platform with at
+ * least the requested API level be present or installed at the same time.
+ * <p/>
+ * Such package requires the {@code <api-level>} element. It is not an optional
+ * property, however it can be invalid.
+ */
+public interface IExactApiLevelDependency {
+
+    /**
+     * The value of {@link #getExactApiLevel()} when the {@link RepoConstants#NODE_API_LEVEL}
+     * was not specified in the XML source.
+     */
+    public static final int API_LEVEL_INVALID = 0;
+
+    /**
+     * Returns the exact API level required by this package, if > 0,
+     * or {@link #API_LEVEL_INVALID} if the value was missing.
+     * <p/>
+     * This attribute is mandatory and should not be normally missing.
+     * It can only happen when dealing with an invalid repository XML.
+     */
+    public abstract int getExactApiLevel();
+}
index 14e6744..e23f3b6 100755 (executable)
@@ -34,7 +34,7 @@ public interface IMinApiLevelDependency {
     public static final int MIN_API_LEVEL_NOT_SPECIFIED = 0;
 
     /**
-     * Returns the minimal API level required by this extra package, if > 0,
+     * Returns the minimal API level required by this package, if > 0,
      * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
      */
     public abstract int getMinApiLevel();
index 7b0494f..c31325f 100755 (executable)
@@ -111,7 +111,9 @@ public class PlatformToolPackage extends Package {
                     error);
 
             BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc,
-                    IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED, archiveOsPath);
+                    IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
+                    IExactApiLevelDependency.API_LEVEL_INVALID,
+                    archiveOsPath);
             return ba;
         }
 
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/BrokenPackageTest.java b/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/BrokenPackageTest.java
new file mode 100755 (executable)
index 0000000..3e9ba8d
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import junit.framework.TestCase;
+
+public class BrokenPackageTest extends TestCase {
+
+    private BrokenPackage m;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        m = new BrokenPackage(null /*props*/,
+                "short description",
+                "long description",
+                12, // min api level
+                13, // exact api level
+                "os/path");
+
+    }
+
+    public final void testGetShortDescription() {
+        assertEquals("short description", m.getShortDescription());
+    }
+
+    public final void testGetLongDescription() {
+        assertEquals("long description", m.getLongDescription());
+    }
+
+    public final void testGetMinApiLevel() {
+        assertEquals(12, m.getMinApiLevel());
+    }
+
+    public final void testGetExactApiLevel() {
+        assertEquals(13, m.getExactApiLevel());
+    }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockBrokenPackage.java b/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockBrokenPackage.java
new file mode 100755 (executable)
index 0000000..289305b
--- /dev/null
@@ -0,0 +1,43 @@
+/*\r
+ * Copyright (C) 2011 The Android Open Source Project\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.android.sdklib.internal.repository;\r
+\r
+\r
+/**\r
+ * A mock {@link BrokenPackage} for testing.\r
+ * <p/>\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+public class MockBrokenPackage extends BrokenPackage {\r
+\r
+    public MockBrokenPackage(int minApiLevel, int exactApiLevel) {\r
+        this(null, null, minApiLevel, exactApiLevel);\r
+    }\r
+\r
+    public MockBrokenPackage(\r
+            String shortDescription,\r
+            String longDescription,\r
+            int minApiLevel,\r
+            int exactApiLevel) {\r
+        super(null /*props*/,\r
+                shortDescription,\r
+                longDescription,\r
+                minApiLevel,\r
+                exactApiLevel,\r
+                null /*osPath*/);\r
+    }\r
+}\r
index f57213c..505a613 100755 (executable)
@@ -21,6 +21,7 @@ import com.android.sdklib.internal.repository.AddonPackage;
 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.IExactApiLevelDependency;\r
 import com.android.sdklib.internal.repository.IMinApiLevelDependency;\r
 import com.android.sdklib.internal.repository.IMinPlatformToolsDependency;\r
 import com.android.sdklib.internal.repository.IMinToolsDependency;\r
@@ -32,9 +33,9 @@ import com.android.sdklib.internal.repository.MinToolsPackage;
 import com.android.sdklib.internal.repository.Package;\r
 import com.android.sdklib.internal.repository.PlatformPackage;\r
 import com.android.sdklib.internal.repository.PlatformToolPackage;\r
+import com.android.sdklib.internal.repository.SamplePackage;\r
 import com.android.sdklib.internal.repository.SdkSource;\r
 import com.android.sdklib.internal.repository.SdkSources;\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
@@ -501,6 +502,21 @@ class UpdaterLogic {
             }\r
         }\r
 \r
+        if (pkg instanceof IExactApiLevelDependency) {\r
+\r
+            ArchiveInfo ai = findExactApiLevelDependency(\r
+                    (IExactApiLevelDependency) 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
@@ -864,7 +880,7 @@ class UpdaterLogic {
 \r
         int api = pkg.getMinApiLevel();\r
 \r
-        if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) {\r
+        if (api == IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED) {\r
             return null;\r
         }\r
 \r
@@ -963,6 +979,104 @@ class UpdaterLogic {
     }\r
 \r
     /**\r
+     * Resolves platform dependencies for add-ons.\r
+     * An add-ons depends on having a platform with an exact specific 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 findExactApiLevelDependency(\r
+            IExactApiLevelDependency pkg,\r
+            ArrayList<ArchiveInfo> outArchives,\r
+            Collection<Archive> selectedArchives,\r
+            ArrayList<Package> remotePkgs,\r
+            SdkSource[] remoteSources,\r
+            ArchiveInfo[] localArchives) {\r
+\r
+        int api = pkg.getExactApiLevel();\r
+\r
+        if (api == IExactApiLevelDependency.API_LEVEL_INVALID) {\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().equals(api)) {\r
+                        // We found one already installed.\r
+                        return null;\r
+                    }\r
+                }\r
+            }\r
+        }\r
+\r
+        // Look in archives already scheduled for install\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().equals(api)) {\r
+                        return ai;\r
+                    }\r
+                }\r
+            }\r
+        }\r
+\r
+        // Otherwise look in the selected archives.\r
+        if (selectedArchives != null) {\r
+            for (Archive a : selectedArchives) {\r
+                Package p = a.getParentPackage();\r
+                if (p instanceof PlatformPackage) {\r
+                    if (((PlatformPackage) p).getVersion().equals(api)) {\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, remoteSources);\r
+        for (Package p : remotePkgs) {\r
+            if (p instanceof PlatformPackage) {\r
+                if (((PlatformPackage) p).getVersion().equals(api)) {\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
+                            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 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
+    /**\r
      * Fetch all remote packages only if really needed.\r
      * <p/>\r
      * This method takes a list of sources. Each source is only fetched once -- that is each\r
index 94fac06..dc65e14 100755 (executable)
@@ -22,6 +22,7 @@ import com.android.sdklib.internal.avd.AvdManager;
 import com.android.sdklib.internal.repository.Archive;\r
 import com.android.sdklib.internal.repository.ITaskFactory;\r
 import com.android.sdklib.internal.repository.MockAddonPackage;\r
+import com.android.sdklib.internal.repository.MockBrokenPackage;\r
 import com.android.sdklib.internal.repository.MockPlatformPackage;\r
 import com.android.sdklib.internal.repository.MockPlatformToolPackage;\r
 import com.android.sdklib.internal.repository.MockToolPackage;\r
@@ -132,6 +133,54 @@ public class UpdaterLogicTest extends TestCase {
     }\r
 \r
     /**\r
+     * Broken add-on packages require an exact platform package to be present or installed.\r
+     * This tests checks that findExactApiLevelDependency() can find a base\r
+     * platform package for a given broken add-on package.\r
+     */\r
+    public void testFindExactApiLevelDependency() {\r
+        MockUpdaterLogic mul = new MockUpdaterLogic(new NullUpdaterData(), null);\r
+\r
+        MockPlatformPackage p1 = new MockPlatformPackage(1, 1);\r
+        MockPlatformPackage p2 = new MockPlatformPackage(2, 1);\r
+\r
+        MockBrokenPackage a1 = new MockBrokenPackage(0, 1);\r
+        MockBrokenPackage a2 = new MockBrokenPackage(0, 2);\r
+\r
+        ArrayList<ArchiveInfo> out = new ArrayList<ArchiveInfo>();\r
+        ArrayList<Archive> selected = new ArrayList<Archive>();\r
+        ArrayList<Package> remote = new ArrayList<Package>();\r
+\r
+        // a2 depends on p2, which is not in the locals\r
+        Package[] localPkgs = { p1, a1 };\r
+        ArchiveInfo[] locals = mul.createLocalArchives(localPkgs);\r
+\r
+        SdkSource[] sources = null;\r
+\r
+        // a1 depends on p1, which can be found in the locals. p1 is already "installed"\r
+        // so we donn't need to suggest it as a dependency to solve any problem.\r
+        ArchiveInfo found = mul.findExactApiLevelDependency(\r
+                a1, out, selected, remote, sources, locals);\r
+        assertNull(found);\r
+\r
+        // a2 now depends on a "fake" archive info with no newArchive that wraps the missing\r
+        // underlying platform.\r
+        found = mul.findExactApiLevelDependency(a2, out, selected, remote, sources, locals);\r
+        assertNotNull(found);\r
+        assertNull(found.getNewArchive());\r
+        assertTrue(found.isRejected());\r
+        assertEquals(0, out.size());\r
+\r
+        // p2 is now selected, and should be scheduled for install in out\r
+        Archive p2_archive = p2.getArchives()[0];\r
+        selected.add(p2_archive);\r
+        found = mul.findExactApiLevelDependency(a2, out, selected, remote, sources, locals);\r
+        assertNotNull(found);\r
+        assertSame(p2_archive, found.getNewArchive());\r
+        assertEquals(1, out.size());\r
+        assertSame(p2_archive, out.get(0).getNewArchive());\r
+    }\r
+\r
+    /**\r
      * Platform packages depend on a tool package.\r
      * This tests checks that UpdaterLogic.findToolsDependency() can find a base\r
      * tool package for a given platform package.\r