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