OSDN Git Service

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