2 * Copyright (C) 2009 The Android Open Source Project
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 package com.android.sdkuilib.internal.repository;
\r
19 import com.android.sdklib.AndroidVersion;
\r
20 import com.android.sdklib.internal.repository.AddonPackage;
\r
21 import com.android.sdklib.internal.repository.Archive;
\r
22 import com.android.sdklib.internal.repository.DocPackage;
\r
23 import com.android.sdklib.internal.repository.ExtraPackage;
\r
24 import com.android.sdklib.internal.repository.IPackageVersion;
\r
25 import com.android.sdklib.internal.repository.MinToolsPackage;
\r
26 import com.android.sdklib.internal.repository.Package;
\r
27 import com.android.sdklib.internal.repository.PlatformPackage;
\r
28 import com.android.sdklib.internal.repository.RepoSource;
\r
29 import com.android.sdklib.internal.repository.RepoSources;
\r
30 import com.android.sdklib.internal.repository.ToolPackage;
\r
31 import com.android.sdklib.internal.repository.Package.UpdateInfo;
\r
33 import java.util.ArrayList;
\r
34 import java.util.Collection;
\r
35 import java.util.HashMap;
\r
38 * The logic to compute which packages to install, based on the choices
\r
39 * made by the user. This adds dependent packages as needed.
\r
41 * When the user doesn't provide a selection, looks at local package to find
\r
42 * those that can be updated and compute dependencies too.
\r
44 class UpdaterLogic {
\r
47 * Compute which packages to install by taking the user selection
\r
48 * and adding dependent packages as needed.
\r
50 * When the user doesn't provide a selection, looks at local packages to find
\r
51 * those that can be updated and compute dependencies too.
\r
53 public ArrayList<ArchiveInfo> computeUpdates(
\r
54 Collection<Archive> selectedArchives,
\r
55 RepoSources sources,
\r
56 Package[] localPkgs) {
\r
58 ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
\r
59 ArrayList<Package> remotePkgs = new ArrayList<Package>();
\r
60 RepoSource[] remoteSources = sources.getSources();
\r
62 if (selectedArchives == null) {
\r
63 selectedArchives = findUpdates(localPkgs, remotePkgs, remoteSources);
\r
66 for (Archive a : selectedArchives) {
\r
73 false /*automated*/);
\r
80 * Finds new platforms that the user does not have in his/her local SDK
\r
81 * and adds them to the list of archives to install.
\r
83 public void addNewPlatforms(ArrayList<ArchiveInfo> archives,
\r
84 RepoSources sources,
\r
85 Package[] localPkgs) {
\r
87 // Find the highest platform installed
\r
88 float currentPlatformScore = 0;
\r
89 float currentAddonScore = 0;
\r
90 float currentDocScore = 0;
\r
91 HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
\r
92 for (Package p : localPkgs) {
\r
93 int rev = p.getRevision();
\r
95 boolean isPreview = false;
\r
96 if (p instanceof IPackageVersion) {
\r
97 AndroidVersion vers = ((IPackageVersion) p).getVersion();
\r
98 api = vers.getApiLevel();
\r
99 isPreview = vers.isPreview();
\r
102 // The score is 10*api + (1 if preview) + rev/100
\r
103 // This allows previews to rank above a non-preview and
\r
104 // allows revisions to rank appropriately.
\r
105 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
\r
107 if (p instanceof PlatformPackage) {
\r
108 currentPlatformScore = Math.max(currentPlatformScore, score);
\r
109 } else if (p instanceof AddonPackage) {
\r
110 currentAddonScore = Math.max(currentAddonScore, score);
\r
111 } else if (p instanceof ExtraPackage) {
\r
112 currentExtraScore.put(((ExtraPackage) p).getPath(), score);
\r
113 } else if (p instanceof DocPackage) {
\r
114 currentDocScore = Math.max(currentDocScore, score);
\r
118 RepoSource[] remoteSources = sources.getSources();
\r
119 ArrayList<Package> remotePkgs = new ArrayList<Package>();
\r
120 fetchRemotePackages(remotePkgs, remoteSources);
\r
122 Package suggestedDoc = null;
\r
124 for (Package p : remotePkgs) {
\r
125 int rev = p.getRevision();
\r
127 boolean isPreview = false;
\r
128 if (p instanceof IPackageVersion) {
\r
129 AndroidVersion vers = ((IPackageVersion) p).getVersion();
\r
130 api = vers.getApiLevel();
\r
131 isPreview = vers.isPreview();
\r
134 float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
\r
136 boolean shouldAdd = false;
\r
137 if (p instanceof PlatformPackage) {
\r
138 shouldAdd = score > currentPlatformScore;
\r
139 } else if (p instanceof AddonPackage) {
\r
140 shouldAdd = score > currentAddonScore;
\r
141 } else if (p instanceof ExtraPackage) {
\r
142 String key = ((ExtraPackage) p).getPath();
\r
143 shouldAdd = !currentExtraScore.containsKey(key) ||
\r
144 score > currentExtraScore.get(key).floatValue();
\r
145 } else if (p instanceof DocPackage) {
\r
146 // We don't want all the doc, only the most recent one
\r
147 if (score > currentDocScore) {
\r
149 currentDocScore = score;
\r
154 // We should suggest this package for installation.
\r
155 for (Archive a : p.getArchives()) {
\r
156 if (a.isCompatible()) {
\r
159 null /*selectedArchives*/,
\r
163 true /*automated*/);
\r
169 if (suggestedDoc != null) {
\r
170 // We should suggest this package for installation.
\r
171 for (Archive a : suggestedDoc.getArchives()) {
\r
172 if (a.isCompatible()) {
\r
175 null /*selectedArchives*/,
\r
179 true /*automated*/);
\r
187 * Find suitable updates to all current local packages.
\r
189 private Collection<Archive> findUpdates(Package[] localPkgs,
\r
190 ArrayList<Package> remotePkgs,
\r
191 RepoSource[] remoteSources) {
\r
192 ArrayList<Archive> updates = new ArrayList<Archive>();
\r
194 fetchRemotePackages(remotePkgs, remoteSources);
\r
196 for (Package localPkg : localPkgs) {
\r
197 for (Package remotePkg : remotePkgs) {
\r
198 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
\r
199 // Found a suitable update. Only accept the remote package
\r
200 // if it provides at least one compatible archive.
\r
202 for (Archive a : remotePkg.getArchives()) {
\r
203 if (a.isCompatible()) {
\r
215 private ArchiveInfo insertArchive(Archive archive,
\r
216 ArrayList<ArchiveInfo> outArchives,
\r
217 Collection<Archive> selectedArchives,
\r
218 ArrayList<Package> remotePkgs,
\r
219 RepoSource[] remoteSources,
\r
220 Package[] localPkgs,
\r
221 boolean automated) {
\r
222 Package p = archive.getParentPackage();
\r
224 // Is this an update?
\r
225 Archive updatedArchive = null;
\r
226 for (Package lp : localPkgs) {
\r
227 assert lp.getArchives().length == 1;
\r
228 if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
\r
229 updatedArchive = lp.getArchives()[0];
\r
233 // Find dependencies
\r
234 ArchiveInfo[] deps = findDependency(p,
\r
241 // Make sure it's not a dup
\r
242 ArchiveInfo ai = null;
\r
244 for (ArchiveInfo ai2 : outArchives) {
\r
245 if (ai2.getNewArchive().getParentPackage().sameItemAs(archive.getParentPackage())) {
\r
252 ai = new ArchiveInfo(
\r
253 archive, //newArchive
\r
254 updatedArchive, //replaced
\r
257 outArchives.add(ai);
\r
260 if (deps != null) {
\r
261 for (ArchiveInfo d : deps) {
\r
262 d.addDependencyFor(ai);
\r
269 private ArchiveInfo[] findDependency(Package pkg,
\r
270 ArrayList<ArchiveInfo> outArchives,
\r
271 Collection<Archive> selectedArchives,
\r
272 ArrayList<Package> remotePkgs,
\r
273 RepoSource[] remoteSources,
\r
274 Package[] localPkgs) {
\r
276 // Current dependencies can be:
\r
277 // - addon: *always* depends on platform of same API level
\r
278 // - platform: *might* depends on tools of rev >= min-tools-rev
\r
279 // - extra: *might* depends on platform with api >= min-api-level
\r
281 if (pkg instanceof AddonPackage) {
\r
282 AddonPackage addon = (AddonPackage) pkg;
\r
284 ArchiveInfo ai = findPlatformDependency(
\r
293 return new ArchiveInfo[] { ai };
\r
296 } else if (pkg instanceof MinToolsPackage) {
\r
297 MinToolsPackage platformOrExtra = (MinToolsPackage) pkg;
\r
300 ArchiveInfo ai1 = findToolsDependency(
\r
308 n += ai1 == null ? 0 : 1;
\r
310 ArchiveInfo ai2 = null;
\r
311 if (pkg instanceof ExtraPackage) {
\r
312 ai2 = findExtraPlatformDependency(
\r
313 (ExtraPackage) pkg,
\r
321 n += ai2 == null ? 0 : 1;
\r
324 ArchiveInfo[] ais = new ArchiveInfo[n];
\r
325 ais[0] = ai1 != null ? ai1 : ai2;
\r
326 if (n > 1) ais[1] = ai2;
\r
335 * Resolves dependencies on tools.
\r
337 * A platform or an extra package can both have a min-tools-rev, in which case it
\r
338 * depends on having a tools package of the requested revision.
\r
339 * Finds the tools dependency. If found, add it to the list of things to install.
\r
340 * Returns the archive info dependency, if any.
\r
342 protected ArchiveInfo findToolsDependency(MinToolsPackage platformOrExtra,
\r
343 ArrayList<ArchiveInfo> outArchives,
\r
344 Collection<Archive> selectedArchives,
\r
345 ArrayList<Package> remotePkgs,
\r
346 RepoSource[] remoteSources,
\r
347 Package[] localPkgs) {
\r
348 // This is the requirement to match.
\r
349 int rev = platformOrExtra.getMinToolsRevision();
\r
351 if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
\r
352 // Well actually there's no requirement.
\r
356 // First look in locally installed packages.
\r
357 for (Package p : localPkgs) {
\r
358 if (p instanceof ToolPackage) {
\r
359 if (((ToolPackage) p).getRevision() >= rev) {
\r
360 // We found one already installed. We don't report this dependency
\r
361 // as the UI only cares about resolving "newly added dependencies".
\r
367 // Look in archives already scheduled for install
\r
368 for (ArchiveInfo ai : outArchives) {
\r
369 Package p = ai.getNewArchive().getParentPackage();
\r
370 if (p instanceof ToolPackage) {
\r
371 if (((ToolPackage) p).getRevision() >= rev) {
\r
372 // The dependency is already scheduled for install, nothing else to do.
\r
378 // Otherwise look in the selected archives.
\r
379 if (selectedArchives != null) {
\r
380 for (Archive a : selectedArchives) {
\r
381 Package p = a.getParentPackage();
\r
382 if (p instanceof ToolPackage) {
\r
383 if (((ToolPackage) p).getRevision() >= rev) {
\r
384 // It's not already in the list of things to install, so add it now
\r
385 return insertArchive(a, outArchives,
\r
386 selectedArchives, remotePkgs, remoteSources, localPkgs,
\r
387 true /*automated*/);
\r
393 // Finally nothing matched, so let's look at all available remote packages
\r
394 fetchRemotePackages(remotePkgs, remoteSources);
\r
395 for (Package p : remotePkgs) {
\r
396 if (p instanceof ToolPackage) {
\r
397 if (((ToolPackage) p).getRevision() >= rev) {
\r
398 // It's not already in the list of things to install, so add the
\r
399 // first compatible archive we can find.
\r
400 for (Archive a : p.getArchives()) {
\r
401 if (a.isCompatible()) {
\r
402 return insertArchive(a, outArchives,
\r
403 selectedArchives, remotePkgs, remoteSources, localPkgs,
\r
404 true /*automated*/);
\r
411 // We end up here if nothing matches. We don't have a good tools to match.
\r
412 // Seriously, that can't happens unless we totally screwed our repo manifest.
\r
413 // We'll let this one go through anyway.
\r
418 * Resolves dependencies on platform.
\r
420 * An addon depends on having a platform with the same API level.
\r
422 * Finds the platform dependency. If found, add it to the list of things to install.
\r
423 * Returns the archive info dependency, if any.
\r
425 protected ArchiveInfo findPlatformDependency(
\r
426 AddonPackage addon,
\r
427 ArrayList<ArchiveInfo> outArchives,
\r
428 Collection<Archive> selectedArchives,
\r
429 ArrayList<Package> remotePkgs,
\r
430 RepoSource[] remoteSources,
\r
431 Package[] localPkgs) {
\r
432 // This is the requirement to match.
\r
433 AndroidVersion v = addon.getVersion();
\r
435 // Find a platform that would satisfy the requirement.
\r
437 // First look in locally installed packages.
\r
438 for (Package p : localPkgs) {
\r
439 if (p instanceof PlatformPackage) {
\r
440 if (v.equals(((PlatformPackage) p).getVersion())) {
\r
441 // We found one already installed. We don't report this dependency
\r
442 // as the UI only cares about resolving "newly added dependencies".
\r
448 // Look in archives already scheduled for install
\r
449 for (ArchiveInfo ai : outArchives) {
\r
450 Package p = ai.getNewArchive().getParentPackage();
\r
451 if (p instanceof PlatformPackage) {
\r
452 if (v.equals(((PlatformPackage) p).getVersion())) {
\r
453 // The dependency is already scheduled for install, nothing else to do.
\r
459 // Otherwise look in the selected archives.
\r
460 if (selectedArchives != null) {
\r
461 for (Archive a : selectedArchives) {
\r
462 Package p = a.getParentPackage();
\r
463 if (p instanceof PlatformPackage) {
\r
464 if (v.equals(((PlatformPackage) p).getVersion())) {
\r
465 // It's not already in the list of things to install, so add it now
\r
466 return insertArchive(a, outArchives,
\r
467 selectedArchives, remotePkgs, remoteSources, localPkgs,
\r
468 true /*automated*/);
\r
474 // Finally nothing matched, so let's look at all available remote packages
\r
475 fetchRemotePackages(remotePkgs, remoteSources);
\r
476 for (Package p : remotePkgs) {
\r
477 if (p instanceof PlatformPackage) {
\r
478 if (v.equals(((PlatformPackage) p).getVersion())) {
\r
479 // It's not already in the list of things to install, so add the
\r
480 // first compatible archive we can find.
\r
481 for (Archive a : p.getArchives()) {
\r
482 if (a.isCompatible()) {
\r
483 return insertArchive(a, outArchives,
\r
484 selectedArchives, remotePkgs, remoteSources, localPkgs,
\r
485 true /*automated*/);
\r
492 // We end up here if nothing matches. We don't have a good platform to match.
\r
493 // Seriously, that can't happens unless the repository contains a bogus addon
\r
494 // entry that does not match any existing platform API level.
\r
495 // It's conceivable that a 3rd part addon repo might have error, in which case
\r
496 // we'll let this one go through anyway.
\r
501 * Resolves platform dependencies for extras.
\r
502 * An extra depends on having a platform with a minimun API level.
\r
504 * We try to return the highest API level available above the specified minimum.
\r
505 * Note that installed packages have priority so if one installed platform satisfies
\r
506 * the dependency, we'll use it even if there's a higher API platform available but
\r
507 * not installed yet.
\r
509 * Finds the platform dependency. If found, add it to the list of things to install.
\r
510 * Returns the archive info dependency, if any.
\r
512 protected ArchiveInfo findExtraPlatformDependency(
\r
513 ExtraPackage extra,
\r
514 ArrayList<ArchiveInfo> outArchives,
\r
515 Collection<Archive> selectedArchives,
\r
516 ArrayList<Package> remotePkgs,
\r
517 RepoSource[] remoteSources,
\r
518 Package[] localPkgs) {
\r
520 int api = extra.getMinApiLevel();
\r
522 if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) {
\r
526 // Find a platform that would satisfy the requirement.
\r
528 // First look in locally installed packages.
\r
529 for (Package p : localPkgs) {
\r
530 if (p instanceof PlatformPackage) {
\r
531 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
\r
532 // We found one already installed. We don't report this dependency
\r
533 // as the UI only cares about resolving "newly added dependencies".
\r
539 // Look in archives already scheduled for install
\r
541 ArchiveInfo foundAi = null;
\r
543 for (ArchiveInfo ai : outArchives) {
\r
544 Package p = ai.getNewArchive().getParentPackage();
\r
545 if (p instanceof PlatformPackage) {
\r
546 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
\r
547 if (api > foundApi) {
\r
555 if (foundAi != null) {
\r
556 // The dependency is already scheduled for install, nothing else to do.
\r
560 // Otherwise look in the selected archives *or* available remote packages
\r
561 // and takes the best out of the two sets.
\r
563 Archive foundArchive = null;
\r
564 if (selectedArchives != null) {
\r
565 for (Archive a : selectedArchives) {
\r
566 Package p = a.getParentPackage();
\r
567 if (p instanceof PlatformPackage) {
\r
568 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
\r
569 if (api > foundApi) {
\r
578 // Finally nothing matched, so let's look at all available remote packages
\r
579 fetchRemotePackages(remotePkgs, remoteSources);
\r
580 for (Package p : remotePkgs) {
\r
581 if (p instanceof PlatformPackage) {
\r
582 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
\r
583 if (api > foundApi) {
\r
584 // It's not already in the list of things to install, so add the
\r
585 // first compatible archive we can find.
\r
586 for (Archive a : p.getArchives()) {
\r
587 if (a.isCompatible()) {
\r
597 if (foundArchive != null) {
\r
598 // It's not already in the list of things to install, so add it now
\r
599 return insertArchive(foundArchive, outArchives,
\r
600 selectedArchives, remotePkgs, remoteSources, localPkgs,
\r
601 true /*automated*/);
\r
604 // We end up here if nothing matches. We don't have a good platform to match.
\r
605 // Seriously, that can't happens unless the repository contains a bogus extra
\r
606 // entry that does not match any existing platform API level.
\r
607 // It's conceivable that a 3rd part addon repo might have error, in which case
\r
608 // we'll let this one go through anyway.
\r
612 /** Fetch all remote packages only if really needed. */
\r
613 protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) {
\r
614 if (remotePkgs.size() > 0) {
\r
618 for (RepoSource remoteSrc : remoteSources) {
\r
619 Package[] pkgs = remoteSrc.getPackages();
\r
620 if (pkgs != null) {
\r
621 nextPackage: for (Package pkg : pkgs) {
\r
622 for (Archive a : pkg.getArchives()) {
\r
623 // Only add a package if it contains at least one compatible archive
\r
624 if (a.isCompatible()) {
\r
625 remotePkgs.add(pkg);
\r
626 continue nextPackage;
\r