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.prefs.AndroidLocation.AndroidLocationException;
     20 import com.android.sdklib.ISdkLog;
     21 import com.android.sdklib.SdkConstants;
     22 import com.android.sdklib.SdkManager;
     23 import com.android.sdklib.internal.avd.AvdManager;
     24 import com.android.sdklib.internal.repository.AddonPackage;
     25 import com.android.sdklib.internal.repository.Archive;
     26 import com.android.sdklib.internal.repository.DocPackage;
     27 import com.android.sdklib.internal.repository.ExtraPackage;
     28 import com.android.sdklib.internal.repository.ITask;
     29 import com.android.sdklib.internal.repository.ITaskFactory;
     30 import com.android.sdklib.internal.repository.ITaskMonitor;
     31 import com.android.sdklib.internal.repository.LocalSdkParser;
     32 import com.android.sdklib.internal.repository.Package;
     33 import com.android.sdklib.internal.repository.PlatformPackage;
     34 import com.android.sdklib.internal.repository.RepoSource;
     35 import com.android.sdklib.internal.repository.RepoSources;
     36 import com.android.sdklib.internal.repository.SamplePackage;
     37 import com.android.sdklib.internal.repository.ToolPackage;
     38 import com.android.sdklib.repository.SdkRepository;
     39 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     40 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
     41 
     42 import org.eclipse.jface.dialogs.MessageDialog;
     43 import org.eclipse.swt.widgets.Display;
     44 import org.eclipse.swt.widgets.Shell;
     45 
     46 import java.io.ByteArrayOutputStream;
     47 import java.io.PrintStream;
     48 import java.util.ArrayList;
     49 import java.util.Collection;
     50 import java.util.HashMap;
     51 import java.util.HashSet;
     52 import java.util.Iterator;
     53 
     54 /**
     55  * Data shared between {@link UpdaterWindowImpl} and its pages.
     56  */
     57 class UpdaterData {
     58     private String mOsSdkRoot;
     59 
     60     private final ISdkLog mSdkLog;
     61     private ITaskFactory mTaskFactory;
     62     private boolean mUserCanChangeSdkRoot;
     63 
     64     private SdkManager mSdkManager;
     65     private AvdManager mAvdManager;
     66 
     67     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
     68     private final RepoSources mSources = new RepoSources();
     69 
     70     private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);
     71     private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);
     72 
     73     private ImageFactory mImageFactory;
     74 
     75     private final SettingsController mSettingsController;
     76 
     77     private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();
     78 
     79     private Shell mWindowShell;
     80 
     81     private AndroidLocationException mAvdManagerInitError;
     82 
     83     /**
     84      * Creates a new updater data.
     85      *
     86      * @param sdkLog Logger. Cannot be null.
     87      * @param osSdkRoot The OS path to the SDK root.
     88      */
     89     public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {
     90         mOsSdkRoot = osSdkRoot;
     91         mSdkLog = sdkLog;
     92 
     93         mSettingsController = new SettingsController(this);
     94 
     95         initSdk();
     96     }
     97 
     98     // ----- getters, setters ----
     99 
    100     public String getOsSdkRoot() {
    101         return mOsSdkRoot;
    102     }
    103 
    104     public void setTaskFactory(ITaskFactory taskFactory) {
    105         mTaskFactory = taskFactory;
    106     }
    107 
    108     public ITaskFactory getTaskFactory() {
    109         return mTaskFactory;
    110     }
    111 
    112     public void setUserCanChangeSdkRoot(boolean userCanChangeSdkRoot) {
    113         mUserCanChangeSdkRoot = userCanChangeSdkRoot;
    114     }
    115 
    116     public boolean canUserChangeSdkRoot() {
    117         return mUserCanChangeSdkRoot;
    118     }
    119 
    120     public RepoSources getSources() {
    121         return mSources;
    122     }
    123 
    124     public RepoSourcesAdapter getSourcesAdapter() {
    125         return mSourcesAdapter;
    126     }
    127 
    128     public LocalSdkParser getLocalSdkParser() {
    129         return mLocalSdkParser;
    130     }
    131 
    132     public LocalSdkAdapter getLocalSdkAdapter() {
    133         return mLocalSdkAdapter;
    134     }
    135 
    136     public ISdkLog getSdkLog() {
    137         return mSdkLog;
    138     }
    139 
    140     public void setImageFactory(ImageFactory imageFactory) {
    141         mImageFactory = imageFactory;
    142     }
    143 
    144     public ImageFactory getImageFactory() {
    145         return mImageFactory;
    146     }
    147 
    148     public SdkManager getSdkManager() {
    149         return mSdkManager;
    150     }
    151 
    152     public AvdManager getAvdManager() {
    153         return mAvdManager;
    154     }
    155 
    156     public SettingsController getSettingsController() {
    157         return mSettingsController;
    158     }
    159 
    160     /** Adds a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
    161     public void addListeners(ISdkListener listener) {
    162         if (mListeners.contains(listener) == false) {
    163             mListeners.add(listener);
    164         }
    165     }
    166 
    167     /** Removes a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
    168     public void removeListener(ISdkListener listener) {
    169         mListeners.remove(listener);
    170     }
    171 
    172     public void setWindowShell(Shell windowShell) {
    173         mWindowShell = windowShell;
    174     }
    175 
    176     public Shell getWindowShell() {
    177         return mWindowShell;
    178     }
    179 
    180     /**
    181      * Check if any error occurred during initialization.
    182      * If it did, display an error message.
    183      *
    184      * @return True if an error occurred, false if we should continue.
    185      */
    186     public boolean checkIfInitFailed() {
    187         if (mAvdManagerInitError != null) {
    188             String example;
    189             if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
    190                 example = "%USERPROFILE%";     //$NON-NLS-1$
    191             } else {
    192                 example = "~";                 //$NON-NLS-1$
    193             }
    194 
    195             String error = String.format(
    196                 "The AVD manager normally uses the user's profile directory to store " +
    197                 "AVD files. However it failed to find the default profile directory. " +
    198                 "\n" +
    199                 "To fix this, please set the environment variable ANDROID_SDK_HOME to " +
    200                 "a valid path such as \"%s\".",
    201                 example);
    202 
    203             // We may not have any UI. Only display a dialog if there's a window shell available.
    204             if (mWindowShell != null) {
    205                 MessageDialog.openError(mWindowShell,
    206                     "Android Virtual Devices Manager",
    207                     error);
    208             } else {
    209                 mSdkLog.error(null /* Throwable */, "%s", error);  //$NON-NLS-1$
    210             }
    211 
    212             return true;
    213         }
    214         return false;
    215     }
    216 
    217     // -----
    218 
    219     /**
    220      * Initializes the {@link SdkManager} and the {@link AvdManager}.
    221      */
    222     private void initSdk() {
    223         mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
    224         try {
    225             mAvdManager = null; // remove the old one if needed.
    226             mAvdManager = new AvdManager(mSdkManager, mSdkLog);
    227         } catch (AndroidLocationException e) {
    228             mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage());  //$NON-NLS-1$
    229 
    230             // Note: we used to continue here, but the thing is that
    231             // mAvdManager==null so nothing is really going to work as
    232             // expected. Let's just display an error later in checkIfInitFailed()
    233             // and abort right there. This step is just too early in the SWT
    234             // setup process to display a message box yet.
    235 
    236             mAvdManagerInitError = e;
    237         }
    238 
    239         // notify listeners.
    240         notifyListeners(false /*init*/);
    241     }
    242 
    243     /**
    244      * Reloads the SDK content (targets).
    245      * <p/>
    246      * This also reloads the AVDs in case their status changed.
    247      * <p/>
    248      * This does not notify the listeners ({@link ISdkListener}).
    249      */
    250     public void reloadSdk() {
    251         // reload SDK
    252         mSdkManager.reloadSdk(mSdkLog);
    253 
    254         // reload AVDs
    255         if (mAvdManager != null) {
    256             try {
    257                 mAvdManager.reloadAvds(mSdkLog);
    258             } catch (AndroidLocationException e) {
    259                 // FIXME
    260             }
    261         }
    262 
    263         // notify adapters?
    264         mLocalSdkParser.clearPackages();
    265         // TODO
    266 
    267         // notify listeners
    268         notifyListeners(false /*init*/);
    269     }
    270 
    271     /**
    272      * Reloads the AVDs.
    273      * <p/>
    274      * This does not notify the listeners.
    275      */
    276     public void reloadAvds() {
    277         // reload AVDs
    278         if (mAvdManager != null) {
    279             try {
    280                 mAvdManager.reloadAvds(mSdkLog);
    281             } catch (AndroidLocationException e) {
    282                 mSdkLog.error(e, null);
    283             }
    284         }
    285     }
    286 
    287     /**
    288      * Sets up the default sources: <br/>
    289      * - the default google SDK repository, <br/>
    290      * - the extra repo URLs from the environment, <br/>
    291      * - the user sources from prefs <br/>
    292      * - and finally the extra user repo URLs from the environment.
    293      */
    294     public void setupDefaultSources() {
    295         RepoSources sources = getSources();
    296         sources.add(new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /*userSource*/));
    297 
    298         // SDK_UPDATER_URLS is a semicolon-separated list of URLs that can be used to
    299         // seed the SDK Updater list for full repositories.
    300         String str = System.getenv("SDK_UPDATER_URLS");
    301         if (str != null) {
    302             String[] urls = str.split(";");
    303             for (String url : urls) {
    304                 if (url != null && url.length() > 0) {
    305                     RepoSource s = new RepoSource(url, false /*userSource*/);
    306                     if (!sources.hasSource(s)) {
    307                         sources.add(s);
    308                     }
    309                 }
    310             }
    311         }
    312 
    313         // Load user sources
    314         sources.loadUserSources(getSdkLog());
    315 
    316         // SDK_UPDATER_USER_URLS is a semicolon-separated list of URLs that can be used to
    317         // seed the SDK Updater list for user-only repositories. User sources can only provide
    318         // add-ons and extra packages.
    319         str = System.getenv("SDK_UPDATER_USER_URLS");
    320         if (str != null) {
    321             String[] urls = str.split(";");
    322             for (String url : urls) {
    323                 if (url != null && url.length() > 0) {
    324                     RepoSource s = new RepoSource(url, true /*userSource*/);
    325                     if (!sources.hasSource(s)) {
    326                         sources.add(s);
    327                     }
    328                 }
    329             }
    330         }
    331     }
    332 
    333     /**
    334      * Returns the list of installed packages, parsing them if this has not yet been done.
    335      */
    336     public Package[] getInstalledPackage() {
    337         LocalSdkParser parser = getLocalSdkParser();
    338 
    339         Package[] packages = parser.getPackages();
    340 
    341         if (packages == null) {
    342             // load on demand the first time
    343             packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), getSdkLog());
    344         }
    345 
    346         return packages;
    347     }
    348 
    349     /**
    350      * Notify the listeners ({@link ISdkListener}) that the SDK was reloaded.
    351      * <p/>
    352      * This can be called from any thread.
    353      *
    354      * @param init whether the SDK loaded for the first time.
    355      */
    356     public void notifyListeners(final boolean init) {
    357         if (mWindowShell != null && mListeners.size() > 0) {
    358             mWindowShell.getDisplay().syncExec(new Runnable() {
    359                 public void run() {
    360                     for (ISdkListener listener : mListeners) {
    361                         try {
    362                             listener.onSdkChange(init);
    363                         } catch (Throwable t) {
    364                             mSdkLog.error(t, null);
    365                         }
    366                     }
    367                 }
    368             });
    369         }
    370     }
    371 
    372     /**
    373      * Install the list of given {@link Archive}s. This is invoked by the user selecting some
    374      * packages in the remote page and then clicking "install selected".
    375      *
    376      * @param result The archives to install. Incompatible ones will be skipped.
    377      */
    378     public void installArchives(final ArrayList<ArchiveInfo> result) {
    379         if (mTaskFactory == null) {
    380             throw new IllegalArgumentException("Task Factory is null");
    381         }
    382 
    383         final boolean forceHttp = getSettingsController().getForceHttp();
    384 
    385         mTaskFactory.start("Installing Archives", new ITask() {
    386             public void run(ITaskMonitor monitor) {
    387 
    388                 final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
    389                 monitor.setProgressMax(result.size() * progressPerArchive);
    390                 monitor.setDescription("Preparing to install archives");
    391 
    392                 boolean installedAddon = false;
    393                 boolean installedTools = false;
    394 
    395                 // Mark all current local archives as already installed.
    396                 HashSet<Archive> installedArchives = new HashSet<Archive>();
    397                 for (Package p : getInstalledPackage()) {
    398                     for (Archive a : p.getArchives()) {
    399                         installedArchives.add(a);
    400                     }
    401                 }
    402 
    403                 int numInstalled = 0;
    404                 nextArchive: for (ArchiveInfo ai : result) {
    405                     Archive archive = ai.getNewArchive();
    406                     if (archive == null) {
    407                         // This is not supposed to happen.
    408                         continue nextArchive;
    409                     }
    410 
    411                     int nextProgress = monitor.getProgress() + progressPerArchive;
    412                     try {
    413                         if (monitor.isCancelRequested()) {
    414                             break;
    415                         }
    416 
    417                         ArchiveInfo[] adeps = ai.getDependsOn();
    418                         if (adeps != null) {
    419                             for (ArchiveInfo adep : adeps) {
    420                                 Archive na = adep.getNewArchive();
    421                                 if (na == null) {
    422                                     // This archive depends on a missing archive.
    423                                     // We shouldn't get here.
    424                                     // Skip it.
    425                                     monitor.setResult("Skipping '%1$s'; it depends on a missing package.",
    426                                             archive.getParentPackage().getShortDescription());
    427                                     continue nextArchive;
    428                                 } else if (!installedArchives.contains(na)) {
    429                                     // This archive depends on another one that was not installed.
    430                                     // We shouldn't get here.
    431                                     // Skip it.
    432                                     monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
    433                                             archive.getParentPackage().getShortDescription(),
    434                                             adep.getShortDescription());
    435                                     continue nextArchive;
    436                                 }
    437                             }
    438                         }
    439 
    440                         if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
    441                             // We installed this archive.
    442                             installedArchives.add(archive);
    443                             numInstalled++;
    444 
    445                             // If this package was replacing an existing one, the old one
    446                             // is no longer installed.
    447                             installedArchives.remove(ai.getReplaced());
    448 
    449                             // Check if we successfully installed a tool or add-on package.
    450                             if (archive.getParentPackage() instanceof AddonPackage) {
    451                                 installedAddon = true;
    452                             } else if (archive.getParentPackage() instanceof ToolPackage) {
    453                                 installedTools = true;
    454                             }
    455                         }
    456 
    457                     } catch (Throwable t) {
    458                         // Display anything unexpected in the monitor.
    459                         String msg = t.getMessage();
    460                         if (msg != null) {
    461                             msg = String.format("Unexpected Error installing '%1$s': %2$s: %3$s",
    462                                     archive.getParentPackage().getShortDescription(),
    463                                     t.getClass().getCanonicalName(), msg);
    464                         } else {
    465                             // no error info? get the stack call to display it
    466                             // At least that'll give us a better bug report.
    467                             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    468                             t.printStackTrace(new PrintStream(baos));
    469 
    470                             msg = String.format("Unexpected Error installing '%1$s'\n%2$s",
    471                                     archive.getParentPackage().getShortDescription(),
    472                                     baos.toString());
    473                         }
    474 
    475                         monitor.setResult(msg);
    476                         mSdkLog.error(t, msg);
    477                     } finally {
    478 
    479                         // Always move the progress bar to the desired position.
    480                         // This allows internal methods to not have to care in case
    481                         // they abort early
    482                         monitor.incProgress(nextProgress - monitor.getProgress());
    483                     }
    484                 }
    485 
    486                 if (installedAddon) {
    487                     // Update the USB vendor ids for adb
    488                     try {
    489                         mSdkManager.updateAdb();
    490                         monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons.");
    491                     } catch (Exception e) {
    492                         mSdkLog.error(e, "Update ADB failed");
    493                         monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons.");
    494                     }
    495                 }
    496 
    497                 if (installedAddon || installedTools) {
    498                     // We need to restart ADB. Actually since we don't know if it's even
    499                     // running, maybe we should just kill it and not start it.
    500                     // Note: it turns out even under Windows we don't need to kill adb
    501                     // before updating the tools folder, as adb.exe is (surprisingly) not
    502                     // locked.
    503 
    504                     askForAdbRestart(monitor);
    505                 }
    506 
    507                 if (installedTools) {
    508                     notifyToolsNeedsToBeRestarted();
    509                 }
    510 
    511                 if (numInstalled == 0) {
    512                     monitor.setDescription("Done. Nothing was installed.");
    513                 } else {
    514                     monitor.setDescription("Done. %1$d %2$s installed.",
    515                             numInstalled,
    516                             numInstalled == 1 ? "package" : "packages");
    517 
    518                     //notify listeners something was installed, so that they can refresh
    519                     reloadSdk();
    520                 }
    521             }
    522         });
    523     }
    524 
    525     /**
    526      * Attempts to restart ADB.
    527      * <p/>
    528      * If the "ask before restart" setting is set (the default), prompt the user whether
    529      * now is a good time to restart ADB.
    530      *
    531      * @param monitor
    532      */
    533     private void askForAdbRestart(ITaskMonitor monitor) {
    534         final boolean[] canRestart = new boolean[] { true };
    535 
    536         if (getWindowShell() != null && getSettingsController().getAskBeforeAdbRestart()) {
    537             // need to ask for permission first
    538             Display display = getWindowShell().getDisplay();
    539 
    540             display.syncExec(new Runnable() {
    541                 public void run() {
    542                     canRestart[0] = MessageDialog.openQuestion(getWindowShell(),
    543                             "ADB Restart",
    544                             "A package that depends on ADB has been updated. It is recommended " +
    545                             "to restart ADB. Is it OK to do it now? If not, you can restart it " +
    546                             "manually later.");
    547                 }
    548             });
    549         }
    550 
    551         if (canRestart[0]) {
    552             AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
    553             adb.stopAdb();
    554             adb.startAdb();
    555         }
    556     }
    557 
    558     private void notifyToolsNeedsToBeRestarted() {
    559         if (getWindowShell() == null) {
    560             // We don't need to print anything if this is a standalone console update.
    561             return;
    562         }
    563 
    564         Display display = getWindowShell().getDisplay();
    565 
    566         display.syncExec(new Runnable() {
    567             public void run() {
    568                 MessageDialog.openInformation(getWindowShell(),
    569                         "Android Tools Updated",
    570                         "The Android SDK and AVD Manager that you are currently using has been updated. " +
    571                         "It is recommended that you now close the manager window and re-open it. " +
    572                         "If you started this window from Eclipse, please check if the Android " +
    573                         "plug-in needs to be updated.");
    574             }
    575         });
    576     }
    577 
    578 
    579     /**
    580      * Tries to update all the *existing* local packages.
    581      * This version *requires* to be run with a GUI.
    582      * <p/>
    583      * There are two modes of operation:
    584      * <ul>
    585      * <li>If selectedArchives is null, refreshes all sources, compares the available remote
    586      * packages with the current local ones and suggest updates to be done to the user (including
    587      * new platforms that the users doesn't have yet).
    588      * <li>If selectedArchives is not null, this represents a list of archives/packages that
    589      * the user wants to install or update, so just process these.
    590      * </ul>
    591      *
    592      * @param selectedArchives The list of remote archives to consider for the update.
    593      *  This can be null, in which case a list of remote archive is fetched from all
    594      *  available sources.
    595      */
    596     public void updateOrInstallAll_WithGUI(Collection<Archive> selectedArchives) {
    597         if (selectedArchives == null) {
    598             refreshSources(true);
    599         }
    600 
    601         UpdaterLogic ul = new UpdaterLogic();
    602         ArrayList<ArchiveInfo> archives = ul.computeUpdates(
    603                 selectedArchives,
    604                 getSources(),
    605                 getLocalSdkParser().getPackages());
    606 
    607         if (selectedArchives == null) {
    608             ul.addNewPlatforms(archives, getSources(), getLocalSdkParser().getPackages());
    609         }
    610 
    611         // TODO if selectedArchives is null and archives.len==0, find if there are
    612         // any new platform we can suggest to install instead.
    613 
    614         UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, archives);
    615         dialog.open();
    616 
    617         ArrayList<ArchiveInfo> result = dialog.getResult();
    618         if (result != null && result.size() > 0) {
    619             installArchives(result);
    620         }
    621     }
    622 
    623     /**
    624      * Tries to update all the *existing* local packages.
    625      * This version is intended to run without a GUI and
    626      * only outputs to the current {@link ISdkLog}.
    627      *
    628      * @param pkgFilter A list of {@link SdkRepository#NODES} to limit the type of packages
    629      *   we can update. A null or empty list means to update everything possible.
    630      * @param includeObsoletes True to also list and install obsolete packages.
    631      * @param dryMode True to check what would be updated/installed but do not actually
    632      *   download or install anything.
    633      */
    634     public void updateOrInstallAll_NoGUI(
    635             Collection<String> pkgFilter,
    636             boolean includeObsoletes,
    637             boolean dryMode) {
    638 
    639         refreshSources(true);
    640 
    641         UpdaterLogic ul = new UpdaterLogic();
    642         ArrayList<ArchiveInfo> archives = ul.computeUpdates(
    643                 null /*selectedArchives*/,
    644                 getSources(),
    645                 getLocalSdkParser().getPackages());
    646 
    647         ul.addNewPlatforms(archives, getSources(), getLocalSdkParser().getPackages());
    648 
    649         // Filter the selected archives to only keep the ones matching the filter
    650         if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
    651             // Map filter types to an SdkRepository Package type.
    652             HashMap<String, Class<? extends Package>> pkgMap =
    653                 new HashMap<String, Class<? extends Package>>();
    654             pkgMap.put(SdkRepository.NODE_PLATFORM, PlatformPackage.class);
    655             pkgMap.put(SdkRepository.NODE_ADD_ON,   AddonPackage.class);
    656             pkgMap.put(SdkRepository.NODE_TOOL,     ToolPackage.class);
    657             pkgMap.put(SdkRepository.NODE_DOC,      DocPackage.class);
    658             pkgMap.put(SdkRepository.NODE_SAMPLE,   SamplePackage.class);
    659             pkgMap.put(SdkRepository.NODE_EXTRA,    ExtraPackage.class);
    660 
    661             if (SdkRepository.NODES.length != pkgMap.size()) {
    662                 // Sanity check in case we forget to update this package map.
    663                 // We don't cancel the operation though.
    664                 mSdkLog.error(null,
    665                     "Filter Mismatch!\nThe package filter list has changed. Please report this.");
    666             }
    667 
    668             // Now make a set of the types that are allowed by the filter.
    669             HashSet<Class<? extends Package>> allowedPkgSet =
    670                 new HashSet<Class<? extends Package>>();
    671             for (String type : pkgFilter) {
    672                 if (pkgMap.containsKey(type)) {
    673                     allowedPkgSet.add(pkgMap.get(type));
    674                 } else {
    675                     // This should not happen unless there's a mismatch in the package map.
    676                     mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
    677                 }
    678             }
    679 
    680             // we don't need the map anymore
    681             pkgMap = null;
    682 
    683             Iterator<ArchiveInfo> it = archives.iterator();
    684             while (it.hasNext()) {
    685                 boolean keep = false;
    686                 ArchiveInfo ai = it.next();
    687                 Archive a = ai.getNewArchive();
    688                 if (a != null) {
    689                     Package p = a.getParentPackage();
    690                     if (p != null && allowedPkgSet.contains(p.getClass())) {
    691                         keep = true;
    692                     }
    693                 }
    694 
    695                 if (!keep) {
    696                     it.remove();
    697                 }
    698             }
    699 
    700             if (archives.size() == 0) {
    701                 mSdkLog.warning("The package filter removed all packages. There is nothing to install.\n" +
    702                         "Please consider trying updating again without a package filter.");
    703                 return;
    704             }
    705         }
    706 
    707         if (!includeObsoletes && archives != null && archives.size() > 0) {
    708             // Filter obsolete packages out
    709             Iterator<ArchiveInfo> it = archives.iterator();
    710             while (it.hasNext()) {
    711                 boolean keep = false;
    712                 ArchiveInfo ai = it.next();
    713                 Archive a = ai.getNewArchive();
    714                 if (a != null) {
    715                     Package p = a.getParentPackage();
    716                     if (p != null && !p.isObsolete()) {
    717                         keep = true;
    718                     }
    719                 }
    720 
    721                 if (!keep) {
    722                     it.remove();
    723                 }
    724             }
    725 
    726             if (archives.size() == 0) {
    727                 mSdkLog.warning("All candidate packages were obsolete. Nothing to install.");
    728                 return;
    729             }
    730         }
    731 
    732         // TODO if selectedArchives is null and archives.len==0, find if there are
    733         // any new platform we can suggest to install instead.
    734 
    735         if (archives != null && archives.size() > 0) {
    736             if (dryMode) {
    737                 mSdkLog.printf("Packages selected for install:\n");
    738                 for (ArchiveInfo ai : archives) {
    739                     Archive a = ai.getNewArchive();
    740                     if (a != null) {
    741                         Package p = a.getParentPackage();
    742                         if (p != null) {
    743                             mSdkLog.printf("- %1$s\n", p.getShortDescription());
    744                         }
    745                     }
    746                 }
    747                 mSdkLog.printf("\nDry mode is on so nothing will actually be installed.\n");
    748             } else {
    749                 installArchives(archives);
    750             }
    751         } else {
    752             mSdkLog.printf("There is nothing to install or update.\n");
    753         }
    754     }
    755 
    756     /**
    757      * Refresh all sources. This is invoked either internally (reusing an existing monitor)
    758      * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
    759      * null and a new task should be created.)
    760      *
    761      * @param forceFetching When true, load sources that haven't been loaded yet.
    762      *                      When false, only refresh sources that have been loaded yet.
    763      */
    764     public void refreshSources(final boolean forceFetching) {
    765         assert mTaskFactory != null;
    766 
    767         final boolean forceHttp = getSettingsController().getForceHttp();
    768 
    769         mTaskFactory.start("Refresh Sources", new ITask() {
    770             public void run(ITaskMonitor monitor) {
    771                 RepoSource[] sources = mSources.getSources();
    772                 monitor.setProgressMax(sources.length);
    773                 for (RepoSource source : sources) {
    774                     if (forceFetching ||
    775                             source.getPackages() != null ||
    776                             source.getFetchError() != null) {
    777                         source.load(monitor.createSubMonitor(1), forceHttp);
    778                     }
    779                     monitor.incProgress(1);
    780                 }
    781             }
    782         });
    783     }
    784 }
    785