public class AndroidVersion {
private static final String PROP_API_LEVEL = "AndroidVersion.ApiLevel"; //$NON-NLS-1$
- private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
+ private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
private final int mApiLevel;
private final String mCodename;
/**
* Creates an {@link AndroidVersion} with the given api level and codename.
+ * Codename should be null for a release version, otherwise it's a preview codename.
*/
public AndroidVersion(int apiLevel, String codename) {
mApiLevel = apiLevel;
* {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.\r
* This is used to list local SDK folders in which case there is one archive which\r
* URL is the actual target location.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
AddonPackage(IAndroidTarget target, Properties props) {\r
super( null, //source\r
* Manually create a new package with one archive and the given attributes.\r
* This is used to create packages from local directories in which case there must be\r
* one archive which URL is the actual target location.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
DocPackage(RepoSource source,\r
Properties props,\r
* Manually create a new package with one archive and the given attributes or properties.\r
* This is used to create packages from local directories in which case there must be\r
* one archive which URL is the actual target location.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
ExtraPackage(RepoSource source,\r
Properties props,\r
* Manually create a new package with one archive and the given attributes.\r
* This is used to create packages from local directories in which case there must be\r
* one archive which URL is the actual target location.\r
- *\r
+ * <p/>\r
* Properties from props are used first when possible, e.g. if props is non null.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
public Package(\r
RepoSource source,\r
*/\r
public class PlatformPackage extends Package {\r
\r
- private static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$\r
- private static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$\r
+ protected static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$\r
+ protected static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$\r
\r
/** The package version, for platform, add-on and doc packages. */\r
private final AndroidVersion mVersion;\r
* must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.\r
* This is used to list local SDK folders in which case there is one archive which\r
* URL is the actual target location.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
PlatformPackage(IAndroidTarget target, Properties props) {\r
super( null, //source\r
* Manually create a new package with one archive and the given attributes or properties.\r
* This is used to create packages from local directories in which case there must be\r
* one archive which URL is the actual target location.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
*/\r
ToolPackage(\r
RepoSource source,\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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
+import com.android.sdklib.AndroidVersion;\r
+import com.android.sdklib.IAndroidTarget;\r
+\r
+/**\r
+ * A mock {@link AddonPackage} for testing.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+public class MockAddonPackage extends AddonPackage {\r
+\r
+ /**\r
+ * Creates a {@link MockAddonTarget} with the requested base platform and addon revision\r
+ * and then a {@link MockAddonPackage} wrapping it.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+ public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {\r
+ super(new MockAddonTarget(basePlatform.getTarget(), revision), null /*props*/);\r
+ }\r
+\r
+ /**\r
+ * A mock AddonTarget.\r
+ * This reimplements the minimum needed from the interface for our limited testing needs.\r
+ */\r
+ static class MockAddonTarget implements IAndroidTarget {\r
+\r
+ private final IAndroidTarget mParentTarget;\r
+ private final int mRevision;\r
+\r
+ public MockAddonTarget(IAndroidTarget parentTarget, int revision) {\r
+ mParentTarget = parentTarget;\r
+ mRevision = revision;\r
+ }\r
+\r
+ public String getClasspathName() {\r
+ return null;\r
+ }\r
+\r
+ public String getDefaultSkin() {\r
+ return null;\r
+ }\r
+\r
+ public String getDescription() {\r
+ return "mock addon target";\r
+ }\r
+\r
+ public String getFullName() {\r
+ return "mock addon target";\r
+ }\r
+\r
+ public String getLocation() {\r
+ return "";\r
+ }\r
+\r
+ public String getName() {\r
+ return "mock addon target";\r
+ }\r
+\r
+ public IOptionalLibrary[] getOptionalLibraries() {\r
+ return null;\r
+ }\r
+\r
+ public IAndroidTarget getParent() {\r
+ return mParentTarget;\r
+ }\r
+\r
+ public String getPath(int pathId) {\r
+ return null;\r
+ }\r
+\r
+ public String[] getPlatformLibraries() {\r
+ return null;\r
+ }\r
+\r
+ public int getRevision() {\r
+ return mRevision;\r
+ }\r
+\r
+ public String[] getSkins() {\r
+ return null;\r
+ }\r
+\r
+ public int getUsbVendorId() {\r
+ return 0;\r
+ }\r
+\r
+ public String getVendor() {\r
+ return null;\r
+ }\r
+\r
+ public AndroidVersion getVersion() {\r
+ return mParentTarget.getVersion();\r
+ }\r
+\r
+ public String getVersionName() {\r
+ return String.format("mock-addon-%1$d", getVersion().getApiLevel());\r
+ }\r
+\r
+ public String hashString() {\r
+ return getVersionName();\r
+ }\r
+\r
+ /** Returns false for an addon. */\r
+ public boolean isPlatform() {\r
+ return false;\r
+ }\r
+\r
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {\r
+ throw new UnsupportedOperationException("Implement this as needed for tests");\r
+ }\r
+\r
+ public int compareTo(IAndroidTarget o) {\r
+ throw new UnsupportedOperationException("Implement this as needed for tests");\r
+ }\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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
+import com.android.sdklib.AndroidVersion;\r
+import com.android.sdklib.IAndroidTarget;\r
+\r
+import java.util.Properties;\r
+\r
+/**\r
+ * A mock {@link PlatformPackage} for testing.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+public class MockPlatformPackage extends PlatformPackage {\r
+\r
+ private final IAndroidTarget mTarget;\r
+\r
+ /**\r
+ * Creates a {@link MockPlatformTarget} with the requested API and revision\r
+ * and then a {@link MockPlatformPackage} wrapping it.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+ public MockPlatformPackage(int apiLevel, int revision) {\r
+ this(new MockPlatformTarget(apiLevel, revision), null /*props*/);\r
+ }\r
+\r
+ /**\r
+ * Creates a {@link MockPlatformTarget} with the requested API and revision\r
+ * and then a {@link MockPlatformPackage} wrapping it.\r
+ *\r
+ * Also sets the min-tools-rev of the platform.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+ public MockPlatformPackage(int apiLevel, int revision, int min_tools_rev) {\r
+ this(new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));\r
+ }\r
+\r
+ /** A little trick to be able to capture the target new after passing it to the super. */\r
+ private MockPlatformPackage(IAndroidTarget target, Properties props) {\r
+ super(target, props);\r
+ mTarget = target;\r
+ }\r
+\r
+ private static Properties createProps(int min_tools_rev) {\r
+ Properties props = new Properties();\r
+ props.setProperty(PlatformPackage.PROP_MIN_TOOLS_REV, Integer.toString((min_tools_rev)));\r
+ return props;\r
+ }\r
+\r
+ public IAndroidTarget getTarget() {\r
+ return mTarget;\r
+ }\r
+\r
+ /**\r
+ * A mock PlatformTarget.\r
+ * This reimplements the minimum needed from the interface for our limited testing needs.\r
+ */\r
+ static class MockPlatformTarget implements IAndroidTarget {\r
+\r
+ private final int mApiLevel;\r
+ private final int mRevision;\r
+\r
+ public MockPlatformTarget(int apiLevel, int revision) {\r
+ mApiLevel = apiLevel;\r
+ mRevision = revision;\r
+\r
+ }\r
+\r
+ public String getClasspathName() {\r
+ return null;\r
+ }\r
+\r
+ public String getDefaultSkin() {\r
+ return null;\r
+ }\r
+\r
+ public String getDescription() {\r
+ return "mock platform target";\r
+ }\r
+\r
+ public String getFullName() {\r
+ return "mock platform target";\r
+ }\r
+\r
+ public String getLocation() {\r
+ return "";\r
+ }\r
+\r
+ public String getName() {\r
+ return "mock platform target";\r
+ }\r
+\r
+ public IOptionalLibrary[] getOptionalLibraries() {\r
+ return null;\r
+ }\r
+\r
+ public IAndroidTarget getParent() {\r
+ return null;\r
+ }\r
+\r
+ public String getPath(int pathId) {\r
+ return null;\r
+ }\r
+\r
+ public String[] getPlatformLibraries() {\r
+ return null;\r
+ }\r
+\r
+ public int getRevision() {\r
+ return mRevision;\r
+ }\r
+\r
+ public String[] getSkins() {\r
+ return null;\r
+ }\r
+\r
+ public int getUsbVendorId() {\r
+ return 0;\r
+ }\r
+\r
+ public String getVendor() {\r
+ return null;\r
+ }\r
+\r
+ public AndroidVersion getVersion() {\r
+ return new AndroidVersion(mApiLevel, null /*codename*/);\r
+ }\r
+\r
+ public String getVersionName() {\r
+ return String.format("android-%1$d", mApiLevel);\r
+ }\r
+\r
+ public String hashString() {\r
+ return getVersionName();\r
+ }\r
+\r
+ /** Returns true for a platform. */\r
+ public boolean isPlatform() {\r
+ return true;\r
+ }\r
+\r
+ public boolean isCompatibleBaseFor(IAndroidTarget target) {\r
+ throw new UnsupportedOperationException("Implement this as needed for tests");\r
+ }\r
+\r
+ public int compareTo(IAndroidTarget o) {\r
+ throw new UnsupportedOperationException("Implement this as needed for tests");\r
+ }\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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
+import com.android.sdklib.internal.repository.Archive.Arch;\r
+import com.android.sdklib.internal.repository.Archive.Os;\r
+\r
+/**\r
+ * A mock {@link ToolPackage} for testing.\r
+ *\r
+ * By design, this package contains one and only one archive.\r
+ */\r
+public class MockToolPackage extends ToolPackage {\r
+\r
+ /**\r
+ * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults\r
+ * for everything else.\r
+ * <p/>\r
+ * By design, this creates a package with one and only one archive.\r
+ */\r
+ public MockToolPackage(int revision) {\r
+ super(\r
+ null, // source,\r
+ null, // props,\r
+ revision,\r
+ null, // license,\r
+ "desc", // description,\r
+ "url", // descUrl,\r
+ Os.getCurrentOs(), // archiveOs,\r
+ Arch.getCurrentArch(), // archiveArch,\r
+ "foo" // archiveOsPath\r
+ );\r
+ }\r
+}\r
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
- <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
- <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry kind="src" path="tests"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>\r
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>\r
+ <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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.sdkuilib.internal.repository;\r
+\r
+import com.android.sdklib.internal.repository.Archive;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+/**\r
+ * Represents an archive that we want to install.\r
+ * Note that the installer deals with archives whereas the user mostly sees packages\r
+ * but as far as we are concerned for installation there's a 1-to-1 mapping.\r
+ * <p/>\r
+ * A new archive is always a remote archive that needs to be downloaded and then\r
+ * installed. It can replace an existing local one. It can also depends on another\r
+ * (new or local) archive, which means the dependent archive needs to be successfully\r
+ * installed first. Finally this archive can also be a dependency for another one.\r
+ *\r
+ * @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo)\r
+ */\r
+class ArchiveInfo {\r
+\r
+ private final Archive mNewArchive;\r
+ private final Archive mReplaced;\r
+ private final ArchiveInfo mDependsOn;\r
+ private final ArrayList<ArchiveInfo> mDependencyFor = new ArrayList<ArchiveInfo>();\r
+ private boolean mAccepted;\r
+ private boolean mRejected;\r
+\r
+ /**\r
+ *\r
+ * @param newArchive A "new archive" to be installed. This is always an archive\r
+ * that comes from a remote site. This can not be null.\r
+ * @param replaced An optional local archive that the new one will replace.\r
+ * Can be null if this archive does not replace anything.\r
+ * @param dependsOn An optional new or local dependency, that is an archive that\r
+ * <em>this</em> archive depends upon. In other words, we can only install\r
+ * this archive if the dependency has been successfully installed. It also\r
+ * means we need to install the dependency first.\r
+ */\r
+ public ArchiveInfo(Archive newArchive, Archive replaced, ArchiveInfo dependsOn) {\r
+ mNewArchive = newArchive;\r
+ mReplaced = replaced;\r
+ mDependsOn = dependsOn;\r
+ }\r
+\r
+ /**\r
+ * Returns the "new archive" to be installed.\r
+ * This is always an archive that comes from a remote site.\r
+ */\r
+ public Archive getNewArchive() {\r
+ return mNewArchive;\r
+ }\r
+\r
+ /**\r
+ * Returns an optional local archive that the new one will replace.\r
+ * Can be null if this archive does not replace anything.\r
+ */\r
+ public Archive getReplaced() {\r
+ return mReplaced;\r
+ }\r
+\r
+ /**\r
+ * Returns an optional new or local dependency, that is an archive that <em>this</em>\r
+ * archive depends upon. In other words, we can only install this archive if the\r
+ * dependency has been successfully installed. It also means we need to install the\r
+ * dependency first.\r
+ */\r
+ public ArchiveInfo getDependsOn() {\r
+ return mDependsOn;\r
+ }\r
+\r
+ /**\r
+ * Returns true if this new archive is a dependency for <em>another</em> one that we\r
+ * want to install.\r
+ */\r
+ public boolean isDependencyFor() {\r
+ return mDependencyFor.size() > 0;\r
+ }\r
+\r
+ /**\r
+ * Set to true if this new archive is a dependency for <em>another</em> one that we\r
+ * want to install.\r
+ */\r
+ public void addDependencyFor(ArchiveInfo dependencyFor) {\r
+ mDependencyFor.add(dependencyFor);\r
+ }\r
+\r
+ public Collection<ArchiveInfo> getDependenciesFor() {\r
+ return mDependencyFor;\r
+ }\r
+\r
+ /**\r
+ * Sets whether this archive was accepted (either manually by the user or\r
+ * automatically if it doesn't have a license) for installation.\r
+ */\r
+ public void setAccepted(boolean accepted) {\r
+ mAccepted = accepted;\r
+ }\r
+\r
+ /**\r
+ * Returns whether this archive was accepted (either manually by the user or\r
+ * automatically if it doesn't have a license) for installation.\r
+ */\r
+ public boolean isAccepted() {\r
+ return mAccepted;\r
+ }\r
+\r
+ /**\r
+ * Sets whether this archive was rejected manually by the user.\r
+ * An archive can neither accepted nor rejected.\r
+ */\r
+ public void setRejected(boolean rejected) {\r
+ mRejected = rejected;\r
+ }\r
+\r
+ /**\r
+ * Returns whether this archive was rejected manually by the user.\r
+ * An archive can neither accepted nor rejected.\r
+ */\r
+ public boolean isRejected() {\r
+ return mRejected;\r
+ }\r
+}\r
import org.eclipse.swt.widgets.TableColumn;\r
\r
import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Comparator;\r
-import java.util.HashSet;\r
-import java.util.Map;\r
-import java.util.TreeMap;\r
\r
\r
/**\r
*/\r
final class UpdateChooserDialog extends Dialog {\r
\r
- /**\r
- * Min Y location for dialog. Need to deal with the menu bar on mac os.\r
- */\r
- private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ?\r
- 20 : 0;\r
+ /** Min Y location for dialog. Need to deal with the menu bar on mac os. */\r
+ private final static int MIN_Y =\r
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 20 : 0;\r
\r
/** Last dialog size for this session. */\r
private static Point sLastSize;\r
+ private boolean mCancelled = true;\r
private boolean mCompleted;\r
- private final Map<Archive, Archive> mNewToOldArchiveMap;\r
private boolean mLicenseAcceptAll;\r
private boolean mInternalLicenseRadioUpdate;\r
- private HashSet<Archive> mAccepted = new HashSet<Archive>();\r
- private HashSet<Archive> mRejected = new HashSet<Archive>();\r
- private ArrayList<Archive> mResult = new ArrayList<Archive>();\r
\r
// UI fields\r
private Shell mDialogShell;\r
private Group mPackageTextGroup;\r
private final UpdaterData mUpdaterData;\r
private Group mTableGroup;\r
+ private Label mErrorLabel;\r
+\r
+ /**\r
+ * List of all archives to be installed with dependency information.\r
+ *\r
+ * Note: in a lot of cases, we need to find the archive info for a given archive. This\r
+ * is currently done using a simple linear search, which is fine since we only have a very\r
+ * limited number of archives to deal with (e.g. < 10 now). We might want to revisit\r
+ * this later if it becomes an issue. Right now just do the simple thing.\r
+ *\r
+ * Typically we could add a map Archive=>ArchiveInfo later.\r
+ */\r
+ private final ArrayList<ArchiveInfo> mArchives;\r
+\r
\r
\r
/**\r
* Create the dialog.\r
* @param parentShell The shell to use, typically updaterData.getWindowShell()\r
* @param updaterData The updater data\r
- * @param newToOldUpdates The map [new archive => old archive] of potential updates\r
+ * @param archives The archives to be installed\r
*/\r
public UpdateChooserDialog(Shell parentShell,\r
UpdaterData updaterData,\r
- Map<Archive, Archive> newToOldUpdates) {\r
+ ArrayList<ArchiveInfo> archives) {\r
super(parentShell,\r
SWT.APPLICATION_MODAL);\r
mUpdaterData = updaterData;\r
-\r
- mNewToOldArchiveMap = new TreeMap<Archive, Archive>(new Comparator<Archive>() {\r
- public int compare(Archive a1, Archive a2) {\r
- // The items are archive but what we show are packages so we'll\r
- // sort of packages short descriptions\r
- String desc1 = a1.getParentPackage().getShortDescription();\r
- String desc2 = a2.getParentPackage().getShortDescription();\r
- return desc1.compareTo(desc2);\r
- }\r
- });\r
- mNewToOldArchiveMap.putAll(newToOldUpdates);\r
+ mArchives = archives;\r
}\r
\r
/**\r
* Returns the results, i.e. the list of selected new archives to install.\r
- * The list is always non null. It is empty when cancel is selected or when\r
- * all potential updates have been refused.\r
+ * This is similar to the {@link ArchiveInfo} list instance given to the constructor\r
+ * except only accepted archives are present.\r
+ *\r
+ * An empty list is returned if cancel was choosen.\r
*/\r
- public Collection<Archive> getResult() {\r
- return mResult;\r
+ public ArrayList<ArchiveInfo> getResult() {\r
+ ArrayList<ArchiveInfo> ais = new ArrayList<ArchiveInfo>();\r
+\r
+ if (!mCancelled) {\r
+ for (ArchiveInfo ai : mArchives) {\r
+ if (ai.isAccepted()) {\r
+ ais.add(ai);\r
+ }\r
+ }\r
+ }\r
+\r
+ return ais;\r
}\r
\r
/**\r
\r
mSashForm.setWeights(new int[] {200, 300});\r
\r
- // Bottom buttons\r
+ // Error message area\r
+\r
+ mErrorLabel = new Label(mDialogShell, SWT.NONE);\r
+ mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));\r
+\r
+\r
+ // Bottom buttons area\r
+\r
placeholder = new Label(mDialogShell, SWT.NONE);\r
placeholder.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1));\r
\r
private void postCreate() {\r
setWindowImage();\r
\r
- // Automatically accept those with an empty license\r
- for (Archive a : mNewToOldArchiveMap.keySet()) {\r
+ // Automatically accept those with an empty license or no license\r
+ for (ArchiveInfo ai : mArchives) {\r
+ Archive a = ai.getNewArchive();\r
+ assert a != null;\r
\r
String license = a.getParentPackage().getLicense();\r
- if (license != null) {\r
- license = license.trim();\r
- if (license.length() == 0) {\r
- mAccepted.add(a);\r
- }\r
- } else {\r
- mAccepted.add(a);\r
- }\r
+ ai.setAccepted(license == null || license.trim().length() == 0);\r
}\r
\r
// Fill the list with the replacement packages\r
mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());\r
mTableViewPackage.setContentProvider(new NewArchivesContentProvider());\r
- mTableViewPackage.setInput(mNewToOldArchiveMap);\r
+ mTableViewPackage.setInput(mArchives);\r
\r
adjustColumnsWidth();\r
\r
}\r
\r
/**\r
- * Callback invoked when the Install button is selected. Fills {@link #mResult} and\r
- * completes the dialog.\r
+ * Callback invoked when the Install button is selected. Completes the dialog.\r
*/\r
private void onInstallSelected() {\r
- // get list of accepted items\r
- mResult.addAll(mAccepted);\r
+ mCancelled = false;\r
mCompleted = true;\r
}\r
\r
* Callback invoked when the Cancel button is selected.\r
*/\r
private void onCancelSelected() {\r
+ mCancelled = true;\r
mCompleted = true;\r
}\r
\r
* Callback invoked when a package item is selected in the list.\r
*/\r
private void onPackageSelected() {\r
- Archive a = getSelectedArchive();\r
- displayInformation(a);\r
- updateLicenceRadios(a);\r
+ ArchiveInfo ai = getSelectedArchive();\r
+ displayInformation(ai);\r
+ displayMissingDependency(ai);\r
+ updateLicenceRadios(ai);\r
}\r
\r
- /** Returns the currently selected Archive or null. */\r
- private Archive getSelectedArchive() {\r
+ /** Returns the currently selected {@link ArchiveInfo} or null. */\r
+ private ArchiveInfo getSelectedArchive() {\r
ISelection sel = mTableViewPackage.getSelection();\r
if (sel instanceof IStructuredSelection) {\r
Object elem = ((IStructuredSelection) sel).getFirstElement();\r
- if (elem instanceof Archive) {\r
- return (Archive) elem;\r
+ if (elem instanceof ArchiveInfo) {\r
+ return (ArchiveInfo) elem;\r
}\r
}\r
return null;\r
}\r
\r
- private void displayInformation(Archive a) {\r
- if (a == null) {\r
+ /**\r
+ * Updates the package description and license text depending on the selected package.\r
+ */\r
+ private void displayInformation(ArchiveInfo ai) {\r
+ if (ai == null) {\r
mPackageText.setText("Please select a package.");\r
return;\r
}\r
\r
- mPackageText.setText(""); //$NON-NLS-1$\r
+ Archive anew = ai.getNewArchive();\r
+\r
+ mPackageText.setText(""); //$NON-NLS-1$\r
\r
addSectionTitle("Package Description\n");\r
- addText(a.getParentPackage().getLongDescription(), "\n\n"); //$NON-NLS-1$\r
+ addText(anew.getParentPackage().getLongDescription(), "\n\n"); //$NON-NLS-1$\r
\r
- Archive aold = mNewToOldArchiveMap.get(a);\r
+ Archive aold = ai.getReplaced();\r
if (aold != null) {\r
addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",\r
aold.getParentPackage().getRevision(),\r
- a.getParentPackage().getRevision()));\r
+ anew.getParentPackage().getRevision()));\r
}\r
\r
+ ArchiveInfo adep = ai.getDependsOn();\r
+ if (adep != null || ai.isDependencyFor()) {\r
+ addSectionTitle("Dependencies\n");\r
+\r
+ if (adep != null) {\r
+ addText(String.format("This package depends on %1$s.\n\n",\r
+ adep.getNewArchive().getParentPackage().getShortDescription()));\r
+ }\r
+\r
+ if (ai.isDependencyFor()) {\r
+ addText("This package is a dependency for:");\r
+ for (ArchiveInfo ai2 : ai.getDependenciesFor()) {\r
+ addText("\n- " +\r
+ ai2.getNewArchive().getParentPackage().getShortDescription());\r
+ }\r
+ addText("\n\n");\r
+ }\r
+ }\r
\r
addSectionTitle("Archive Description\n");\r
- addText(a.getLongDescription(), "\n\n"); //$NON-NLS-1$\r
+ addText(anew.getLongDescription(), "\n\n"); //$NON-NLS-1$\r
\r
- String license = a.getParentPackage().getLicense();\r
+ String license = anew.getParentPackage().getLicense();\r
if (license != null) {\r
addSectionTitle("License\n");\r
- addText(license.trim(), "\n"); //$NON-NLS-1$\r
+ addText(license.trim(), "\n"); //$NON-NLS-1$\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Computes and display missing dependency.\r
+ * If there's a selected package, check the dependency for that one.\r
+ * Otherwise display the first missing dependency.\r
+ */\r
+ private void displayMissingDependency(ArchiveInfo ai) {\r
+ String error = null;\r
+\r
+ try {\r
+ if (ai != null) {\r
+\r
+ if (!ai.isAccepted()) {\r
+ // Case where this package blocks another one when not accepted\r
+ for (ArchiveInfo ai2 : ai.getDependenciesFor()) {\r
+ // It only matters if the blocked one is accepted\r
+ if (ai2.isAccepted()) {\r
+ error = String.format("Package '%1$s' depends on this one.",\r
+ ai2.getNewArchive().getParentPackage().getShortDescription());\r
+ return;\r
+ }\r
+ }\r
+ } else {\r
+ // Case where this package is accepted but blocked by another non-accepted one\r
+ ArchiveInfo adep = ai.getDependsOn();\r
+ if (adep != null && !adep.isAccepted()) {\r
+ error = String.format("This package depends on '%1$s'.",\r
+ adep.getNewArchive().getParentPackage().getShortDescription());\r
+ return;\r
+ }\r
+ }\r
+ }\r
+\r
+ // If there's no selection, just find the first missing dependency of any accepted\r
+ // package.\r
+ for (ArchiveInfo ai2 : mArchives) {\r
+ if (ai2.isAccepted()) {\r
+ ArchiveInfo adep = ai.getDependsOn();\r
+ if (adep != null && !adep.isAccepted()) {\r
+ error = String.format("Package '%1$s' depends on '%2$s'",\r
+ ai2.getNewArchive().getParentPackage().getShortDescription(),\r
+ adep.getNewArchive().getParentPackage().getShortDescription());\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ } finally {\r
+ mErrorLabel.setText(error == null ? "" : error); //$NON-NLS-1$\r
}\r
}\r
\r
mPackageText.setStyleRange(sr);\r
}\r
\r
- private void updateLicenceRadios(Archive a) {\r
+ private void updateLicenceRadios(ArchiveInfo ai) {\r
if (mInternalLicenseRadioUpdate) {\r
return;\r
}\r
mInternalLicenseRadioUpdate = true;\r
\r
+ boolean oneAccepted = false;\r
+\r
if (mLicenseAcceptAll) {\r
mLicenseRadioAcceptAll.setSelection(true);\r
+ mLicenseRadioAccept.setEnabled(true);\r
+ mLicenseRadioReject.setEnabled(true);\r
mLicenseRadioAccept.setSelection(false);\r
mLicenseRadioReject.setSelection(false);\r
} else {\r
mLicenseRadioAcceptAll.setSelection(false);\r
- mLicenseRadioAccept.setSelection(mAccepted.contains(a));\r
- mLicenseRadioReject.setSelection(mRejected.contains(a));\r
+ oneAccepted = ai != null && ai.isAccepted();\r
+ mLicenseRadioAccept.setEnabled(ai != null);\r
+ mLicenseRadioReject.setEnabled(ai != null);\r
+ mLicenseRadioAccept.setSelection(oneAccepted);\r
+ mLicenseRadioReject.setSelection(ai != null && ai.isRejected());\r
}\r
\r
- // The install button is enabled if there's at least one\r
- // package accepted.\r
- mInstallButton.setEnabled(mAccepted.size() > 0);\r
+ // The install button is enabled if there's at least one package accepted.\r
+ // If the current one isn't, look for another one.\r
+ boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;\r
+ if (!missing && !oneAccepted) {\r
+ for(ArchiveInfo ai2 : mArchives) {\r
+ if (ai2.isAccepted()) {\r
+ oneAccepted = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ mInstallButton.setEnabled(!missing && oneAccepted);\r
\r
mInternalLicenseRadioUpdate = false;\r
}\r
}\r
mInternalLicenseRadioUpdate = true;\r
\r
- Archive a = getSelectedArchive();\r
+ ArchiveInfo ai = getSelectedArchive();\r
boolean needUpdate = true;\r
\r
if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {\r
// Accept all has been switched on. Mark all packages as accepted\r
mLicenseAcceptAll = true;\r
- mAccepted.addAll(mNewToOldArchiveMap.keySet());\r
- mRejected.clear();\r
+ for(ArchiveInfo ai2 : mArchives) {\r
+ ai2.setAccepted(true);\r
+ ai2.setRejected(false);\r
+ }\r
\r
} else if (mLicenseRadioAccept.getSelection()) {\r
// Accept only this one\r
mLicenseAcceptAll = false;\r
- mAccepted.add(a);\r
- mRejected.remove(a);\r
+ ai.setAccepted(true);\r
+ ai.setRejected(false);\r
\r
} else if (mLicenseRadioReject.getSelection()) {\r
// Reject only this one\r
mLicenseAcceptAll = false;\r
- mAccepted.remove(a);\r
- mRejected.add(a);\r
+ ai.setAccepted(false);\r
+ ai.setRejected(true);\r
\r
} else {\r
needUpdate = false;\r
if (mLicenseAcceptAll) {\r
mTableViewPackage.refresh();\r
} else {\r
- mTableViewPackage.refresh(a);\r
+ mTableViewPackage.refresh(ai);\r
}\r
- updateLicenceRadios(a);\r
+ displayMissingDependency(ai);\r
+ updateLicenceRadios(ai);\r
}\r
}\r
\r
* Callback invoked when a package item is double-clicked in the list.\r
*/\r
private void onPackageDoubleClick() {\r
- Archive a = getSelectedArchive();\r
+ ArchiveInfo ai = getSelectedArchive();\r
\r
- if (mAccepted.contains(a)) {\r
- // toggle from accepted to rejected\r
- mAccepted.remove(a);\r
- mRejected.add(a);\r
- } else {\r
- // toggle from rejected or unknown to accepted\r
- mAccepted.add(a);\r
- mRejected.remove(a);\r
- }\r
+ boolean wasAccepted = ai.isAccepted();\r
+ ai.setAccepted(!wasAccepted);\r
+ ai.setRejected(wasAccepted);\r
\r
// update state\r
mLicenseAcceptAll = false;\r
- mTableViewPackage.refresh(a);\r
- updateLicenceRadios(a);\r
+ mTableViewPackage.refresh(ai);\r
+ updateLicenceRadios(ai);\r
}\r
\r
private class NewArchivesLabelProvider extends LabelProvider {\r
@Override\r
public Image getImage(Object element) {\r
+ assert element instanceof ArchiveInfo;\r
+ ArchiveInfo ai = (ArchiveInfo) element;\r
+\r
ImageFactory imgFactory = mUpdaterData.getImageFactory();\r
if (imgFactory != null) {\r
- if (mAccepted.contains(element)) {\r
+ if (ai.isAccepted()) {\r
return imgFactory.getImageByName("accept_icon16.png");\r
- } else if (mRejected.contains(element)) {\r
+ } else if (ai.isRejected()) {\r
return imgFactory.getImageByName("reject_icon16.png");\r
}\r
return imgFactory.getImageByName("unknown_icon16.png");\r
\r
@Override\r
public String getText(Object element) {\r
- if (element instanceof Archive) {\r
- return ((Archive) element).getParentPackage().getShortDescription();\r
+ assert element instanceof ArchiveInfo;\r
+ ArchiveInfo ai = (ArchiveInfo) element;\r
+\r
+ String desc = ai.getNewArchive().getParentPackage().getShortDescription();\r
+\r
+ if (ai.isDependencyFor()) {\r
+ desc += " [*]";\r
}\r
- return super.getText(element);\r
+\r
+ return desc;\r
}\r
}\r
\r
}\r
\r
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
- // Ignore. The input is always mNewArchives\r
+ // Ignore. The input is always mArchives\r
}\r
\r
public Object[] getElements(Object inputElement) {\r
- return mNewToOldArchiveMap.keySet().toArray();\r
+ return mArchives.toArray();\r
}\r
}\r
\r
import com.android.sdklib.internal.repository.RepoSource;\r
import com.android.sdklib.internal.repository.RepoSources;\r
import com.android.sdklib.internal.repository.ToolPackage;\r
-import com.android.sdklib.internal.repository.Package.UpdateInfo;\r
import com.android.sdkuilib.internal.repository.icons.ImageFactory;\r
import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;\r
\r
import java.io.PrintStream;\r
import java.util.ArrayList;\r
import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
+import java.util.HashSet;\r
\r
/**\r
* Data shared between {@link UpdaterWindowImpl} and its pages.\r
* Install the list of given {@link Archive}s. This is invoked by the user selecting some\r
* packages in the remote page and then clicking "install selected".\r
*\r
- * @param archives The archives to install. Incompatible ones will be skipped.\r
+ * @param result The archives to install. Incompatible ones will be skipped.\r
*/\r
- public void installArchives(final Collection<Archive> archives) {\r
+ public void installArchives(final ArrayList<ArchiveInfo> result) {\r
if (mTaskFactory == null) {\r
throw new IllegalArgumentException("Task Factory is null");\r
}\r
public void run(ITaskMonitor monitor) {\r
\r
final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;\r
- monitor.setProgressMax(archives.size() * progressPerArchive);\r
+ monitor.setProgressMax(result.size() * progressPerArchive);\r
monitor.setDescription("Preparing to install archives");\r
\r
boolean installedAddon = false;\r
boolean installedTools = false;\r
\r
+ // Mark all current local archives as already installed.\r
+ HashSet<Archive> installedArchives = new HashSet<Archive>();\r
+ for (Package p : getInstalledPackage()) {\r
+ for (Archive a : p.getArchives()) {\r
+ installedArchives.add(a);\r
+ }\r
+ }\r
+\r
int numInstalled = 0;\r
- for (Archive archive : archives) {\r
+ for (ArchiveInfo ai : result) {\r
+ Archive archive = ai.getNewArchive();\r
\r
int nextProgress = monitor.getProgress() + progressPerArchive;\r
try {\r
break;\r
}\r
\r
+ ArchiveInfo adep = ai.getDependsOn();\r
+ if (adep != null && !installedArchives.contains(adep)) {\r
+ // This archive depends on another one that was not installed.\r
+ // Skip it.\r
+ monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",\r
+ archive.getParentPackage().getShortDescription(),\r
+ adep.getNewArchive().getParentPackage().getShortDescription());\r
+ }\r
+\r
if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {\r
+ // We installed this archive.\r
+ installedArchives.add(archive);\r
numInstalled++;\r
\r
+ // If this package was replacing an existing one, the old one\r
+ // is no longer installed.\r
+ installedArchives.remove(ai.getReplaced());\r
+\r
// Check if we successfully installed a tool or add-on package.\r
if (archive.getParentPackage() instanceof AddonPackage) {\r
installedAddon = true;\r
refreshSources(true);\r
}\r
\r
- final Map<Archive, Archive> updates = findUpdates(selectedArchives);\r
-\r
- if (selectedArchives != null) {\r
- // Not only we want to perform updates but we also want to install the\r
- // selected archives. If they do not match an update, list them anyway\r
- // except they map themselves to null (no "old" archive)\r
- for (Archive a : selectedArchives) {\r
- if (!updates.containsKey(a)) {\r
- updates.put(a, null);\r
- }\r
- }\r
- }\r
+ UpdaterLogic ul = new UpdaterLogic();\r
+ ArrayList<ArchiveInfo> archives = ul.computeUpdates(\r
+ selectedArchives,\r
+ getSources(),\r
+ getLocalSdkParser().getPackages());\r
\r
- UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, updates);\r
+ UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, archives);\r
dialog.open();\r
\r
- Collection<Archive> result = dialog.getResult();\r
+ ArrayList<ArchiveInfo> result = dialog.getResult();\r
if (result != null && result.size() > 0) {\r
installArchives(result);\r
}\r
}\r
-\r
/**\r
* Refresh all sources. This is invoked either internally (reusing an existing monitor)\r
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is\r
}\r
});\r
}\r
-\r
- /**\r
- * Check the local archives vs the remote available packages to find potential updates.\r
- * Return a map [remote archive => local archive] of suitable update candidates.\r
- * Returns null if there's an unexpected error. Otherwise returns a map that can be\r
- * empty but not null.\r
- *\r
- * @param selectedArchives The list of remote archive to consider for the update.\r
- * This can be null, in which case a list of remote archive is fetched from all\r
- * available sources.\r
- */\r
- private Map<Archive, Archive> findUpdates(Collection<Archive> selectedArchives) {\r
- // Map [remote archive => local archive] of suitable update candidates\r
- Map<Archive, Archive> result = new HashMap<Archive, Archive>();\r
-\r
- // First go thru all sources and make a list of all available remote archives\r
- // sorted by package class.\r
- HashMap<Class<? extends Package>, ArrayList<Archive>> availablePkgs =\r
- new HashMap<Class<? extends Package>, ArrayList<Archive>>();\r
-\r
- if (selectedArchives != null) {\r
- // Only consider the archives given\r
-\r
- for (Archive a : selectedArchives) {\r
- // Only add compatible archives\r
- if (a.isCompatible()) {\r
- Class<? extends Package> clazz = a.getParentPackage().getClass();\r
-\r
- ArrayList<Archive> list = availablePkgs.get(clazz);\r
- if (list == null) {\r
- availablePkgs.put(clazz, list = new ArrayList<Archive>());\r
- }\r
-\r
- list.add(a);\r
- }\r
- }\r
-\r
- } else {\r
- // Get all the available archives from all loaded sources\r
- RepoSource[] remoteSources = getSources().getSources();\r
-\r
- for (RepoSource remoteSrc : remoteSources) {\r
- Package[] remotePkgs = remoteSrc.getPackages();\r
- if (remotePkgs != null) {\r
- for (Package remotePkg : remotePkgs) {\r
- Class<? extends Package> clazz = remotePkg.getClass();\r
-\r
- ArrayList<Archive> list = availablePkgs.get(clazz);\r
- if (list == null) {\r
- availablePkgs.put(clazz, list = new ArrayList<Archive>());\r
- }\r
-\r
- for (Archive a : remotePkg.getArchives()) {\r
- // Only add compatible archives\r
- if (a.isCompatible()) {\r
- list.add(a);\r
- }\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- Package[] localPkgs = getLocalSdkParser().getPackages();\r
- if (localPkgs == null) {\r
- // This is unexpected. The local sdk parser should have been called first.\r
- return null;\r
- }\r
-\r
- for (Package localPkg : localPkgs) {\r
- // get the available archive list for this package type\r
- ArrayList<Archive> list = availablePkgs.get(localPkg.getClass());\r
-\r
- // if this list is empty, we'll never find anything that matches\r
- if (list == null || list.size() == 0) {\r
- continue;\r
- }\r
-\r
- // local packages should have one archive at most\r
- Archive[] localArchives = localPkg.getArchives();\r
- if (localArchives != null && localArchives.length > 0) {\r
- Archive localArchive = localArchives[0];\r
- // only consider archives compatible with the current platform\r
- if (localArchive != null && localArchive.isCompatible()) {\r
-\r
- // We checked all this archive stuff because that's what eventually gets\r
- // installed, but the "update" mechanism really works on packages. So now\r
- // the real question: is there a remote package that can update this\r
- // local package?\r
-\r
- for (Archive availArchive : list) {\r
- UpdateInfo info = localPkg.canBeUpdatedBy(availArchive.getParentPackage());\r
- if (info == UpdateInfo.UPDATE) {\r
- // Found one!\r
- result.put(availArchive, localArchive);\r
- break;\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- return result;\r
- }\r
}\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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.sdkuilib.internal.repository;\r
+\r
+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.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.ToolPackage;\r
+import com.android.sdklib.internal.repository.Package.UpdateInfo;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+class UpdaterLogic {\r
+\r
+ private RepoSources mSources;\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
+\r
+ if (selectedArchives == null) {\r
+ selectedArchives = findUpdates(localPkgs, remotePkgs);\r
+ }\r
+\r
+ for (Archive a : selectedArchives) {\r
+ insertArchive(a, archives, selectedArchives, remotePkgs, localPkgs, false);\r
+ }\r
+\r
+ return archives;\r
+ }\r
+\r
+\r
+ /**\r
+ * Find suitable updates to all current local packages.\r
+ */\r
+ private Collection<Archive> findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs) {\r
+ ArrayList<Archive> updates = new ArrayList<Archive>();\r
+\r
+ fetchRemotePackages(remotePkgs);\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
+ // if it provides at least one compatible archive.\r
+\r
+ for (Archive a : remotePkg.getArchives()) {\r
+ if (a.isCompatible()) {\r
+ updates.add(a);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ return updates;\r
+ }\r
+\r
+ private ArchiveInfo insertArchive(Archive archive,\r
+ ArrayList<ArchiveInfo> outArchives,\r
+ Collection<Archive> selectedArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ Package[] localPkgs,\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
+ }\r
+ }\r
+\r
+ // find dependencies\r
+ ArchiveInfo dep = findDependency(p, outArchives, selectedArchives, remotePkgs, localPkgs);\r
+\r
+ ArchiveInfo ai = new ArchiveInfo(\r
+ archive, //newArchive\r
+ updatedArchive, //replaced\r
+ dep //dependsOn\r
+ );\r
+\r
+ outArchives.add(ai);\r
+ if (dep != null) {\r
+ dep.addDependencyFor(ai);\r
+ }\r
+\r
+ return ai;\r
+ }\r
+\r
+ private ArchiveInfo findDependency(Package pkg,\r
+ ArrayList<ArchiveInfo> outArchives,\r
+ Collection<Archive> selectedArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ Package[] localPkgs) {\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
+\r
+ if (pkg instanceof AddonPackage) {\r
+ AddonPackage addon = (AddonPackage) pkg;\r
+\r
+ return findAddonDependency(\r
+ addon, outArchives, selectedArchives, remotePkgs, localPkgs);\r
+\r
+ } else if (pkg instanceof PlatformPackage) {\r
+ PlatformPackage platform = (PlatformPackage) pkg;\r
+\r
+ return findPlatformDependency(\r
+ platform, outArchives, selectedArchives, remotePkgs, localPkgs);\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
+ * 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
+ ArrayList<ArchiveInfo> outArchives,\r
+ Collection<Archive> selectedArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ Package[] localPkgs) {\r
+ // This is the requirement to match.\r
+ int rev = platform.getMinToolsRevision();\r
+\r
+ if (rev == PlatformPackage.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
+ }\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
+ }\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
+ }\r
+ }\r
+ }\r
+\r
+ // Finally nothing matched, so let's look at all available remote packages\r
+ fetchRemotePackages(remotePkgs);\r
+ for (Package p : remotePkgs) {\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 the\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
+ }\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
+ }\r
+\r
+ /**\r
+ * An addon depends on having a platform with the same API version.\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
+ ArrayList<ArchiveInfo> outArchives,\r
+ Collection<Archive> selectedArchives,\r
+ ArrayList<Package> remotePkgs,\r
+ Package[] localPkgs) {\r
+ // This is the requirement to match.\r
+ AndroidVersion v = addon.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
+ }\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
+ }\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
+ }\r
+ }\r
+ }\r
+\r
+ // Finally nothing matched, so let's look at all available remote packages\r
+ fetchRemotePackages(remotePkgs);\r
+ for (Package p : remotePkgs) {\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 the\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
+ }\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
+ }\r
+\r
+ /** Fetch all remote packages only if really needed. */\r
+ protected void fetchRemotePackages(ArrayList<Package> remotePkgs) {\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
+ nextPackage: for (Package pkg : pkgs) {\r
+ for (Archive a : pkg.getArchives()) {\r
+ // Only add a package if it contains at least one compatible archive\r
+ if (a.isCompatible()) {\r
+ remotePkgs.add(pkg);\r
+ continue nextPackage;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright (C) 2009 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.sdkuilib.internal.repository;\r
+\r
+import com.android.sdklib.internal.repository.Archive;\r
+import com.android.sdklib.internal.repository.MockAddonPackage;\r
+import com.android.sdklib.internal.repository.MockPlatformPackage;\r
+import com.android.sdklib.internal.repository.MockToolPackage;\r
+import com.android.sdklib.internal.repository.Package;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+\r
+import junit.framework.TestCase;\r
+\r
+public class UpdaterLogicTest extends TestCase {\r
+\r
+ private static class MockUpdaterLogic extends UpdaterLogic {\r
+ private final Package[] mRemotePackages;\r
+\r
+ public MockUpdaterLogic(Package[] remotePackages) {\r
+ mRemotePackages = remotePackages;\r
+ }\r
+\r
+ @Override\r
+ protected void fetchRemotePackages(ArrayList<Package> remotePkgs) {\r
+ if (mRemotePackages != null) {\r
+ remotePkgs.addAll(Arrays.asList(mRemotePackages));\r
+ }\r
+ }\r
+ }\r
+\r
+ public void testFindAddonDependency() throws Exception {\r
+ MockUpdaterLogic mul = new MockUpdaterLogic(null);\r
+\r
+ MockPlatformPackage p1 = new MockPlatformPackage(1, 1);\r
+ MockPlatformPackage p2 = new MockPlatformPackage(2, 1);\r
+\r
+ MockAddonPackage a1 = new MockAddonPackage(p1, 1);\r
+ MockAddonPackage a2 = new MockAddonPackage(p2, 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[] locals = { p1, a1 };\r
+ assertNull(mul.findAddonDependency(a2, out, selected, remote, locals));\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
+ ArchiveInfo ai2 = mul.findAddonDependency(a2, out, selected, remote, locals);\r
+ assertNotNull(ai2);\r
+ assertSame(p2_archive, ai2.getNewArchive());\r
+ assertEquals(1, out.size());\r
+ assertSame(p2_archive, out.get(0).getNewArchive());\r
+ }\r
+\r
+ public void testFindPlatformDependency() throws Exception {\r
+ MockUpdaterLogic mul = new MockUpdaterLogic(null);\r
+\r
+ MockToolPackage t1 = new MockToolPackage(1);\r
+ MockToolPackage t2 = new MockToolPackage(2);\r
+\r
+ MockPlatformPackage p2 = new MockPlatformPackage(2, 1, 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
+ // p2 depends on t2, which is not locally installed\r
+ Package[] locals = { t1 };\r
+ assertNull(mul.findPlatformDependency(p2, out, selected, remote, locals));\r
+ assertEquals(0, out.size());\r
+\r
+ // t2 is now selected and can be used as a dependency\r
+ Archive t2_archive = t2.getArchives()[0];\r
+ selected.add(t2_archive);\r
+ ArchiveInfo ai2 = mul.findPlatformDependency(p2, out, selected, remote, locals);\r
+ assertNotNull(ai2);\r
+ assertSame(t2_archive, ai2.getNewArchive());\r
+ assertEquals(1, out.size());\r
+ assertSame(t2_archive, out.get(0).getNewArchive());\r
+ }\r
+}\r