/* | |
* Copyright (C) 2009 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.sdkuilib.internal.repository; | |
import com.android.sdklib.AndroidVersion; | |
import com.android.sdklib.internal.repository.AddonPackage; | |
import com.android.sdklib.internal.repository.Archive; | |
import com.android.sdklib.internal.repository.Package; | |
import com.android.sdklib.internal.repository.PlatformPackage; | |
import com.android.sdklib.internal.repository.RepoSource; | |
import com.android.sdklib.internal.repository.RepoSources; | |
import com.android.sdklib.internal.repository.ToolPackage; | |
import com.android.sdklib.internal.repository.Package.UpdateInfo; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
class UpdaterLogic { | |
private RepoSources mSources; | |
public ArrayList<ArchiveInfo> computeUpdates( | |
Collection<Archive> selectedArchives, | |
RepoSources sources, | |
Package[] localPkgs) { | |
mSources = sources; | |
ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>(); | |
ArrayList<Package> remotePkgs = new ArrayList<Package>(); | |
if (selectedArchives == null) { | |
selectedArchives = findUpdates(localPkgs, remotePkgs); | |
} | |
for (Archive a : selectedArchives) { | |
insertArchive(a, archives, selectedArchives, remotePkgs, localPkgs, false); | |
} | |
return archives; | |
} | |
/** | |
* Find suitable updates to all current local packages. | |
*/ | |
private Collection<Archive> findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs) { | |
ArrayList<Archive> updates = new ArrayList<Archive>(); | |
fetchRemotePackages(remotePkgs); | |
for (Package localPkg : localPkgs) { | |
for (Package remotePkg : remotePkgs) { | |
if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) { | |
// Found a suitable update. Only accept the remote package | |
// if it provides at least one compatible archive. | |
for (Archive a : remotePkg.getArchives()) { | |
if (a.isCompatible()) { | |
updates.add(a); | |
break; | |
} | |
} | |
} | |
} | |
} | |
return updates; | |
} | |
private ArchiveInfo insertArchive(Archive archive, | |
ArrayList<ArchiveInfo> outArchives, | |
Collection<Archive> selectedArchives, | |
ArrayList<Package> remotePkgs, | |
Package[] localPkgs, | |
boolean automated) { | |
Package p = archive.getParentPackage(); | |
// Is this an update? | |
Archive updatedArchive = null; | |
for (Package lp : localPkgs) { | |
assert lp.getArchives().length == 1; | |
if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) { | |
updatedArchive = lp.getArchives()[0]; | |
} | |
} | |
// find dependencies | |
ArchiveInfo dep = findDependency(p, outArchives, selectedArchives, remotePkgs, localPkgs); | |
ArchiveInfo ai = new ArchiveInfo( | |
archive, //newArchive | |
updatedArchive, //replaced | |
dep //dependsOn | |
); | |
outArchives.add(ai); | |
if (dep != null) { | |
dep.addDependencyFor(ai); | |
} | |
return ai; | |
} | |
private ArchiveInfo findDependency(Package pkg, | |
ArrayList<ArchiveInfo> outArchives, | |
Collection<Archive> selectedArchives, | |
ArrayList<Package> remotePkgs, | |
Package[] localPkgs) { | |
// Current dependencies can be: | |
// - addon: *always* depends on platform of same API level | |
// - platform: *might* depends on tools of rev >= min-tools-rev | |
if (pkg instanceof AddonPackage) { | |
AddonPackage addon = (AddonPackage) pkg; | |
return findAddonDependency( | |
addon, outArchives, selectedArchives, remotePkgs, localPkgs); | |
} else if (pkg instanceof PlatformPackage) { | |
PlatformPackage platform = (PlatformPackage) pkg; | |
return findPlatformDependency( | |
platform, outArchives, selectedArchives, remotePkgs, localPkgs); | |
} | |
return null; | |
} | |
/** | |
* A platform can have a min-tools-rev, in which case it depends on having a tools package | |
* of the requested revision. | |
* Finds the tools dependency. If found, add it to the list of things to install. | |
* Returns the archive info dependency, if any. | |
*/ | |
protected ArchiveInfo findPlatformDependency(PlatformPackage platform, | |
ArrayList<ArchiveInfo> outArchives, | |
Collection<Archive> selectedArchives, | |
ArrayList<Package> remotePkgs, | |
Package[] localPkgs) { | |
// This is the requirement to match. | |
int rev = platform.getMinToolsRevision(); | |
if (rev == PlatformPackage.MIN_TOOLS_REV_NOT_SPECIFIED) { | |
// Well actually there's no requirement. | |
return null; | |
} | |
// First look in local packages. | |
for (Package p : localPkgs) { | |
if (p instanceof ToolPackage) { | |
if (((ToolPackage) p).getRevision() >= rev) { | |
// We found one already installed. We don't report this dependency | |
// as the UI only cares about resolving "newly added dependencies". | |
return null; | |
} | |
} | |
} | |
// Look in archives already scheduled for install | |
for (ArchiveInfo ai : outArchives) { | |
Package p = ai.getNewArchive().getParentPackage(); | |
if (p instanceof PlatformPackage) { | |
if (((ToolPackage) p).getRevision() >= rev) { | |
// The dependency is already scheduled for install, nothing else to do. | |
return ai; | |
} | |
} | |
} | |
// Otherwise look in the selected archives. | |
for (Archive a : selectedArchives) { | |
Package p = a.getParentPackage(); | |
if (p instanceof ToolPackage) { | |
if (((ToolPackage) p).getRevision() >= rev) { | |
// It's not already in the list of things to install, so add it now | |
return insertArchive(a, outArchives, | |
selectedArchives, remotePkgs, localPkgs, | |
true); | |
} | |
} | |
} | |
// Finally nothing matched, so let's look at all available remote packages | |
fetchRemotePackages(remotePkgs); | |
for (Package p : remotePkgs) { | |
if (p instanceof ToolPackage) { | |
if (((ToolPackage) p).getRevision() >= rev) { | |
// It's not already in the list of things to install, so add the | |
// first compatible archive we can find. | |
for (Archive a : p.getArchives()) { | |
if (a.isCompatible()) { | |
return insertArchive(a, outArchives, | |
selectedArchives, remotePkgs, localPkgs, | |
true); | |
} | |
} | |
} | |
} | |
} | |
// We end up here if nothing matches. We don't have a good tools to match. | |
// Seriously, that can't happens unless we totally screwed our repo manifest. | |
// We'll let this one go through anyway. | |
return null; | |
} | |
/** | |
* An addon depends on having a platform with the same API version. | |
* Finds the platform dependency. If found, add it to the list of things to install. | |
* Returns the archive info dependency, if any. | |
*/ | |
protected ArchiveInfo findAddonDependency(AddonPackage addon, | |
ArrayList<ArchiveInfo> outArchives, | |
Collection<Archive> selectedArchives, | |
ArrayList<Package> remotePkgs, | |
Package[] localPkgs) { | |
// This is the requirement to match. | |
AndroidVersion v = addon.getVersion(); | |
// Find a platform that would satisfy the requirement. | |
// First look in local packages. | |
for (Package p : localPkgs) { | |
if (p instanceof PlatformPackage) { | |
if (v.equals(((PlatformPackage) p).getVersion())) { | |
// We found one already installed. We don't report this dependency | |
// as the UI only cares about resolving "newly added dependencies". | |
return null; | |
} | |
} | |
} | |
// Look in archives already scheduled for install | |
for (ArchiveInfo ai : outArchives) { | |
Package p = ai.getNewArchive().getParentPackage(); | |
if (p instanceof PlatformPackage) { | |
if (v.equals(((PlatformPackage) p).getVersion())) { | |
// The dependency is already scheduled for install, nothing else to do. | |
return ai; | |
} | |
} | |
} | |
// Otherwise look in the selected archives. | |
for (Archive a : selectedArchives) { | |
Package p = a.getParentPackage(); | |
if (p instanceof PlatformPackage) { | |
if (v.equals(((PlatformPackage) p).getVersion())) { | |
// It's not already in the list of things to install, so add it now | |
return insertArchive(a, outArchives, | |
selectedArchives, remotePkgs, localPkgs, | |
true); | |
} | |
} | |
} | |
// Finally nothing matched, so let's look at all available remote packages | |
fetchRemotePackages(remotePkgs); | |
for (Package p : remotePkgs) { | |
if (p instanceof PlatformPackage) { | |
if (v.equals(((PlatformPackage) p).getVersion())) { | |
// It's not already in the list of things to install, so add the | |
// first compatible archive we can find. | |
for (Archive a : p.getArchives()) { | |
if (a.isCompatible()) { | |
return insertArchive(a, outArchives, | |
selectedArchives, remotePkgs, localPkgs, | |
true); | |
} | |
} | |
} | |
} | |
} | |
// We end up here if nothing matches. We don't have a good platform to match. | |
// Seriously, that can't happens unless the repository contains a bogus addon | |
// entry that does not match any existing platform API level. | |
// It's conceivable that a 3rd part addon repo might have error, in which case | |
// we'll let this one go through anyway. | |
return null; | |
} | |
/** Fetch all remote packages only if really needed. */ | |
protected void fetchRemotePackages(ArrayList<Package> remotePkgs) { | |
if (remotePkgs.size() > 0) { | |
return; | |
} | |
// Get all the available packages from all loaded sources | |
RepoSource[] remoteSources = mSources.getSources(); | |
for (RepoSource remoteSrc : remoteSources) { | |
Package[] pkgs = remoteSrc.getPackages(); | |
if (pkgs != null) { | |
nextPackage: for (Package pkg : pkgs) { | |
for (Archive a : pkg.getArchives()) { | |
// Only add a package if it contains at least one compatible archive | |
if (a.isCompatible()) { | |
remotePkgs.add(pkg); | |
continue nextPackage; | |
} | |
} | |
} | |
} | |
} | |
} | |
} |