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
* 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
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
* 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
String shortDescription,\r
String longDescription,\r
int minApiLevel,\r
+ int exactApiLevel,\r
String archiveOsPath) {\r
super( null, //source\r
props, //properties\r
);\r
mShortDescription = shortDescription;\r
mMinApiLevel = minApiLevel;\r
+ mExactApiLevel = exactApiLevel;\r
}\r
\r
/**\r
}\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
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
--- /dev/null
+/*
+ * 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();
+}
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();
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;
}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*\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
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
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
}\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
\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
}\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
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
}\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