• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdkuilib.internal.repository;
18 
19 import com.android.sdklib.AndroidVersion;
20 import com.android.sdklib.internal.repository.AddonPackage;
21 import com.android.sdklib.internal.repository.Archive;
22 import com.android.sdklib.internal.repository.DocPackage;
23 import com.android.sdklib.internal.repository.ExtraPackage;
24 import com.android.sdklib.internal.repository.IExactApiLevelDependency;
25 import com.android.sdklib.internal.repository.IMinApiLevelDependency;
26 import com.android.sdklib.internal.repository.IMinPlatformToolsDependency;
27 import com.android.sdklib.internal.repository.IMinToolsDependency;
28 import com.android.sdklib.internal.repository.IPackageVersion;
29 import com.android.sdklib.internal.repository.IPlatformDependency;
30 import com.android.sdklib.internal.repository.ITask;
31 import com.android.sdklib.internal.repository.ITaskMonitor;
32 import com.android.sdklib.internal.repository.MinToolsPackage;
33 import com.android.sdklib.internal.repository.Package;
34 import com.android.sdklib.internal.repository.PlatformPackage;
35 import com.android.sdklib.internal.repository.PlatformToolPackage;
36 import com.android.sdklib.internal.repository.SamplePackage;
37 import com.android.sdklib.internal.repository.SdkSource;
38 import com.android.sdklib.internal.repository.SdkSources;
39 import com.android.sdklib.internal.repository.ToolPackage;
40 import com.android.sdklib.internal.repository.Package.UpdateInfo;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Set;
49 
50 /**
51  * The logic to compute which packages to install, based on the choices
52  * made by the user. This adds required packages as needed.
53  * <p/>
54  * When the user doesn't provide a selection, looks at local package to find
55  * those that can be updated and compute dependencies too.
56  */
57 class SdkUpdaterLogic {
58 
59     private final IUpdaterData mUpdaterData;
60 
SdkUpdaterLogic(IUpdaterData updaterData)61     public SdkUpdaterLogic(IUpdaterData updaterData) {
62         mUpdaterData = updaterData;
63     }
64 
65     /**
66      * Compute which packages to install by taking the user selection
67      * and adding required packages as needed.
68      *
69      * When the user doesn't provide a selection, looks at local packages to find
70      * those that can be updated and compute dependencies too.
71      */
computeUpdates( Collection<Archive> selectedArchives, SdkSources sources, Package[] localPkgs, boolean includeObsoletes)72     public List<ArchiveInfo> computeUpdates(
73             Collection<Archive> selectedArchives,
74             SdkSources sources,
75             Package[] localPkgs,
76             boolean includeObsoletes) {
77 
78         List<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
79         List<Package>   remotePkgs = new ArrayList<Package>();
80         SdkSource[] remoteSources = sources.getAllSources();
81 
82         // Create ArchiveInfos out of local (installed) packages.
83         ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
84 
85         // If we do not have a specific list of archives to install (that is the user
86         // selected "update all" rather than request specific packages), then we try to
87         // find updates based on the *existing* packages.
88         if (selectedArchives == null) {
89             selectedArchives = findUpdates(
90                     localArchives,
91                     remotePkgs,
92                     remoteSources,
93                     includeObsoletes);
94         }
95 
96         // Once we have a list of packages to install, we try to solve all their
97         // dependencies by automatically adding them to the list of things to install.
98         // This works on the list provided either by the user directly or the list
99         // computed from potential updates.
100         for (Archive a : selectedArchives) {
101             insertArchive(a,
102                     archives,
103                     selectedArchives,
104                     remotePkgs,
105                     remoteSources,
106                     localArchives,
107                     false /*automated*/);
108         }
109 
110         // Finally we need to look at *existing* packages which are not being updated
111         // and check if they have any missing dependencies and suggest how to fix
112         // these dependencies.
113         fixMissingLocalDependencies(
114                 archives,
115                 selectedArchives,
116                 remotePkgs,
117                 remoteSources,
118                 localArchives);
119 
120         return archives;
121     }
122 
123     /**
124      * Finds new packages that the user does not have in his/her local SDK
125      * and adds them to the list of archives to install.
126      * <p/>
127      * The default is to only find "new" platforms, that is anything more
128      * recent than the highest platform currently installed.
129      * A side effect is that for an empty SDK install this will list *all*
130      * platforms available (since there's no "highest" installed platform.)
131      *
132      * @param archives The in-out list of archives to install. Typically the
133      *  list is not empty at first as it should contain any archives that is
134      *  already scheduled for install. This method will add to the list.
135      * @param sources The list of all sources, to fetch them as necessary.
136      * @param localPkgs The list of all currently installed packages.
137      * @param includeObsoletes When true, this will list all platform
138      * (included these lower than the highest installed one) as well as
139      * all obsolete packages of these platforms.
140      */
addNewPlatforms( Collection<ArchiveInfo> archives, SdkSources sources, Package[] localPkgs, boolean includeObsoletes)141     public void addNewPlatforms(
142             Collection<ArchiveInfo> archives,
143             SdkSources sources,
144             Package[] localPkgs,
145             boolean includeObsoletes) {
146 
147         // Create ArchiveInfos out of local (installed) packages.
148         ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
149 
150         // Find the highest platform installed
151         float currentPlatformScore = 0;
152         float currentSampleScore = 0;
153         float currentAddonScore = 0;
154         float currentDocScore = 0;
155         HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
156         if (!includeObsoletes) {
157             if (localPkgs != null) {
158                 for (Package p : localPkgs) {
159                     int rev = p.getRevision();
160                     int api = 0;
161                     boolean isPreview = false;
162                     if (p instanceof IPackageVersion) {
163                         AndroidVersion vers = ((IPackageVersion) p).getVersion();
164                         api = vers.getApiLevel();
165                         isPreview = vers.isPreview();
166                     }
167 
168                     // The score is 10*api + (1 if preview) + rev/100
169                     // This allows previews to rank above a non-preview and
170                     // allows revisions to rank appropriately.
171                     float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
172 
173                     if (p instanceof PlatformPackage) {
174                         currentPlatformScore = Math.max(currentPlatformScore, score);
175                     } else if (p instanceof SamplePackage) {
176                         currentSampleScore = Math.max(currentSampleScore, score);
177                     } else if (p instanceof AddonPackage) {
178                         currentAddonScore = Math.max(currentAddonScore, score);
179                     } else if (p instanceof ExtraPackage) {
180                         currentExtraScore.put(((ExtraPackage) p).getPath(), score);
181                     } else if (p instanceof DocPackage) {
182                         currentDocScore = Math.max(currentDocScore, score);
183                     }
184                 }
185             }
186         }
187 
188         SdkSource[] remoteSources = sources.getAllSources();
189         ArrayList<Package> remotePkgs = new ArrayList<Package>();
190         fetchRemotePackages(remotePkgs, remoteSources);
191 
192         Package suggestedDoc = null;
193 
194         for (Package p : remotePkgs) {
195             // Skip obsolete packages unless requested to include them.
196             if (p.isObsolete() && !includeObsoletes) {
197                 continue;
198             }
199 
200             int rev = p.getRevision();
201             int api = 0;
202             boolean isPreview = false;
203             if (p instanceof  IPackageVersion) {
204                 AndroidVersion vers = ((IPackageVersion) p).getVersion();
205                 api = vers.getApiLevel();
206                 isPreview = vers.isPreview();
207             }
208 
209             float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
210 
211             boolean shouldAdd = false;
212             if (p instanceof PlatformPackage) {
213                 shouldAdd = score > currentPlatformScore;
214             } else if (p instanceof SamplePackage) {
215                 shouldAdd = score > currentSampleScore;
216             } else if (p instanceof AddonPackage) {
217                 shouldAdd = score > currentAddonScore;
218             } else if (p instanceof ExtraPackage) {
219                 String key = ((ExtraPackage) p).getPath();
220                 shouldAdd = !currentExtraScore.containsKey(key) ||
221                     score > currentExtraScore.get(key).floatValue();
222             } else if (p instanceof DocPackage) {
223                 // We don't want all the doc, only the most recent one
224                 if (score > currentDocScore) {
225                     suggestedDoc = p;
226                     currentDocScore = score;
227                 }
228             }
229 
230             if (shouldAdd) {
231                 // We should suggest this package for installation.
232                 for (Archive a : p.getArchives()) {
233                     if (a.isCompatible()) {
234                         insertArchive(a,
235                                 archives,
236                                 null /*selectedArchives*/,
237                                 remotePkgs,
238                                 remoteSources,
239                                 localArchives,
240                                 true /*automated*/);
241                     }
242                 }
243             }
244         }
245 
246         if (suggestedDoc != null) {
247             // We should suggest this package for installation.
248             for (Archive a : suggestedDoc.getArchives()) {
249                 if (a.isCompatible()) {
250                     insertArchive(a,
251                             archives,
252                             null /*selectedArchives*/,
253                             remotePkgs,
254                             remoteSources,
255                             localArchives,
256                             true /*automated*/);
257                 }
258             }
259         }
260     }
261 
262     /**
263      * Create a array of {@link ArchiveInfo} based on all local (already installed)
264      * packages. The array is always non-null but may be empty.
265      * <p/>
266      * The local {@link ArchiveInfo} are guaranteed to have one non-null archive
267      * that you can retrieve using {@link ArchiveInfo#getNewArchive()}.
268      */
createLocalArchives(Package[] localPkgs)269     protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) {
270 
271         if (localPkgs != null) {
272             ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
273             for (Package p : localPkgs) {
274                 // Only accept packages that have one compatible archive.
275                 // Local package should have 1 and only 1 compatible archive anyway.
276                 for (Archive a : p.getArchives()) {
277                     if (a != null && a.isCompatible()) {
278                         // We create an "installed" archive info to wrap the local package.
279                         // Note that dependencies are not computed since right now we don't
280                         // deal with more than one level of dependencies and installed archives
281                         // are deemed implicitly accepted anyway.
282                         list.add(new LocalArchiveInfo(a));
283                     }
284                 }
285             }
286 
287             return list.toArray(new ArchiveInfo[list.size()]);
288         }
289 
290         return new ArchiveInfo[0];
291     }
292 
293     /**
294      * Find suitable updates to all current local packages.
295      * <p/>
296      * Returns a list of potential updates for *existing* packages. This does NOT solve
297      * dependencies for the new packages.
298      * <p/>
299      * Always returns a non-null collection, which can be empty.
300      */
findUpdates( ArchiveInfo[] localArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, boolean includeObsoletes)301     private Collection<Archive> findUpdates(
302             ArchiveInfo[] localArchives,
303             Collection<Package> remotePkgs,
304             SdkSource[] remoteSources,
305             boolean includeObsoletes) {
306         ArrayList<Archive> updates = new ArrayList<Archive>();
307 
308         fetchRemotePackages(remotePkgs, remoteSources);
309 
310         for (ArchiveInfo ai : localArchives) {
311             Archive na = ai.getNewArchive();
312             if (na == null) {
313                 continue;
314             }
315             Package localPkg = na.getParentPackage();
316 
317             for (Package remotePkg : remotePkgs) {
318                 // Only look for non-obsolete updates unless requested to include them
319                 if ((includeObsoletes || !remotePkg.isObsolete()) &&
320                         localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
321                     // Found a suitable update. Only accept the remote package
322                     // if it provides at least one compatible archive
323 
324                     addArchives:
325                     for (Archive a : remotePkg.getArchives()) {
326                         if (a.isCompatible()) {
327 
328                             // If we're trying to add a package for revision N,
329                             // make sure we don't also have a package for revision N-1.
330                             for (int i = updates.size() - 1; i >= 0; i--) {
331                                 Package pkgFound = updates.get(i).getParentPackage();
332                                 if (pkgFound.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
333                                     // This package can update one we selected earlier.
334                                     // Remove the one that can be updated by this new one.
335                                    updates.remove(i);
336                                 } else if (remotePkg.canBeUpdatedBy(pkgFound) ==
337                                                 UpdateInfo.UPDATE) {
338                                     // There is a package in the list that is already better
339                                     // than the one we want to add, so don't add it.
340                                     break addArchives;
341                                 }
342                             }
343 
344                             updates.add(a);
345                             break;
346                         }
347                     }
348                 }
349             }
350         }
351 
352         return updates;
353     }
354 
355     /**
356      * Check all local archives which are NOT being updated and see if they
357      * miss any dependency. If they do, try to fix that dependency by selecting
358      * an appropriate package.
359      */
fixMissingLocalDependencies( Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)360     private void fixMissingLocalDependencies(
361             Collection<ArchiveInfo> outArchives,
362             Collection<Archive> selectedArchives,
363             Collection<Package> remotePkgs,
364             SdkSource[] remoteSources,
365             ArchiveInfo[] localArchives) {
366 
367         nextLocalArchive: for (ArchiveInfo ai : localArchives) {
368             Archive a = ai.getNewArchive();
369             Package p = a == null ? null : a.getParentPackage();
370             if (p == null) {
371                 continue;
372             }
373 
374             // Is this local archive being updated?
375             for (ArchiveInfo ai2 : outArchives) {
376                 if (ai2.getReplaced() == a) {
377                     // this new archive will replace the current local one,
378                     // so we don't have to care about fixing dependencies (since the
379                     // new archive should already have had its dependencies resolved)
380                     continue nextLocalArchive;
381                 }
382             }
383 
384             // find dependencies for the local archive and add them as needed
385             // to the outArchives collection.
386             ArchiveInfo[] deps = findDependency(p,
387                   outArchives,
388                   selectedArchives,
389                   remotePkgs,
390                   remoteSources,
391                   localArchives);
392 
393             if (deps != null) {
394                 // The already installed archive has a missing dependency, which we
395                 // just selected for install. Make sure we remember the dependency
396                 // so that we can enforce it later in the UI.
397                 for (ArchiveInfo aid : deps) {
398                     aid.addDependencyFor(ai);
399                 }
400             }
401         }
402     }
403 
insertArchive(Archive archive, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives, boolean automated)404     private ArchiveInfo insertArchive(Archive archive,
405             Collection<ArchiveInfo> outArchives,
406             Collection<Archive> selectedArchives,
407             Collection<Package> remotePkgs,
408             SdkSource[] remoteSources,
409             ArchiveInfo[] localArchives,
410             boolean automated) {
411         Package p = archive.getParentPackage();
412 
413         // Is this an update?
414         Archive updatedArchive = null;
415         for (ArchiveInfo ai : localArchives) {
416             Archive a = ai.getNewArchive();
417             if (a != null) {
418                 Package lp = a.getParentPackage();
419 
420                 if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
421                     updatedArchive = a;
422                 }
423             }
424         }
425 
426         // Find dependencies and adds them as needed to outArchives
427         ArchiveInfo[] deps = findDependency(p,
428                 outArchives,
429                 selectedArchives,
430                 remotePkgs,
431                 remoteSources,
432                 localArchives);
433 
434         // Make sure it's not a dup
435         ArchiveInfo ai = null;
436 
437         for (ArchiveInfo ai2 : outArchives) {
438             Archive a2 = ai2.getNewArchive();
439             if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {
440                 ai = ai2;
441                 break;
442             }
443         }
444 
445         if (ai == null) {
446             ai = new ArchiveInfo(
447                 archive,        //newArchive
448                 updatedArchive, //replaced
449                 deps            //dependsOn
450                 );
451             outArchives.add(ai);
452         }
453 
454         if (deps != null) {
455             for (ArchiveInfo d : deps) {
456                 d.addDependencyFor(ai);
457             }
458         }
459 
460         return ai;
461     }
462 
463     /**
464      * Resolves dependencies for a given package.
465      *
466      * Returns null if no dependencies were found.
467      * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have
468      * at least size 1 and contain no null elements.
469      */
findDependency(Package pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)470     private ArchiveInfo[] findDependency(Package pkg,
471             Collection<ArchiveInfo> outArchives,
472             Collection<Archive> selectedArchives,
473             Collection<Package> remotePkgs,
474             SdkSource[] remoteSources,
475             ArchiveInfo[] localArchives) {
476 
477         // Current dependencies can be:
478         // - addon: *always* depends on platform of same API level
479         // - platform: *might* depends on tools of rev >= min-tools-rev
480         // - extra: *might* depends on platform with api >= min-api-level
481 
482         Set<ArchiveInfo> aiFound = new HashSet<ArchiveInfo>();
483 
484         if (pkg instanceof IPlatformDependency) {
485             ArchiveInfo ai = findPlatformDependency(
486                     (IPlatformDependency) pkg,
487                     outArchives,
488                     selectedArchives,
489                     remotePkgs,
490                     remoteSources,
491                     localArchives);
492 
493             if (ai != null) {
494                 aiFound.add(ai);
495             }
496         }
497 
498         if (pkg instanceof IMinToolsDependency) {
499 
500             ArchiveInfo ai = findToolsDependency(
501                     (IMinToolsDependency) pkg,
502                     outArchives,
503                     selectedArchives,
504                     remotePkgs,
505                     remoteSources,
506                     localArchives);
507 
508             if (ai != null) {
509                 aiFound.add(ai);
510             }
511         }
512 
513         if (pkg instanceof IMinPlatformToolsDependency) {
514 
515             ArchiveInfo ai = findPlatformToolsDependency(
516                     (IMinPlatformToolsDependency) pkg,
517                     outArchives,
518                     selectedArchives,
519                     remotePkgs,
520                     remoteSources,
521                     localArchives);
522 
523             if (ai != null) {
524                 aiFound.add(ai);
525             }
526         }
527 
528         if (pkg instanceof IMinApiLevelDependency) {
529 
530             ArchiveInfo ai = findMinApiLevelDependency(
531                     (IMinApiLevelDependency) pkg,
532                     outArchives,
533                     selectedArchives,
534                     remotePkgs,
535                     remoteSources,
536                     localArchives);
537 
538             if (ai != null) {
539                 aiFound.add(ai);
540             }
541         }
542 
543         if (pkg instanceof IExactApiLevelDependency) {
544 
545             ArchiveInfo ai = findExactApiLevelDependency(
546                     (IExactApiLevelDependency) pkg,
547                     outArchives,
548                     selectedArchives,
549                     remotePkgs,
550                     remoteSources,
551                     localArchives);
552 
553             if (ai != null) {
554                 aiFound.add(ai);
555             }
556         }
557 
558         if (aiFound.size() > 0) {
559             ArchiveInfo[] result = aiFound.toArray(new ArchiveInfo[aiFound.size()]);
560             Arrays.sort(result);
561             return result;
562         }
563 
564         return null;
565     }
566 
567     /**
568      * Resolves dependencies on tools.
569      *
570      * A platform or an extra package can both have a min-tools-rev, in which case it
571      * depends on having a tools package of the requested revision.
572      * Finds the tools dependency. If found, add it to the list of things to install.
573      * Returns the archive info dependency, if any.
574      */
findToolsDependency( IMinToolsDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)575     protected ArchiveInfo findToolsDependency(
576             IMinToolsDependency pkg,
577             Collection<ArchiveInfo> outArchives,
578             Collection<Archive> selectedArchives,
579             Collection<Package> remotePkgs,
580             SdkSource[] remoteSources,
581             ArchiveInfo[] localArchives) {
582         // This is the requirement to match.
583         int rev = pkg.getMinToolsRevision();
584 
585         if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
586             // Well actually there's no requirement.
587             return null;
588         }
589 
590         // First look in locally installed packages.
591         for (ArchiveInfo ai : localArchives) {
592             Archive a = ai.getNewArchive();
593             if (a != null) {
594                 Package p = a.getParentPackage();
595                 if (p instanceof ToolPackage) {
596                     if (((ToolPackage) p).getRevision() >= rev) {
597                         // We found one already installed.
598                         return null;
599                     }
600                 }
601             }
602         }
603 
604         // Look in archives already scheduled for install
605         for (ArchiveInfo ai : outArchives) {
606             Archive a = ai.getNewArchive();
607             if (a != null) {
608                 Package p = a.getParentPackage();
609                 if (p instanceof ToolPackage) {
610                     if (((ToolPackage) p).getRevision() >= rev) {
611                         // The dependency is already scheduled for install, nothing else to do.
612                         return ai;
613                     }
614                 }
615             }
616         }
617 
618         // Otherwise look in the selected archives.
619         if (selectedArchives != null) {
620             for (Archive a : selectedArchives) {
621                 Package p = a.getParentPackage();
622                 if (p instanceof ToolPackage) {
623                     if (((ToolPackage) p).getRevision() >= rev) {
624                         // It's not already in the list of things to install, so add it now
625                         return insertArchive(a,
626                                 outArchives,
627                                 selectedArchives,
628                                 remotePkgs,
629                                 remoteSources,
630                                 localArchives,
631                                 true /*automated*/);
632                     }
633                 }
634             }
635         }
636 
637         // Finally nothing matched, so let's look at all available remote packages
638         fetchRemotePackages(remotePkgs, remoteSources);
639         for (Package p : remotePkgs) {
640             if (p instanceof ToolPackage) {
641                 if (((ToolPackage) p).getRevision() >= rev) {
642                     // It's not already in the list of things to install, so add the
643                     // first compatible archive we can find.
644                     for (Archive a : p.getArchives()) {
645                         if (a.isCompatible()) {
646                             return insertArchive(a,
647                                     outArchives,
648                                     selectedArchives,
649                                     remotePkgs,
650                                     remoteSources,
651                                     localArchives,
652                                     true /*automated*/);
653                         }
654                     }
655                 }
656             }
657         }
658 
659         // We end up here if nothing matches. We don't have a good platform to match.
660         // We need to indicate this extra depends on a missing platform archive
661         // so that it can be impossible to install later on.
662         return new MissingArchiveInfo(MissingArchiveInfo.TITLE_TOOL, rev);
663     }
664 
665     /**
666      * Resolves dependencies on platform-tools.
667      *
668      * A tool package can have a min-platform-tools-rev, in which case it depends on
669      * having a platform-tool package of the requested revision.
670      * Finds the platform-tool dependency. If found, add it to the list of things to install.
671      * Returns the archive info dependency, if any.
672      */
findPlatformToolsDependency( IMinPlatformToolsDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)673     protected ArchiveInfo findPlatformToolsDependency(
674             IMinPlatformToolsDependency pkg,
675             Collection<ArchiveInfo> outArchives,
676             Collection<Archive> selectedArchives,
677             Collection<Package> remotePkgs,
678             SdkSource[] remoteSources,
679             ArchiveInfo[] localArchives) {
680         // This is the requirement to match.
681         int rev = pkg.getMinPlatformToolsRevision();
682         boolean findMax = false;
683         ArchiveInfo aiMax = null;
684         Archive aMax = null;
685 
686         if (rev == IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID) {
687             // The requirement is invalid, which is not supposed to happen since this
688             // property is mandatory. However in a typical upgrade scenario we can end
689             // up with the previous updater managing a new package and not dealing
690             // correctly with the new unknown property.
691             // So instead we parse all the existing and remote packages and try to find
692             // the max available revision and we'll use it.
693             findMax = true;
694         }
695 
696         // First look in locally installed packages.
697         for (ArchiveInfo ai : localArchives) {
698             Archive a = ai.getNewArchive();
699             if (a != null) {
700                 Package p = a.getParentPackage();
701                 if (p instanceof PlatformToolPackage) {
702                     int r = ((PlatformToolPackage) p).getRevision();
703                     if (findMax && r > rev) {
704                         rev = r;
705                         aiMax = ai;
706                     } else if (!findMax && r >= rev) {
707                         // We found one already installed.
708                         return null;
709                     }
710                 }
711             }
712         }
713 
714         // Look in archives already scheduled for install
715         for (ArchiveInfo ai : outArchives) {
716             Archive a = ai.getNewArchive();
717             if (a != null) {
718                 Package p = a.getParentPackage();
719                 if (p instanceof PlatformToolPackage) {
720                     int r = ((PlatformToolPackage) p).getRevision();
721                     if (findMax && r > rev) {
722                         rev = r;
723                         aiMax = ai;
724                     } else if (!findMax && r >= rev) {
725                         // The dependency is already scheduled for install, nothing else to do.
726                         return ai;
727                     }
728                 }
729             }
730         }
731 
732         // Otherwise look in the selected archives.
733         if (selectedArchives != null) {
734             for (Archive a : selectedArchives) {
735                 Package p = a.getParentPackage();
736                 if (p instanceof PlatformToolPackage) {
737                     int r = ((PlatformToolPackage) p).getRevision();
738                     if (findMax && r > rev) {
739                         rev = r;
740                         aiMax = null;
741                         aMax = a;
742                     } else if (!findMax && r >= rev) {
743                         // It's not already in the list of things to install, so add it now
744                         return insertArchive(a,
745                                 outArchives,
746                                 selectedArchives,
747                                 remotePkgs,
748                                 remoteSources,
749                                 localArchives,
750                                 true /*automated*/);
751                     }
752                 }
753             }
754         }
755 
756         // Finally nothing matched, so let's look at all available remote packages
757         fetchRemotePackages(remotePkgs, remoteSources);
758         for (Package p : remotePkgs) {
759             if (p instanceof PlatformToolPackage) {
760                 int r = ((PlatformToolPackage) p).getRevision();
761                 if (r >= rev) {
762                     // Make sure there's at least one valid archive here
763                     for (Archive a : p.getArchives()) {
764                         if (a.isCompatible()) {
765                             if (findMax && r > rev) {
766                                 rev = r;
767                                 aiMax = null;
768                                 aMax = a;
769                             } else if (!findMax && r >= rev) {
770                                 // It's not already in the list of things to install, so add the
771                                 // first compatible archive we can find.
772                                 return insertArchive(a,
773                                         outArchives,
774                                         selectedArchives,
775                                         remotePkgs,
776                                         remoteSources,
777                                         localArchives,
778                                         true /*automated*/);
779                             }
780                         }
781                     }
782                 }
783             }
784         }
785 
786         if (findMax) {
787             if (aMax != null) {
788                 return insertArchive(aMax,
789                         outArchives,
790                         selectedArchives,
791                         remotePkgs,
792                         remoteSources,
793                         localArchives,
794                         true /*automated*/);
795             } else if (aiMax != null) {
796                 return aiMax;
797             }
798         }
799 
800         // We end up here if nothing matches. We don't have a good platform to match.
801         // We need to indicate this package depends on a missing platform archive
802         // so that it can be impossible to install later on.
803         return new MissingArchiveInfo(MissingArchiveInfo.TITLE_PLATFORM_TOOL, rev);
804     }
805 
806     /**
807      * Resolves dependencies on platform for an addon.
808      *
809      * An addon depends on having a platform with the same API level.
810      *
811      * Finds the platform dependency. If found, add it to the list of things to install.
812      * Returns the archive info dependency, if any.
813      */
findPlatformDependency( IPlatformDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)814     protected ArchiveInfo findPlatformDependency(
815             IPlatformDependency pkg,
816             Collection<ArchiveInfo> outArchives,
817             Collection<Archive> selectedArchives,
818             Collection<Package> remotePkgs,
819             SdkSource[] remoteSources,
820             ArchiveInfo[] localArchives) {
821         // This is the requirement to match.
822         AndroidVersion v = pkg.getVersion();
823 
824         // Find a platform that would satisfy the requirement.
825 
826         // First look in locally installed packages.
827         for (ArchiveInfo ai : localArchives) {
828             Archive a = ai.getNewArchive();
829             if (a != null) {
830                 Package p = a.getParentPackage();
831                 if (p instanceof PlatformPackage) {
832                     if (v.equals(((PlatformPackage) p).getVersion())) {
833                         // We found one already installed.
834                         return null;
835                     }
836                 }
837             }
838         }
839 
840         // Look in archives already scheduled for install
841         for (ArchiveInfo ai : outArchives) {
842             Archive a = ai.getNewArchive();
843             if (a != null) {
844                 Package p = a.getParentPackage();
845                 if (p instanceof PlatformPackage) {
846                     if (v.equals(((PlatformPackage) p).getVersion())) {
847                         // The dependency is already scheduled for install, nothing else to do.
848                         return ai;
849                     }
850                 }
851             }
852         }
853 
854         // Otherwise look in the selected archives.
855         if (selectedArchives != null) {
856             for (Archive a : selectedArchives) {
857                 Package p = a.getParentPackage();
858                 if (p instanceof PlatformPackage) {
859                     if (v.equals(((PlatformPackage) p).getVersion())) {
860                         // It's not already in the list of things to install, so add it now
861                         return insertArchive(a,
862                                 outArchives,
863                                 selectedArchives,
864                                 remotePkgs,
865                                 remoteSources,
866                                 localArchives,
867                                 true /*automated*/);
868                     }
869                 }
870             }
871         }
872 
873         // Finally nothing matched, so let's look at all available remote packages
874         fetchRemotePackages(remotePkgs, remoteSources);
875         for (Package p : remotePkgs) {
876             if (p instanceof PlatformPackage) {
877                 if (v.equals(((PlatformPackage) p).getVersion())) {
878                     // It's not already in the list of things to install, so add the
879                     // first compatible archive we can find.
880                     for (Archive a : p.getArchives()) {
881                         if (a.isCompatible()) {
882                             return insertArchive(a,
883                                     outArchives,
884                                     selectedArchives,
885                                     remotePkgs,
886                                     remoteSources,
887                                     localArchives,
888                                     true /*automated*/);
889                         }
890                     }
891                 }
892             }
893         }
894 
895         // We end up here if nothing matches. We don't have a good platform to match.
896         // We need to indicate this addon depends on a missing platform archive
897         // so that it can be impossible to install later on.
898         return new MissingPlatformArchiveInfo(pkg.getVersion());
899     }
900 
901     /**
902      * Resolves platform dependencies for extras.
903      * An extra depends on having a platform with a minimun API level.
904      *
905      * We try to return the highest API level available above the specified minimum.
906      * Note that installed packages have priority so if one installed platform satisfies
907      * the dependency, we'll use it even if there's a higher API platform available but
908      * not installed yet.
909      *
910      * Finds the platform dependency. If found, add it to the list of things to install.
911      * Returns the archive info dependency, if any.
912      */
findMinApiLevelDependency( IMinApiLevelDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)913     protected ArchiveInfo findMinApiLevelDependency(
914             IMinApiLevelDependency pkg,
915             Collection<ArchiveInfo> outArchives,
916             Collection<Archive> selectedArchives,
917             Collection<Package> remotePkgs,
918             SdkSource[] remoteSources,
919             ArchiveInfo[] localArchives) {
920 
921         int api = pkg.getMinApiLevel();
922 
923         if (api == IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED) {
924             return null;
925         }
926 
927         // Find a platform that would satisfy the requirement.
928 
929         // First look in locally installed packages.
930         for (ArchiveInfo ai : localArchives) {
931             Archive a = ai.getNewArchive();
932             if (a != null) {
933                 Package p = a.getParentPackage();
934                 if (p instanceof PlatformPackage) {
935                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
936                         // We found one already installed.
937                         return null;
938                     }
939                 }
940             }
941         }
942 
943         // Look in archives already scheduled for install
944         int foundApi = 0;
945         ArchiveInfo foundAi = null;
946 
947         for (ArchiveInfo ai : outArchives) {
948             Archive a = ai.getNewArchive();
949             if (a != null) {
950                 Package p = a.getParentPackage();
951                 if (p instanceof PlatformPackage) {
952                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
953                         if (api > foundApi) {
954                             foundApi = api;
955                             foundAi = ai;
956                         }
957                     }
958                 }
959             }
960         }
961 
962         if (foundAi != null) {
963             // The dependency is already scheduled for install, nothing else to do.
964             return foundAi;
965         }
966 
967         // Otherwise look in the selected archives *or* available remote packages
968         // and takes the best out of the two sets.
969         foundApi = 0;
970         Archive foundArchive = null;
971         if (selectedArchives != null) {
972             for (Archive a : selectedArchives) {
973                 Package p = a.getParentPackage();
974                 if (p instanceof PlatformPackage) {
975                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
976                         if (api > foundApi) {
977                             foundApi = api;
978                             foundArchive = a;
979                         }
980                     }
981                 }
982             }
983         }
984 
985         // Finally nothing matched, so let's look at all available remote packages
986         fetchRemotePackages(remotePkgs, remoteSources);
987         for (Package p : remotePkgs) {
988             if (p instanceof PlatformPackage) {
989                 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
990                     if (api > foundApi) {
991                         // It's not already in the list of things to install, so add the
992                         // first compatible archive we can find.
993                         for (Archive a : p.getArchives()) {
994                             if (a.isCompatible()) {
995                                 foundApi = api;
996                                 foundArchive = a;
997                             }
998                         }
999                     }
1000                 }
1001             }
1002         }
1003 
1004         if (foundArchive != null) {
1005             // It's not already in the list of things to install, so add it now
1006             return insertArchive(foundArchive,
1007                     outArchives,
1008                     selectedArchives,
1009                     remotePkgs,
1010                     remoteSources,
1011                     localArchives,
1012                     true /*automated*/);
1013         }
1014 
1015         // We end up here if nothing matches. We don't have a good platform to match.
1016         // We need to indicate this extra depends on a missing platform archive
1017         // so that it can be impossible to install later on.
1018         return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
1019     }
1020 
1021     /**
1022      * Resolves platform dependencies for add-ons.
1023      * An add-ons depends on having a platform with an exact specific API level.
1024      *
1025      * Finds the platform dependency. If found, add it to the list of things to install.
1026      * Returns the archive info dependency, if any.
1027      */
findExactApiLevelDependency( IExactApiLevelDependency pkg, Collection<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, Collection<Package> remotePkgs, SdkSource[] remoteSources, ArchiveInfo[] localArchives)1028     protected ArchiveInfo findExactApiLevelDependency(
1029             IExactApiLevelDependency pkg,
1030             Collection<ArchiveInfo> outArchives,
1031             Collection<Archive> selectedArchives,
1032             Collection<Package> remotePkgs,
1033             SdkSource[] remoteSources,
1034             ArchiveInfo[] localArchives) {
1035 
1036         int api = pkg.getExactApiLevel();
1037 
1038         if (api == IExactApiLevelDependency.API_LEVEL_INVALID) {
1039             return null;
1040         }
1041 
1042         // Find a platform that would satisfy the requirement.
1043 
1044         // First look in locally installed packages.
1045         for (ArchiveInfo ai : localArchives) {
1046             Archive a = ai.getNewArchive();
1047             if (a != null) {
1048                 Package p = a.getParentPackage();
1049                 if (p instanceof PlatformPackage) {
1050                     if (((PlatformPackage) p).getVersion().equals(api)) {
1051                         // We found one already installed.
1052                         return null;
1053                     }
1054                 }
1055             }
1056         }
1057 
1058         // Look in archives already scheduled for install
1059 
1060         for (ArchiveInfo ai : outArchives) {
1061             Archive a = ai.getNewArchive();
1062             if (a != null) {
1063                 Package p = a.getParentPackage();
1064                 if (p instanceof PlatformPackage) {
1065                     if (((PlatformPackage) p).getVersion().equals(api)) {
1066                         return ai;
1067                     }
1068                 }
1069             }
1070         }
1071 
1072         // Otherwise look in the selected archives.
1073         if (selectedArchives != null) {
1074             for (Archive a : selectedArchives) {
1075                 Package p = a.getParentPackage();
1076                 if (p instanceof PlatformPackage) {
1077                     if (((PlatformPackage) p).getVersion().equals(api)) {
1078                         // It's not already in the list of things to install, so add it now
1079                         return insertArchive(a,
1080                                 outArchives,
1081                                 selectedArchives,
1082                                 remotePkgs,
1083                                 remoteSources,
1084                                 localArchives,
1085                                 true /*automated*/);
1086                     }
1087                 }
1088             }
1089         }
1090 
1091         // Finally nothing matched, so let's look at all available remote packages
1092         fetchRemotePackages(remotePkgs, remoteSources);
1093         for (Package p : remotePkgs) {
1094             if (p instanceof PlatformPackage) {
1095                 if (((PlatformPackage) p).getVersion().equals(api)) {
1096                     // It's not already in the list of things to install, so add the
1097                     // first compatible archive we can find.
1098                     for (Archive a : p.getArchives()) {
1099                         if (a.isCompatible()) {
1100                             return insertArchive(a,
1101                                     outArchives,
1102                                     selectedArchives,
1103                                     remotePkgs,
1104                                     remoteSources,
1105                                     localArchives,
1106                                     true /*automated*/);
1107                         }
1108                     }
1109                 }
1110             }
1111         }
1112 
1113         // We end up here if nothing matches. We don't have a good platform to match.
1114         // We need to indicate this extra depends on a missing platform archive
1115         // so that it can be impossible to install later on.
1116         return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
1117     }
1118 
1119     /**
1120      * Fetch all remote packages only if really needed.
1121      * <p/>
1122      * This method takes a list of sources. Each source is only fetched once -- that is each
1123      * source keeps the list of packages that we fetched from the remote XML file. If the list
1124      * is null, it means this source has never been fetched so we'll do it once here. Otherwise
1125      * we rely on the cached list of packages from this source.
1126      * <p/>
1127      * This method also takes a remote package list as input, which it will fill out.
1128      * If a source has already been fetched, we'll add its packages to the remote package list
1129      * if they are not already present. Otherwise, the source will be fetched and the packages
1130      * added to the list.
1131      *
1132      * @param remotePkgs An in-out list of packages available from remote sources.
1133      *                   This list must not be null.
1134      *                   It can be empty or already contain some packages.
1135      * @param remoteSources A list of available remote sources to fetch from.
1136      */
fetchRemotePackages( final Collection<Package> remotePkgs, final SdkSource[] remoteSources)1137     protected void fetchRemotePackages(
1138             final Collection<Package> remotePkgs,
1139             final SdkSource[] remoteSources) {
1140         if (remotePkgs.size() > 0) {
1141             return;
1142         }
1143 
1144         // First check if there's any remote source we need to fetch.
1145         // This will bring the task window, so we rather not display it unless
1146         // necessary.
1147         boolean needsFetch = false;
1148         for (final SdkSource remoteSrc : remoteSources) {
1149             Package[] pkgs = remoteSrc.getPackages();
1150             if (pkgs == null) {
1151                 // This source has never been fetched. We'll do it below.
1152                 needsFetch = true;
1153             } else {
1154                 // This source has already been fetched and we know its package list.
1155                 // We still need to make sure all of its packages are present in the
1156                 // remotePkgs list.
1157 
1158                 nextPackage: for (Package pkg : pkgs) {
1159                     for (Archive a : pkg.getArchives()) {
1160                         // Only add a package if it contains at least one compatible archive
1161                         // and is not already in the remote package list.
1162                         if (a.isCompatible()) {
1163                             if (!remotePkgs.contains(pkg)) {
1164                                 remotePkgs.add(pkg);
1165                                 continue nextPackage;
1166                             }
1167                         }
1168                     }
1169                 }
1170             }
1171         }
1172 
1173         if (!needsFetch) {
1174             return;
1175         }
1176 
1177         final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
1178 
1179         mUpdaterData.getTaskFactory().start("Refresh Sources", new ITask() {
1180             public void run(ITaskMonitor monitor) {
1181                 for (SdkSource remoteSrc : remoteSources) {
1182                     Package[] pkgs = remoteSrc.getPackages();
1183 
1184                     if (pkgs == null) {
1185                         remoteSrc.load(monitor, forceHttp);
1186                         pkgs = remoteSrc.getPackages();
1187                     }
1188 
1189                     if (pkgs != null) {
1190                         nextPackage: for (Package pkg : pkgs) {
1191                             for (Archive a : pkg.getArchives()) {
1192                                 // Only add a package if it contains at least one compatible archive
1193                                 // and is not already in the remote package list.
1194                                 if (a.isCompatible()) {
1195                                     if (!remotePkgs.contains(pkg)) {
1196                                         remotePkgs.add(pkg);
1197                                         continue nextPackage;
1198                                     }
1199                                 }
1200                             }
1201                         }
1202                     }
1203                 }
1204             }
1205         });
1206     }
1207 
1208 
1209     /**
1210      * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed
1211      * "local" package/archive.
1212      * <p/>
1213      * In this case, the "new Archive" is still expected to be non null and the
1214      * "replaced Archive" is null. Installed archives are always accepted and never
1215      * rejected.
1216      * <p/>
1217      * Dependencies are not set.
1218      */
1219     private static class LocalArchiveInfo extends ArchiveInfo {
1220 
LocalArchiveInfo(Archive localArchive)1221         public LocalArchiveInfo(Archive localArchive) {
1222             super(localArchive, null /*replaced*/, null /*dependsOn*/);
1223         }
1224 
1225         /** Installed archives are always accepted. */
1226         @Override
isAccepted()1227         public boolean isAccepted() {
1228             return true;
1229         }
1230 
1231         /** Installed archives are never rejected. */
1232         @Override
isRejected()1233         public boolean isRejected() {
1234             return false;
1235         }
1236     }
1237 
1238     /**
1239      * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a
1240      * package/archive that we <em>really</em> need as a dependency but that we don't have.
1241      * <p/>
1242      * This is currently used for addons and extras in case we can't find a matching base platform.
1243      * <p/>
1244      * This kind of archive has specific properties: the new archive to install is null,
1245      * there are no dependencies and no archive is being replaced. The info can never be
1246      * accepted and is always rejected.
1247      */
1248     private static class MissingPlatformArchiveInfo extends ArchiveInfo {
1249 
1250         private final AndroidVersion mVersion;
1251 
1252         /**
1253          * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
1254          * given platform version is missing.
1255          */
MissingPlatformArchiveInfo(AndroidVersion version)1256         public MissingPlatformArchiveInfo(AndroidVersion version) {
1257             super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
1258             mVersion = version;
1259         }
1260 
1261         /** Missing archives are never accepted. */
1262         @Override
isAccepted()1263         public boolean isAccepted() {
1264             return false;
1265         }
1266 
1267         /** Missing archives are always rejected. */
1268         @Override
isRejected()1269         public boolean isRejected() {
1270             return true;
1271         }
1272 
1273         @Override
getShortDescription()1274         public String getShortDescription() {
1275             return String.format("Missing SDK Platform Android%1$s, API %2$d",
1276                     mVersion.isPreview() ? " Preview" : "",
1277                     mVersion.getApiLevel());
1278         }
1279     }
1280 
1281     /**
1282      * A {@link MissingArchiveInfo} is an {@link ArchiveInfo} that represents a
1283      * package/archive that we <em>really</em> need as a dependency but that we don't have.
1284      * <p/>
1285      * This is currently used for extras in case we can't find a matching tool revision
1286      * or when a platform-tool is missing.
1287      * <p/>
1288      * This kind of archive has specific properties: the new archive to install is null,
1289      * there are no dependencies and no archive is being replaced. The info can never be
1290      * accepted and is always rejected.
1291      */
1292     private static class MissingArchiveInfo extends ArchiveInfo {
1293 
1294         private final int mRevision;
1295         private final String mTitle;
1296 
1297         public static final String TITLE_TOOL = "Tools";
1298         public static final String TITLE_PLATFORM_TOOL = "Platform-tools";
1299 
1300         /**
1301          * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
1302          * given platform version is missing.
1303          *
1304          * @param title Typically "Tools" or "Platform-tools".
1305          * @param revision The required revision.
1306          */
MissingArchiveInfo(String title, int revision)1307         public MissingArchiveInfo(String title, int revision) {
1308             super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
1309             mTitle = title;
1310             mRevision = revision;
1311         }
1312 
1313         /** Missing archives are never accepted. */
1314         @Override
isAccepted()1315         public boolean isAccepted() {
1316             return false;
1317         }
1318 
1319         /** Missing archives are always rejected. */
1320         @Override
isRejected()1321         public boolean isRejected() {
1322             return true;
1323         }
1324 
1325         @Override
getShortDescription()1326         public String getShortDescription() {
1327             return String.format("Missing Android SDK %1$s, revision %2$d", mTitle, mRevision);
1328         }
1329     }
1330 }
1331