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