• 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.IMinApiLevelDependency;
25 import com.android.sdklib.internal.repository.IMinToolsDependency;
26 import com.android.sdklib.internal.repository.IPackageVersion;
27 import com.android.sdklib.internal.repository.IPlatformDependency;
28 import com.android.sdklib.internal.repository.MinToolsPackage;
29 import com.android.sdklib.internal.repository.Package;
30 import com.android.sdklib.internal.repository.PlatformPackage;
31 import com.android.sdklib.internal.repository.RepoSource;
32 import com.android.sdklib.internal.repository.RepoSources;
33 import com.android.sdklib.internal.repository.SamplePackage;
34 import com.android.sdklib.internal.repository.ToolPackage;
35 import com.android.sdklib.internal.repository.Package.UpdateInfo;
36 
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashMap;
40 
41 /**
42  * The logic to compute which packages to install, based on the choices
43  * made by the user. This adds dependent packages as needed.
44  * <p/>
45  * When the user doesn't provide a selection, looks at local package to find
46  * those that can be updated and compute dependencies too.
47  */
48 class UpdaterLogic {
49 
50     /**
51      * Compute which packages to install by taking the user selection
52      * and adding dependent packages as needed.
53      *
54      * When the user doesn't provide a selection, looks at local packages to find
55      * those that can be updated and compute dependencies too.
56      */
computeUpdates( Collection<Archive> selectedArchives, RepoSources sources, Package[] localPkgs)57     public ArrayList<ArchiveInfo> computeUpdates(
58             Collection<Archive> selectedArchives,
59             RepoSources sources,
60             Package[] localPkgs) {
61 
62         ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
63         ArrayList<Package> remotePkgs = new ArrayList<Package>();
64         RepoSource[] remoteSources = sources.getSources();
65 
66         // Create ArchiveInfos out of local (installed) packages.
67         ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
68 
69         if (selectedArchives == null) {
70             selectedArchives = findUpdates(localArchives, remotePkgs, remoteSources);
71         }
72 
73         for (Archive a : selectedArchives) {
74             insertArchive(a,
75                     archives,
76                     selectedArchives,
77                     remotePkgs,
78                     remoteSources,
79                     localArchives,
80                     false /*automated*/);
81         }
82 
83         return archives;
84     }
85 
86     /**
87      * Finds new packages that the user does not have in his/her local SDK
88      * and adds them to the list of archives to install.
89      */
addNewPlatforms(ArrayList<ArchiveInfo> archives, RepoSources sources, Package[] localPkgs)90     public void addNewPlatforms(ArrayList<ArchiveInfo> archives,
91             RepoSources sources,
92             Package[] localPkgs) {
93 
94         // Create ArchiveInfos out of local (installed) packages.
95         ArchiveInfo[] localArchives = createLocalArchives(localPkgs);
96 
97         // Find the highest platform installed
98         float currentPlatformScore = 0;
99         float currentSampleScore = 0;
100         float currentAddonScore = 0;
101         float currentDocScore = 0;
102         HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
103         for (Package p : localPkgs) {
104             int rev = p.getRevision();
105             int api = 0;
106             boolean isPreview = false;
107             if (p instanceof IPackageVersion) {
108                 AndroidVersion vers = ((IPackageVersion) p).getVersion();
109                 api = vers.getApiLevel();
110                 isPreview = vers.isPreview();
111             }
112 
113             // The score is 10*api + (1 if preview) + rev/100
114             // This allows previews to rank above a non-preview and
115             // allows revisions to rank appropriately.
116             float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
117 
118             if (p instanceof PlatformPackage) {
119                 currentPlatformScore = Math.max(currentPlatformScore, score);
120             } else if (p instanceof SamplePackage) {
121                 currentSampleScore = Math.max(currentSampleScore, score);
122             } else if (p instanceof AddonPackage) {
123                 currentAddonScore = Math.max(currentAddonScore, score);
124             } else if (p instanceof ExtraPackage) {
125                 currentExtraScore.put(((ExtraPackage) p).getPath(), score);
126             } else if (p instanceof DocPackage) {
127                 currentDocScore = Math.max(currentDocScore, score);
128             }
129         }
130 
131         RepoSource[] remoteSources = sources.getSources();
132         ArrayList<Package> remotePkgs = new ArrayList<Package>();
133         fetchRemotePackages(remotePkgs, remoteSources);
134 
135         Package suggestedDoc = null;
136 
137         for (Package p : remotePkgs) {
138             int rev = p.getRevision();
139             int api = 0;
140             boolean isPreview = false;
141             if (p instanceof  IPackageVersion) {
142                 AndroidVersion vers = ((IPackageVersion) p).getVersion();
143                 api = vers.getApiLevel();
144                 isPreview = vers.isPreview();
145             }
146 
147             float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
148 
149             boolean shouldAdd = false;
150             if (p instanceof PlatformPackage) {
151                 shouldAdd = score > currentPlatformScore;
152             } else if (p instanceof SamplePackage) {
153                 shouldAdd = score > currentSampleScore;
154             } else if (p instanceof AddonPackage) {
155                 shouldAdd = score > currentAddonScore;
156             } else if (p instanceof ExtraPackage) {
157                 String key = ((ExtraPackage) p).getPath();
158                 shouldAdd = !currentExtraScore.containsKey(key) ||
159                     score > currentExtraScore.get(key).floatValue();
160             } else if (p instanceof DocPackage) {
161                 // We don't want all the doc, only the most recent one
162                 if (score > currentDocScore) {
163                     suggestedDoc = p;
164                     currentDocScore = score;
165                 }
166             }
167 
168             if (shouldAdd) {
169                 // We should suggest this package for installation.
170                 for (Archive a : p.getArchives()) {
171                     if (a.isCompatible()) {
172                         insertArchive(a,
173                                 archives,
174                                 null /*selectedArchives*/,
175                                 remotePkgs,
176                                 remoteSources,
177                                 localArchives,
178                                 true /*automated*/);
179                     }
180                 }
181             }
182         }
183 
184         if (suggestedDoc != null) {
185             // We should suggest this package for installation.
186             for (Archive a : suggestedDoc.getArchives()) {
187                 if (a.isCompatible()) {
188                     insertArchive(a,
189                             archives,
190                             null /*selectedArchives*/,
191                             remotePkgs,
192                             remoteSources,
193                             localArchives,
194                             true /*automated*/);
195                 }
196             }
197         }
198     }
199 
200     /**
201      * Create a array of {@link ArchiveInfo} based on all local (already installed)
202      * packages. The array is always non-null but may be empty.
203      * <p/>
204      * The local {@link ArchiveInfo} are guaranteed to have one non-null archive
205      * that you can retrieve using {@link ArchiveInfo#getNewArchive()}.
206      */
createLocalArchives(Package[] localPkgs)207     protected ArchiveInfo[] createLocalArchives(Package[] localPkgs) {
208 
209         if (localPkgs != null) {
210             ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
211             for (Package p : localPkgs) {
212                 // Only accept packages that have one compatible archive.
213                 // Local package should have 1 and only 1 compatible archive anyway.
214                 for (Archive a : p.getArchives()) {
215                     if (a != null && a.isCompatible()) {
216                         // We create an "installed" archive info to wrap the local package.
217                         // Note that dependencies are not computed since right now we don't
218                         // deal with more than one level of dependencies and installed archives
219                         // are deemed implicitly accepted anyway.
220                         list.add(new LocalArchiveInfo(a));
221                     }
222                 }
223             }
224 
225             return list.toArray(new ArchiveInfo[list.size()]);
226         }
227 
228         return new ArchiveInfo[0];
229     }
230 
231     /**
232      * Find suitable updates to all current local packages.
233      */
findUpdates(ArchiveInfo[] localArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources)234     private Collection<Archive> findUpdates(ArchiveInfo[] localArchives,
235             ArrayList<Package> remotePkgs,
236             RepoSource[] remoteSources) {
237         ArrayList<Archive> updates = new ArrayList<Archive>();
238 
239         fetchRemotePackages(remotePkgs, remoteSources);
240 
241         for (ArchiveInfo ai : localArchives) {
242             Archive na = ai.getNewArchive();
243             if (na == null) {
244                 continue;
245             }
246             Package localPkg = na.getParentPackage();
247 
248             for (Package remotePkg : remotePkgs) {
249                 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
250                     // Found a suitable update. Only accept the remote package
251                     // if it provides at least one compatible archive.
252 
253                     for (Archive a : remotePkg.getArchives()) {
254                         if (a.isCompatible()) {
255                             updates.add(a);
256                             break;
257                         }
258                     }
259                 }
260             }
261         }
262 
263         return updates;
264     }
265 
insertArchive(Archive archive, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, ArchiveInfo[] localArchives, boolean automated)266     private ArchiveInfo insertArchive(Archive archive,
267             ArrayList<ArchiveInfo> outArchives,
268             Collection<Archive> selectedArchives,
269             ArrayList<Package> remotePkgs,
270             RepoSource[] remoteSources,
271             ArchiveInfo[] localArchives,
272             boolean automated) {
273         Package p = archive.getParentPackage();
274 
275         // Is this an update?
276         Archive updatedArchive = null;
277         for (ArchiveInfo ai : localArchives) {
278             Archive a = ai.getNewArchive();
279             if (a != null) {
280                 Package lp = a.getParentPackage();
281 
282                 if (lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
283                     updatedArchive = a;
284                 }
285             }
286         }
287 
288         // Find dependencies
289         ArchiveInfo[] deps = findDependency(p,
290                 outArchives,
291                 selectedArchives,
292                 remotePkgs,
293                 remoteSources,
294                 localArchives);
295 
296         // Make sure it's not a dup
297         ArchiveInfo ai = null;
298 
299         for (ArchiveInfo ai2 : outArchives) {
300             Archive a2 = ai2.getNewArchive();
301             if (a2 != null && a2.getParentPackage().sameItemAs(archive.getParentPackage())) {
302                 ai = ai2;
303                 break;
304             }
305         }
306 
307         if (ai == null) {
308             ai = new ArchiveInfo(
309                 archive,        //newArchive
310                 updatedArchive, //replaced
311                 deps            //dependsOn
312                 );
313             outArchives.add(ai);
314         }
315 
316         if (deps != null) {
317             for (ArchiveInfo d : deps) {
318                 d.addDependencyFor(ai);
319             }
320         }
321 
322         return ai;
323     }
324 
325     /**
326      * Resolves dependencies for a given package.
327      *
328      * Returns null if no dependencies were found.
329      * Otherwise return an array of {@link ArchiveInfo}, which is guaranteed to have
330      * at least size 1 and contain no null elements.
331      */
findDependency(Package pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, ArchiveInfo[] localArchives)332     private ArchiveInfo[] findDependency(Package pkg,
333             ArrayList<ArchiveInfo> outArchives,
334             Collection<Archive> selectedArchives,
335             ArrayList<Package> remotePkgs,
336             RepoSource[] remoteSources,
337             ArchiveInfo[] localArchives) {
338 
339         // Current dependencies can be:
340         // - addon: *always* depends on platform of same API level
341         // - platform: *might* depends on tools of rev >= min-tools-rev
342         // - extra: *might* depends on platform with api >= min-api-level
343 
344         ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
345 
346         if (pkg instanceof IPlatformDependency) {
347             ArchiveInfo ai = findPlatformDependency(
348                     (IPlatformDependency) pkg,
349                     outArchives,
350                     selectedArchives,
351                     remotePkgs,
352                     remoteSources,
353                     localArchives);
354 
355             if (ai != null) {
356                 list.add(ai);
357             }
358         }
359 
360         if (pkg instanceof IMinToolsDependency) {
361 
362             ArchiveInfo ai = findToolsDependency(
363                     (IMinToolsDependency) pkg,
364                     outArchives,
365                     selectedArchives,
366                     remotePkgs,
367                     remoteSources,
368                     localArchives);
369 
370             if (ai != null) {
371                 list.add(ai);
372             }
373         }
374 
375         if (pkg instanceof IMinApiLevelDependency) {
376 
377             ArchiveInfo ai = findMinApiLevelDependency(
378                     (IMinApiLevelDependency) pkg,
379                     outArchives,
380                     selectedArchives,
381                     remotePkgs,
382                     remoteSources,
383                     localArchives);
384 
385             if (ai != null) {
386                 list.add(ai);
387             }
388         }
389 
390         if (list.size() > 0) {
391             return list.toArray(new ArchiveInfo[list.size()]);
392         }
393 
394         return null;
395     }
396 
397     /**
398      * Resolves dependencies on tools.
399      *
400      * A platform or an extra package can both have a min-tools-rev, in which case it
401      * depends on having a tools package of the requested revision.
402      * Finds the tools dependency. If found, add it to the list of things to install.
403      * Returns the archive info dependency, if any.
404      */
findToolsDependency( IMinToolsDependency pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, ArchiveInfo[] localArchives)405     protected ArchiveInfo findToolsDependency(
406             IMinToolsDependency pkg,
407             ArrayList<ArchiveInfo> outArchives,
408             Collection<Archive> selectedArchives,
409             ArrayList<Package> remotePkgs,
410             RepoSource[] remoteSources,
411             ArchiveInfo[] localArchives) {
412         // This is the requirement to match.
413         int rev = pkg.getMinToolsRevision();
414 
415         if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
416             // Well actually there's no requirement.
417             return null;
418         }
419 
420         // First look in locally installed packages.
421         for (ArchiveInfo ai : localArchives) {
422             Archive a = ai.getNewArchive();
423             if (a != null) {
424                 Package p = a.getParentPackage();
425                 if (p instanceof ToolPackage) {
426                     if (((ToolPackage) p).getRevision() >= rev) {
427                         // We found one already installed.
428                         return null;
429                     }
430                 }
431             }
432         }
433 
434         // Look in archives already scheduled for install
435         for (ArchiveInfo ai : outArchives) {
436             Archive a = ai.getNewArchive();
437             if (a != null) {
438                 Package p = a.getParentPackage();
439                 if (p instanceof ToolPackage) {
440                     if (((ToolPackage) p).getRevision() >= rev) {
441                         // The dependency is already scheduled for install, nothing else to do.
442                         return ai;
443                     }
444                 }
445             }
446         }
447 
448         // Otherwise look in the selected archives.
449         if (selectedArchives != null) {
450             for (Archive a : selectedArchives) {
451                 Package p = a.getParentPackage();
452                 if (p instanceof ToolPackage) {
453                     if (((ToolPackage) p).getRevision() >= rev) {
454                         // It's not already in the list of things to install, so add it now
455                         return insertArchive(a,
456                                 outArchives,
457                                 selectedArchives,
458                                 remotePkgs,
459                                 remoteSources,
460                                 localArchives,
461                                 true /*automated*/);
462                     }
463                 }
464             }
465         }
466 
467         // Finally nothing matched, so let's look at all available remote packages
468         fetchRemotePackages(remotePkgs, remoteSources);
469         for (Package p : remotePkgs) {
470             if (p instanceof ToolPackage) {
471                 if (((ToolPackage) p).getRevision() >= rev) {
472                     // It's not already in the list of things to install, so add the
473                     // first compatible archive we can find.
474                     for (Archive a : p.getArchives()) {
475                         if (a.isCompatible()) {
476                             return insertArchive(a,
477                                     outArchives,
478                                     selectedArchives,
479                                     remotePkgs,
480                                     remoteSources,
481                                     localArchives,
482                                     true /*automated*/);
483                         }
484                     }
485                 }
486             }
487         }
488 
489         // We end up here if nothing matches. We don't have a good platform to match.
490         // We need to indicate this extra depends on a missing platform archive
491         // so that it can be impossible to install later on.
492         return new MissingToolArchiveInfo(rev);
493     }
494 
495     /**
496      * Resolves dependencies on platform for an addon.
497      *
498      * An addon depends on having a platform with the same API level.
499      *
500      * Finds the platform dependency. If found, add it to the list of things to install.
501      * Returns the archive info dependency, if any.
502      */
findPlatformDependency( IPlatformDependency pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, ArchiveInfo[] localArchives)503     protected ArchiveInfo findPlatformDependency(
504             IPlatformDependency pkg,
505             ArrayList<ArchiveInfo> outArchives,
506             Collection<Archive> selectedArchives,
507             ArrayList<Package> remotePkgs,
508             RepoSource[] remoteSources,
509             ArchiveInfo[] localArchives) {
510         // This is the requirement to match.
511         AndroidVersion v = pkg.getVersion();
512 
513         // Find a platform that would satisfy the requirement.
514 
515         // First look in locally installed packages.
516         for (ArchiveInfo ai : localArchives) {
517             Archive a = ai.getNewArchive();
518             if (a != null) {
519                 Package p = a.getParentPackage();
520                 if (p instanceof PlatformPackage) {
521                     if (v.equals(((PlatformPackage) p).getVersion())) {
522                         // We found one already installed.
523                         return null;
524                     }
525                 }
526             }
527         }
528 
529         // Look in archives already scheduled for install
530         for (ArchiveInfo ai : outArchives) {
531             Archive a = ai.getNewArchive();
532             if (a != null) {
533                 Package p = a.getParentPackage();
534                 if (p instanceof PlatformPackage) {
535                     if (v.equals(((PlatformPackage) p).getVersion())) {
536                         // The dependency is already scheduled for install, nothing else to do.
537                         return ai;
538                     }
539                 }
540             }
541         }
542 
543         // Otherwise look in the selected archives.
544         if (selectedArchives != null) {
545             for (Archive a : selectedArchives) {
546                 Package p = a.getParentPackage();
547                 if (p instanceof PlatformPackage) {
548                     if (v.equals(((PlatformPackage) p).getVersion())) {
549                         // It's not already in the list of things to install, so add it now
550                         return insertArchive(a,
551                                 outArchives,
552                                 selectedArchives,
553                                 remotePkgs,
554                                 remoteSources,
555                                 localArchives,
556                                 true /*automated*/);
557                     }
558                 }
559             }
560         }
561 
562         // Finally nothing matched, so let's look at all available remote packages
563         fetchRemotePackages(remotePkgs, remoteSources);
564         for (Package p : remotePkgs) {
565             if (p instanceof PlatformPackage) {
566                 if (v.equals(((PlatformPackage) p).getVersion())) {
567                     // It's not already in the list of things to install, so add the
568                     // first compatible archive we can find.
569                     for (Archive a : p.getArchives()) {
570                         if (a.isCompatible()) {
571                             return insertArchive(a,
572                                     outArchives,
573                                     selectedArchives,
574                                     remotePkgs,
575                                     remoteSources,
576                                     localArchives,
577                                     true /*automated*/);
578                         }
579                     }
580                 }
581             }
582         }
583 
584         // We end up here if nothing matches. We don't have a good platform to match.
585         // We need to indicate this addon depends on a missing platform archive
586         // so that it can be impossible to install later on.
587         return new MissingPlatformArchiveInfo(pkg.getVersion());
588     }
589 
590     /**
591      * Resolves platform dependencies for extras.
592      * An extra depends on having a platform with a minimun API level.
593      *
594      * We try to return the highest API level available above the specified minimum.
595      * Note that installed packages have priority so if one installed platform satisfies
596      * the dependency, we'll use it even if there's a higher API platform available but
597      * not installed yet.
598      *
599      * Finds the platform dependency. If found, add it to the list of things to install.
600      * Returns the archive info dependency, if any.
601      */
findMinApiLevelDependency( IMinApiLevelDependency pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, ArchiveInfo[] localArchives)602     protected ArchiveInfo findMinApiLevelDependency(
603             IMinApiLevelDependency pkg,
604             ArrayList<ArchiveInfo> outArchives,
605             Collection<Archive> selectedArchives,
606             ArrayList<Package> remotePkgs,
607             RepoSource[] remoteSources,
608             ArchiveInfo[] localArchives) {
609 
610         int api = pkg.getMinApiLevel();
611 
612         if (api == ExtraPackage.MIN_API_LEVEL_NOT_SPECIFIED) {
613             return null;
614         }
615 
616         // Find a platform that would satisfy the requirement.
617 
618         // First look in locally installed packages.
619         for (ArchiveInfo ai : localArchives) {
620             Archive a = ai.getNewArchive();
621             if (a != null) {
622                 Package p = a.getParentPackage();
623                 if (p instanceof PlatformPackage) {
624                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
625                         // We found one already installed.
626                         return null;
627                     }
628                 }
629             }
630         }
631 
632         // Look in archives already scheduled for install
633         int foundApi = 0;
634         ArchiveInfo foundAi = null;
635 
636         for (ArchiveInfo ai : outArchives) {
637             Archive a = ai.getNewArchive();
638             if (a != null) {
639                 Package p = a.getParentPackage();
640                 if (p instanceof PlatformPackage) {
641                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
642                         if (api > foundApi) {
643                             foundApi = api;
644                             foundAi = ai;
645                         }
646                     }
647                 }
648             }
649         }
650 
651         if (foundAi != null) {
652             // The dependency is already scheduled for install, nothing else to do.
653             return foundAi;
654         }
655 
656         // Otherwise look in the selected archives *or* available remote packages
657         // and takes the best out of the two sets.
658         foundApi = 0;
659         Archive foundArchive = null;
660         if (selectedArchives != null) {
661             for (Archive a : selectedArchives) {
662                 Package p = a.getParentPackage();
663                 if (p instanceof PlatformPackage) {
664                     if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
665                         if (api > foundApi) {
666                             foundApi = api;
667                             foundArchive = a;
668                         }
669                     }
670                 }
671             }
672         }
673 
674         // Finally nothing matched, so let's look at all available remote packages
675         fetchRemotePackages(remotePkgs, remoteSources);
676         for (Package p : remotePkgs) {
677             if (p instanceof PlatformPackage) {
678                 if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {
679                     if (api > foundApi) {
680                         // It's not already in the list of things to install, so add the
681                         // first compatible archive we can find.
682                         for (Archive a : p.getArchives()) {
683                             if (a.isCompatible()) {
684                                 foundApi = api;
685                                 foundArchive = a;
686                             }
687                         }
688                     }
689                 }
690             }
691         }
692 
693         if (foundArchive != null) {
694             // It's not already in the list of things to install, so add it now
695             return insertArchive(foundArchive,
696                     outArchives,
697                     selectedArchives,
698                     remotePkgs,
699                     remoteSources,
700                     localArchives,
701                     true /*automated*/);
702         }
703 
704         // We end up here if nothing matches. We don't have a good platform to match.
705         // We need to indicate this extra depends on a missing platform archive
706         // so that it can be impossible to install later on.
707         return new MissingPlatformArchiveInfo(new AndroidVersion(api, null /*codename*/));
708     }
709 
710     /** Fetch all remote packages only if really needed. */
fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources)711     protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) {
712         if (remotePkgs.size() > 0) {
713             return;
714         }
715 
716         for (RepoSource remoteSrc : remoteSources) {
717             Package[] pkgs = remoteSrc.getPackages();
718             if (pkgs != null) {
719                 nextPackage: for (Package pkg : pkgs) {
720                     for (Archive a : pkg.getArchives()) {
721                         // Only add a package if it contains at least one compatible archive
722                         if (a.isCompatible()) {
723                             remotePkgs.add(pkg);
724                             continue nextPackage;
725                         }
726                     }
727                 }
728             }
729         }
730     }
731 
732 
733     /**
734      * A {@link LocalArchiveInfo} is an {@link ArchiveInfo} that wraps an already installed
735      * "local" package/archive.
736      * <p/>
737      * In this case, the "new Archive" is still expected to be non null and the
738      * "replaced Archive" isnull. Installed archives are always accepted and never
739      * rejected.
740      * <p/>
741      * Dependencies are not set.
742      */
743     private static class LocalArchiveInfo extends ArchiveInfo {
744 
LocalArchiveInfo(Archive localArchive)745         public LocalArchiveInfo(Archive localArchive) {
746             super(localArchive, null /*replaced*/, null /*dependsOn*/);
747         }
748 
749         /** Installed archives are always accepted. */
750         @Override
isAccepted()751         public boolean isAccepted() {
752             return true;
753         }
754 
755         /** Installed archives are never rejected. */
756         @Override
isRejected()757         public boolean isRejected() {
758             return false;
759         }
760     }
761 
762     /**
763      * A {@link MissingPlatformArchiveInfo} is an {@link ArchiveInfo} that represents a
764      * package/archive that we <em>really</em> need as a dependency but that we don't have.
765      * <p/>
766      * This is currently used for addons and extras in case we can't find a matching base platform.
767      * <p/>
768      * This kind of archive has specific properties: the new archive to install is null,
769      * there are no dependencies and no archive is being replaced. The info can never be
770      * accepted and is always rejected.
771      */
772     private static class MissingPlatformArchiveInfo extends ArchiveInfo {
773 
774         private final AndroidVersion mVersion;
775 
776         /**
777          * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
778          * given platform version is missing.
779          */
MissingPlatformArchiveInfo(AndroidVersion version)780         public MissingPlatformArchiveInfo(AndroidVersion version) {
781             super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
782             mVersion = version;
783         }
784 
785         /** Missing archives are never accepted. */
786         @Override
isAccepted()787         public boolean isAccepted() {
788             return false;
789         }
790 
791         /** Missing archives are always rejected. */
792         @Override
isRejected()793         public boolean isRejected() {
794             return true;
795         }
796 
797         @Override
getShortDescription()798         public String getShortDescription() {
799             return String.format("Missing SDK Platform Android%1$s, API %2$d",
800                     mVersion.isPreview() ? " Preview" : "",
801                     mVersion.getApiLevel());
802         }
803     }
804 
805     /**
806      * A {@link MissingToolArchiveInfo} is an {@link ArchiveInfo} that represents a
807      * package/archive that we <em>really</em> need as a dependency but that we don't have.
808      * <p/>
809      * This is currently used for extras in case we can't find a matching tool revision.
810      * <p/>
811      * This kind of archive has specific properties: the new archive to install is null,
812      * there are no dependencies and no archive is being replaced. The info can never be
813      * accepted and is always rejected.
814      */
815     private static class MissingToolArchiveInfo extends ArchiveInfo {
816 
817         private final int mRevision;
818 
819         /**
820          * Constructs a {@link MissingPlatformArchiveInfo} that will indicate the
821          * given platform version is missing.
822          */
MissingToolArchiveInfo(int revision)823         public MissingToolArchiveInfo(int revision) {
824             super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);
825             mRevision = revision;
826         }
827 
828         /** Missing archives are never accepted. */
829         @Override
isAccepted()830         public boolean isAccepted() {
831             return false;
832         }
833 
834         /** Missing archives are always rejected. */
835         @Override
isRejected()836         public boolean isRejected() {
837             return true;
838         }
839 
840         @Override
getShortDescription()841         public String getShortDescription() {
842             return String.format("Missing Android SDK Tools, revision %1$d", mRevision);
843         }
844     }
845 }
846