OSDN Git Service

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