• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdkuilib.internal.repository;
18 
19 import com.android.sdklib.AndroidVersion;
20 import com.android.sdklib.internal.repository.AddonPackage;
21 import com.android.sdklib.internal.repository.Archive;
22 import com.android.sdklib.internal.repository.DocPackage;
23 import com.android.sdklib.internal.repository.ExtraPackage;
24 import com.android.sdklib.internal.repository.IPackageVersion;
25 import com.android.sdklib.internal.repository.MinToolsPackage;
26 import com.android.sdklib.internal.repository.Package;
27 import com.android.sdklib.internal.repository.PlatformPackage;
28 import com.android.sdklib.internal.repository.RepoSource;
29 import com.android.sdklib.internal.repository.RepoSources;
30 import com.android.sdklib.internal.repository.ToolPackage;
31 import com.android.sdklib.internal.repository.Package.UpdateInfo;
32 
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.HashMap;
36 
37 /**
38  * The logic to compute which packages to install, based on the choices
39  * made by the user. This adds dependent packages as needed.
40  * <p/>
41  * When the user doesn't provide a selection, looks at local package to find
42  * those that can be updated and compute dependencies too.
43  */
44 class UpdaterLogic {
45 
46     /**
47      * Compute which packages to install by taking the user selection
48      * and adding dependent packages as needed.
49      *
50      * When the user doesn't provide a selection, looks at local package to find
51      * those that can be updated and compute dependencies too.
52      */
computeUpdates( Collection<Archive> selectedArchives, RepoSources sources, Package[] localPkgs)53     public ArrayList<ArchiveInfo> computeUpdates(
54             Collection<Archive> selectedArchives,
55             RepoSources sources,
56             Package[] localPkgs) {
57 
58         ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
59         ArrayList<Package> remotePkgs = new ArrayList<Package>();
60         RepoSource[] remoteSources = sources.getSources();
61 
62         if (selectedArchives == null) {
63             selectedArchives = findUpdates(localPkgs, remotePkgs, remoteSources);
64         }
65 
66         for (Archive a : selectedArchives) {
67             insertArchive(a,
68                     archives,
69                     selectedArchives,
70                     remotePkgs,
71                     remoteSources,
72                     localPkgs,
73                     false /*automated*/);
74         }
75 
76         return archives;
77     }
78 
79     /**
80      * Finds new platforms that the user does not have in his/her local SDK
81      * and adds them to the list of archives to install.
82      */
addNewPlatforms(ArrayList<ArchiveInfo> archives, RepoSources sources, Package[] localPkgs)83     public void addNewPlatforms(ArrayList<ArchiveInfo> archives,
84             RepoSources sources,
85             Package[] localPkgs) {
86 
87         // Find the highest platform installed
88         float currentPlatformScore = 0;
89         float currentAddonScore = 0;
90         float currentDocScore = 0;
91         HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
92         for (Package p : localPkgs) {
93             int rev = p.getRevision();
94             int api = 0;
95             boolean isPreview = false;
96             if (p instanceof  IPackageVersion) {
97                 AndroidVersion vers = ((IPackageVersion) p).getVersion();
98                 api = vers.getApiLevel();
99                 isPreview = vers.isPreview();
100             }
101 
102             // The score is 10*api + (1 if preview) + rev/100
103             // This allows previews to rank above a non-preview and
104             // allows revisions to rank appropriately.
105             float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
106 
107             if (p instanceof PlatformPackage) {
108                 currentPlatformScore = Math.max(currentPlatformScore, score);
109             } else if (p instanceof AddonPackage) {
110                 currentAddonScore = Math.max(currentAddonScore, score);
111             } else if (p instanceof ExtraPackage) {
112                 currentExtraScore.put(((ExtraPackage) p).getPath(), score);
113             } else if (p instanceof DocPackage) {
114                 currentDocScore = Math.max(currentDocScore, score);
115             }
116         }
117 
118         RepoSource[] remoteSources = sources.getSources();
119         ArrayList<Package> remotePkgs = new ArrayList<Package>();
120         fetchRemotePackages(remotePkgs, remoteSources);
121 
122         Package suggestedDoc = null;
123 
124         for (Package p : remotePkgs) {
125             int rev = p.getRevision();
126             int api = 0;
127             boolean isPreview = false;
128             if (p instanceof  IPackageVersion) {
129                 AndroidVersion vers = ((IPackageVersion) p).getVersion();
130                 api = vers.getApiLevel();
131                 isPreview = vers.isPreview();
132             }
133 
134             float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
135 
136             boolean shouldAdd = false;
137             if (p instanceof PlatformPackage) {
138                 shouldAdd = score > currentPlatformScore;
139             } else if (p instanceof AddonPackage) {
140                 shouldAdd = score > currentAddonScore;
141             } else if (p instanceof ExtraPackage) {
142                 String key = ((ExtraPackage) p).getPath();
143                 shouldAdd = !currentExtraScore.containsKey(key) ||
144                     score > currentExtraScore.get(key).floatValue();
145             } else if (p instanceof DocPackage) {
146                 // We don't want all the doc, only the most recent one
147                 if (score > currentDocScore) {
148                     suggestedDoc = p;
149                     currentDocScore = score;
150                 }
151             }
152 
153             if (shouldAdd) {
154                 // We should suggest this package for installation.
155                 for (Archive a : p.getArchives()) {
156                     if (a.isCompatible()) {
157                         insertArchive(a,
158                                 archives,
159                                 null /*selectedArchives*/,
160                                 remotePkgs,
161                                 remoteSources,
162                                 localPkgs,
163                                 true /*automated*/);
164                     }
165                 }
166             }
167         }
168 
169         if (suggestedDoc != null) {
170             // We should suggest this package for installation.
171             for (Archive a : suggestedDoc.getArchives()) {
172                 if (a.isCompatible()) {
173                     insertArchive(a,
174                             archives,
175                             null /*selectedArchives*/,
176                             remotePkgs,
177                             remoteSources,
178                             localPkgs,
179                             true /*automated*/);
180                 }
181             }
182         }
183 
184     }
185 
186     /**
187      * Find suitable updates to all current local packages.
188      */
findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs, RepoSource[] remoteSources)189     private Collection<Archive> findUpdates(Package[] localPkgs,
190             ArrayList<Package> remotePkgs,
191             RepoSource[] remoteSources) {
192         ArrayList<Archive> updates = new ArrayList<Archive>();
193 
194         fetchRemotePackages(remotePkgs, remoteSources);
195 
196         for (Package localPkg : localPkgs) {
197             for (Package remotePkg : remotePkgs) {
198                 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
199                     // Found a suitable update. Only accept the remote package
200                     // if it provides at least one compatible archive.
201 
202                     for (Archive a : remotePkg.getArchives()) {
203                         if (a.isCompatible()) {
204                             updates.add(a);
205                             break;
206                         }
207                     }
208                 }
209             }
210         }
211 
212         return updates;
213     }
214 
insertArchive(Archive archive, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs, boolean automated)215     private ArchiveInfo insertArchive(Archive archive,
216             ArrayList<ArchiveInfo> outArchives,
217             Collection<Archive> selectedArchives,
218             ArrayList<Package> remotePkgs,
219             RepoSource[] remoteSources,
220             Package[] localPkgs,
221             boolean automated) {
222         Package p = archive.getParentPackage();
223 
224         // Is this an update?
225         Archive updatedArchive = null;
226         for (Package lp : localPkgs) {
227             assert lp.getArchives().length == 1;
228             if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
229                 updatedArchive = lp.getArchives()[0];
230             }
231         }
232 
233         // find dependencies
234         ArchiveInfo dep = findDependency(p,
235                 outArchives,
236                 selectedArchives,
237                 remotePkgs,
238                 remoteSources,
239                 localPkgs);
240 
241         // Make sure it's not a dup
242         ArchiveInfo ai = null;
243 
244         for (ArchiveInfo ai2 : outArchives) {
245             if (ai2.getNewArchive().getParentPackage().sameItemAs(archive.getParentPackage())) {
246                 ai = ai2;
247                 break;
248             }
249         }
250 
251         if (ai == null) {
252             ai = new ArchiveInfo(
253                 archive, //newArchive
254                 updatedArchive, //replaced
255                 dep //dependsOn
256                 );
257             outArchives.add(ai);
258         }
259 
260         if (dep != null) {
261             dep.addDependencyFor(ai);
262         }
263 
264         return ai;
265     }
266 
findDependency(Package pkg, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)267     private ArchiveInfo findDependency(Package pkg,
268             ArrayList<ArchiveInfo> outArchives,
269             Collection<Archive> selectedArchives,
270             ArrayList<Package> remotePkgs,
271             RepoSource[] remoteSources,
272             Package[] localPkgs) {
273 
274         // Current dependencies can be:
275         // - addon: *always* depends on platform of same API level
276         // - platform: *might* depends on tools of rev >= min-tools-rev
277 
278         if (pkg instanceof AddonPackage) {
279             AddonPackage addon = (AddonPackage) pkg;
280 
281             return findPlatformDependency(
282                     addon,
283                     outArchives,
284                     selectedArchives,
285                     remotePkgs,
286                     remoteSources,
287                     localPkgs);
288 
289         } else if (pkg instanceof MinToolsPackage) {
290             MinToolsPackage platformOrExtra = (MinToolsPackage) pkg;
291 
292             return findToolsDependency(
293                     platformOrExtra,
294                     outArchives,
295                     selectedArchives,
296                     remotePkgs,
297                     remoteSources,
298                     localPkgs);
299         }
300 
301         return null;
302     }
303 
304     /**
305      * Resolves dependencies on tools.
306      *
307      * A platform or an extra package can both have a min-tools-rev, in which case it
308      * depends on having a tools package of the requested revision.
309      * Finds the tools dependency. If found, add it to the list of things to install.
310      * Returns the archive info dependency, if any.
311      */
findToolsDependency(MinToolsPackage platformOrExtra, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)312     protected ArchiveInfo findToolsDependency(MinToolsPackage platformOrExtra,
313             ArrayList<ArchiveInfo> outArchives,
314             Collection<Archive> selectedArchives,
315             ArrayList<Package> remotePkgs,
316             RepoSource[] remoteSources,
317             Package[] localPkgs) {
318         // This is the requirement to match.
319         int rev = platformOrExtra.getMinToolsRevision();
320 
321         if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
322             // Well actually there's no requirement.
323             return null;
324         }
325 
326         // First look in local packages.
327         for (Package p : localPkgs) {
328             if (p instanceof ToolPackage) {
329                 if (((ToolPackage) p).getRevision() >= rev) {
330                     // We found one already installed. We don't report this dependency
331                     // as the UI only cares about resolving "newly added dependencies".
332                     return null;
333                 }
334             }
335         }
336 
337         // Look in archives already scheduled for install
338         for (ArchiveInfo ai : outArchives) {
339             Package p = ai.getNewArchive().getParentPackage();
340             if (p instanceof ToolPackage) {
341                 if (((ToolPackage) p).getRevision() >= rev) {
342                     // The dependency is already scheduled for install, nothing else to do.
343                     return ai;
344                 }
345             }
346         }
347 
348         // Otherwise look in the selected archives.
349         if (selectedArchives != null) {
350             for (Archive a : selectedArchives) {
351                 Package p = a.getParentPackage();
352                 if (p instanceof ToolPackage) {
353                     if (((ToolPackage) p).getRevision() >= rev) {
354                         // It's not already in the list of things to install, so add it now
355                         return insertArchive(a, outArchives,
356                                 selectedArchives, remotePkgs, remoteSources, localPkgs,
357                                 true /*automated*/);
358                     }
359                 }
360             }
361         }
362 
363         // Finally nothing matched, so let's look at all available remote packages
364         fetchRemotePackages(remotePkgs, remoteSources);
365         for (Package p : remotePkgs) {
366             if (p instanceof ToolPackage) {
367                 if (((ToolPackage) p).getRevision() >= rev) {
368                     // It's not already in the list of things to install, so add the
369                     // first compatible archive we can find.
370                     for (Archive a : p.getArchives()) {
371                         if (a.isCompatible()) {
372                             return insertArchive(a, outArchives,
373                                     selectedArchives, remotePkgs, remoteSources, localPkgs,
374                                     true /*automated*/);
375                         }
376                     }
377                 }
378             }
379         }
380 
381         // We end up here if nothing matches. We don't have a good tools to match.
382         // Seriously, that can't happens unless we totally screwed our repo manifest.
383         // We'll let this one go through anyway.
384         return null;
385     }
386 
387     /**
388      * Resolves dependencies on platform.
389      *
390      * An addon depends on having a platform with the same API version.
391      * Finds the platform dependency. If found, add it to the list of things to install.
392      * Returns the archive info dependency, if any.
393      */
findPlatformDependency(AddonPackage addon, ArrayList<ArchiveInfo> outArchives, Collection<Archive> selectedArchives, ArrayList<Package> remotePkgs, RepoSource[] remoteSources, Package[] localPkgs)394     protected ArchiveInfo findPlatformDependency(AddonPackage addon,
395             ArrayList<ArchiveInfo> outArchives,
396             Collection<Archive> selectedArchives,
397             ArrayList<Package> remotePkgs,
398             RepoSource[] remoteSources,
399             Package[] localPkgs) {
400         // This is the requirement to match.
401         AndroidVersion v = addon.getVersion();
402 
403         // Find a platform that would satisfy the requirement.
404 
405         // First look in local packages.
406         for (Package p : localPkgs) {
407             if (p instanceof PlatformPackage) {
408                 if (v.equals(((PlatformPackage) p).getVersion())) {
409                     // We found one already installed. We don't report this dependency
410                     // as the UI only cares about resolving "newly added dependencies".
411                     return null;
412                 }
413             }
414         }
415 
416         // Look in archives already scheduled for install
417         for (ArchiveInfo ai : outArchives) {
418             Package p = ai.getNewArchive().getParentPackage();
419             if (p instanceof PlatformPackage) {
420                 if (v.equals(((PlatformPackage) p).getVersion())) {
421                     // The dependency is already scheduled for install, nothing else to do.
422                     return ai;
423                 }
424             }
425         }
426 
427         // Otherwise look in the selected archives.
428         if (selectedArchives != null) {
429             for (Archive a : selectedArchives) {
430                 Package p = a.getParentPackage();
431                 if (p instanceof PlatformPackage) {
432                     if (v.equals(((PlatformPackage) p).getVersion())) {
433                         // It's not already in the list of things to install, so add it now
434                         return insertArchive(a, outArchives,
435                                 selectedArchives, remotePkgs, remoteSources, localPkgs,
436                                 true /*automated*/);
437                     }
438                 }
439             }
440         }
441 
442         // Finally nothing matched, so let's look at all available remote packages
443         fetchRemotePackages(remotePkgs, remoteSources);
444         for (Package p : remotePkgs) {
445             if (p instanceof PlatformPackage) {
446                 if (v.equals(((PlatformPackage) p).getVersion())) {
447                     // It's not already in the list of things to install, so add the
448                     // first compatible archive we can find.
449                     for (Archive a : p.getArchives()) {
450                         if (a.isCompatible()) {
451                             return insertArchive(a, outArchives,
452                                     selectedArchives, remotePkgs, remoteSources, localPkgs,
453                                     true /*automated*/);
454                         }
455                     }
456                 }
457             }
458         }
459 
460         // We end up here if nothing matches. We don't have a good platform to match.
461         // Seriously, that can't happens unless the repository contains a bogus addon
462         // entry that does not match any existing platform API level.
463         // It's conceivable that a 3rd part addon repo might have error, in which case
464         // we'll let this one go through anyway.
465         return null;
466     }
467 
468     /** Fetch all remote packages only if really needed. */
fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources)469     protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) {
470         if (remotePkgs.size() > 0) {
471             return;
472         }
473 
474         for (RepoSource remoteSrc : remoteSources) {
475             Package[] pkgs = remoteSrc.getPackages();
476             if (pkgs != null) {
477                 nextPackage: for (Package pkg : pkgs) {
478                     for (Archive a : pkg.getArchives()) {
479                         // Only add a package if it contains at least one compatible archive
480                         if (a.isCompatible()) {
481                             remotePkgs.add(pkg);
482                             continue nextPackage;
483                         }
484                     }
485                 }
486             }
487         }
488     }
489 
490 }
491