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.SdkConstants;
     20 import com.android.annotations.VisibleForTesting;
     21 import com.android.annotations.VisibleForTesting.Visibility;
     22 import com.android.prefs.AndroidLocation.AndroidLocationException;
     23 import com.android.sdklib.SdkManager;
     24 import com.android.sdklib.internal.avd.AvdManager;
     25 import com.android.sdklib.internal.repository.AdbWrapper;
     26 import com.android.sdklib.internal.repository.DownloadCache;
     27 import com.android.sdklib.internal.repository.ITask;
     28 import com.android.sdklib.internal.repository.ITaskFactory;
     29 import com.android.sdklib.internal.repository.ITaskMonitor;
     30 import com.android.sdklib.internal.repository.LocalSdkParser;
     31 import com.android.sdklib.internal.repository.NullTaskMonitor;
     32 import com.android.sdklib.internal.repository.archives.Archive;
     33 import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
     34 import com.android.sdklib.internal.repository.packages.AddonPackage;
     35 import com.android.sdklib.internal.repository.packages.Package;
     36 import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
     37 import com.android.sdklib.internal.repository.packages.ToolPackage;
     38 import com.android.sdklib.internal.repository.sources.SdkRepoSource;
     39 import com.android.sdklib.internal.repository.sources.SdkSource;
     40 import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
     41 import com.android.sdklib.internal.repository.sources.SdkSources;
     42 import com.android.sdklib.repository.SdkAddonConstants;
     43 import com.android.sdklib.repository.SdkRepoConstants;
     44 import com.android.sdklib.util.LineUtil;
     45 import com.android.sdklib.util.SparseIntArray;
     46 import com.android.sdkuilib.internal.repository.SettingsController.OnChangedListener;
     47 import com.android.sdkuilib.internal.repository.core.PackageLoader;
     48 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     49 import com.android.sdkuilib.internal.repository.ui.SdkUpdaterWindowImpl2;
     50 import com.android.sdkuilib.repository.ISdkChangeListener;
     51 import com.android.utils.ILogger;
     52 
     53 import org.eclipse.jface.dialogs.MessageDialog;
     54 import org.eclipse.swt.widgets.Shell;
     55 
     56 import java.io.ByteArrayOutputStream;
     57 import java.io.PrintStream;
     58 import java.util.ArrayList;
     59 import java.util.Collection;
     60 import java.util.Collections;
     61 import java.util.Comparator;
     62 import java.util.HashMap;
     63 import java.util.HashSet;
     64 import java.util.Iterator;
     65 import java.util.List;
     66 import java.util.Map;
     67 import java.util.Set;
     68 
     69 /**
     70  * Data shared between {@link SdkUpdaterWindowImpl2} and its pages.
     71  */
     72 public class UpdaterData implements IUpdaterData {
     73 
     74     public static final int NO_TOOLS_MSG = 0;
     75     public static final int TOOLS_MSG_UPDATED_FROM_ADT = 1;
     76     public static final int TOOLS_MSG_UPDATED_FROM_SDKMAN = 2;
     77 
     78     private String mOsSdkRoot;
     79 
     80     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
     81     /** Holds all sources. Do not use this directly.
     82      * Instead use {@link #getSources()} so that unit tests can override this as needed. */
     83     private final SdkSources mSources = new SdkSources();
     84     /** Holds settings. Do not use this directly.
     85      * Instead use {@link #getSettingsController()} so that unit tests can override this. */
     86     private final SettingsController mSettingsController;
     87     private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();
     88     private final ILogger mSdkLog;
     89     private ITaskFactory mTaskFactory;
     90     private Shell mWindowShell;
     91     private SdkManager mSdkManager;
     92     private AvdManager mAvdManager;
     93     /**
     94      * The current {@link PackageLoader} to use.
     95      * Lazily created in {@link #getPackageLoader()}.
     96      */
     97     private PackageLoader mPackageLoader;
     98     /**
     99      * The current {@link DownloadCache} to use.
    100      * Lazily created in {@link #getDownloadCache()}.
    101      */
    102     private DownloadCache mDownloadCache;
    103     /**
    104      * The current {@link ImageFactory}.
    105      * Set via {@link #setImageFactory(ImageFactory)} by the window implementation.
    106      * It is null when invoked using the command-line interface.
    107      */
    108     private ImageFactory mImageFactory;
    109     private AndroidLocationException mAvdManagerInitError;
    110 
    111     /**
    112      * Creates a new updater data.
    113      *
    114      * @param sdkLog Logger. Cannot be null.
    115      * @param osSdkRoot The OS path to the SDK root.
    116      */
    117     public UpdaterData(String osSdkRoot, ILogger sdkLog) {
    118         mOsSdkRoot = osSdkRoot;
    119         mSdkLog = sdkLog;
    120 
    121 
    122         mSettingsController = initSettingsController();
    123         initSdk();
    124     }
    125 
    126     // ----- getters, setters ----
    127 
    128     public String getOsSdkRoot() {
    129         return mOsSdkRoot;
    130     }
    131 
    132     @Override
    133     public DownloadCache getDownloadCache() {
    134         if (mDownloadCache == null) {
    135             mDownloadCache = new DownloadCache(
    136                     getSettingsController().getSettings().getUseDownloadCache() ?
    137                             DownloadCache.Strategy.FRESH_CACHE :
    138                             DownloadCache.Strategy.DIRECT);
    139         }
    140         return mDownloadCache;
    141     }
    142 
    143     public void setTaskFactory(ITaskFactory taskFactory) {
    144         mTaskFactory = taskFactory;
    145     }
    146 
    147     @Override
    148     public ITaskFactory getTaskFactory() {
    149         return mTaskFactory;
    150     }
    151 
    152     public SdkSources getSources() {
    153         return mSources;
    154     }
    155 
    156     public LocalSdkParser getLocalSdkParser() {
    157         return mLocalSdkParser;
    158     }
    159 
    160     @Override
    161     public ILogger getSdkLog() {
    162         return mSdkLog;
    163     }
    164 
    165     public void setImageFactory(ImageFactory imageFactory) {
    166         mImageFactory = imageFactory;
    167     }
    168 
    169     @Override
    170     public ImageFactory getImageFactory() {
    171         return mImageFactory;
    172     }
    173 
    174     @Override
    175     public SdkManager getSdkManager() {
    176         return mSdkManager;
    177     }
    178 
    179     @Override
    180     public AvdManager getAvdManager() {
    181         return mAvdManager;
    182     }
    183 
    184     @Override
    185     public SettingsController getSettingsController() {
    186         return mSettingsController;
    187     }
    188 
    189     /** Adds a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
    190     public void addListeners(ISdkChangeListener listener) {
    191         if (mListeners.contains(listener) == false) {
    192             mListeners.add(listener);
    193         }
    194     }
    195 
    196     /** Removes a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
    197     public void removeListener(ISdkChangeListener listener) {
    198         mListeners.remove(listener);
    199     }
    200 
    201     public void setWindowShell(Shell windowShell) {
    202         mWindowShell = windowShell;
    203     }
    204 
    205     @Override
    206     public Shell getWindowShell() {
    207         return mWindowShell;
    208     }
    209 
    210     public PackageLoader getPackageLoader() {
    211         // The package loader is lazily initialized here.
    212         if (mPackageLoader == null) {
    213             mPackageLoader = new PackageLoader(this);
    214         }
    215         return mPackageLoader;
    216     }
    217 
    218     /**
    219      * Check if any error occurred during initialization.
    220      * If it did, display an error message.
    221      *
    222      * @return True if an error occurred, false if we should continue.
    223      */
    224     public boolean checkIfInitFailed() {
    225         if (mAvdManagerInitError != null) {
    226             String example;
    227             if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
    228                 example = "%USERPROFILE%";     //$NON-NLS-1$
    229             } else {
    230                 example = "~";                 //$NON-NLS-1$
    231             }
    232 
    233             String error = String.format(
    234                 "The AVD manager normally uses the user's profile directory to store " +
    235                 "AVD files. However it failed to find the default profile directory. " +
    236                 "\n" +
    237                 "To fix this, please set the environment variable ANDROID_SDK_HOME to " +
    238                 "a valid path such as \"%s\".",
    239                 example);
    240 
    241             // We may not have any UI. Only display a dialog if there's a window shell available.
    242             if (mWindowShell != null && !mWindowShell.isDisposed()) {
    243                 MessageDialog.openError(mWindowShell,
    244                     "Android Virtual Devices Manager",
    245                     error);
    246             } else {
    247                 mSdkLog.error(null /* Throwable */, "%s", error);  //$NON-NLS-1$
    248             }
    249 
    250             return true;
    251         }
    252         return false;
    253     }
    254 
    255     // -----
    256 
    257     /**
    258      * Initializes the {@link SdkManager} and the {@link AvdManager}.
    259      * Extracted so that we can override this in unit tests.
    260      */
    261     @VisibleForTesting(visibility=Visibility.PRIVATE)
    262     protected void initSdk() {
    263         setSdkManager(SdkManager.createManager(mOsSdkRoot, mSdkLog));
    264         try {
    265           mAvdManager = null;
    266           mAvdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
    267         } catch (AndroidLocationException e) {
    268             mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage());  //$NON-NLS-1$
    269 
    270             // Note: we used to continue here, but the thing is that
    271             // mAvdManager==null so nothing is really going to work as
    272             // expected. Let's just display an error later in checkIfInitFailed()
    273             // and abort right there. This step is just too early in the SWT
    274             // setup process to display a message box yet.
    275 
    276             mAvdManagerInitError = e;
    277         }
    278 
    279         // notify listeners.
    280         broadcastOnSdkReload();
    281     }
    282 
    283     /**
    284      * Initializes the {@link SettingsController}
    285      * Extracted so that we can override this in unit tests.
    286      */
    287     @VisibleForTesting(visibility=Visibility.PRIVATE)
    288     protected SettingsController initSettingsController() {
    289         SettingsController settingsController = new SettingsController(mSdkLog);
    290         settingsController.registerOnChangedListener(new OnChangedListener() {
    291             @Override
    292             public void onSettingsChanged(
    293                     SettingsController controller,
    294                     SettingsController.Settings oldSettings) {
    295 
    296                 // Reset the download cache if it doesn't match the right strategy.
    297                 // The cache instance gets lazily recreated later in getDownloadCache().
    298                 if (mDownloadCache != null) {
    299                     if (controller.getSettings().getUseDownloadCache() &&
    300                             mDownloadCache.getStrategy() != DownloadCache.Strategy.FRESH_CACHE) {
    301                         mDownloadCache = null;
    302                     } else if (!controller.getSettings().getUseDownloadCache() &&
    303                             mDownloadCache.getStrategy() != DownloadCache.Strategy.DIRECT) {
    304                         mDownloadCache = null;
    305                     }
    306                 }
    307             }
    308         });
    309         return settingsController;
    310     }
    311 
    312     @VisibleForTesting(visibility=Visibility.PRIVATE)
    313     protected void setSdkManager(SdkManager sdkManager) {
    314         mSdkManager = sdkManager;
    315     }
    316 
    317     /**
    318      * Reloads the SDK content (targets).
    319      * <p/>
    320      * This also reloads the AVDs in case their status changed.
    321      * <p/>
    322      * This does not notify the listeners ({@link ISdkChangeListener}).
    323      */
    324     public void reloadSdk() {
    325         // reload SDK
    326         mSdkManager.reloadSdk(mSdkLog);
    327 
    328         // reload AVDs
    329         if (mAvdManager != null) {
    330             try {
    331                 mAvdManager.reloadAvds(mSdkLog);
    332             } catch (AndroidLocationException e) {
    333                 // FIXME
    334             }
    335         }
    336 
    337         mLocalSdkParser.clearPackages();
    338 
    339         // notify listeners
    340         broadcastOnSdkReload();
    341     }
    342 
    343     /**
    344      * Reloads the AVDs.
    345      * <p/>
    346      * This does not notify the listeners.
    347      */
    348     public void reloadAvds() {
    349         // reload AVDs
    350         if (mAvdManager != null) {
    351             try {
    352                 mAvdManager.reloadAvds(mSdkLog);
    353             } catch (AndroidLocationException e) {
    354                 mSdkLog.error(e, null);
    355             }
    356         }
    357     }
    358 
    359     /**
    360      * Sets up the default sources: <br/>
    361      * - the default google SDK repository, <br/>
    362      * - the user sources from prefs <br/>
    363      * - the extra repo URLs from the environment, <br/>
    364      * - and finally the extra user repo URLs from the environment.
    365      */
    366     public void setupDefaultSources() {
    367         SdkSources sources = getSources();
    368 
    369         // Load the conventional sources.
    370         // For testing, the env var can be set to replace the default root download URL.
    371         // It must end with a / and its the location where the updater will look for
    372         // the repository.xml, addons_list.xml and such files.
    373 
    374         String baseUrl = System.getenv("SDK_TEST_BASE_URL");                        //$NON-NLS-1$
    375         if (baseUrl == null || baseUrl.length() <= 0 || !baseUrl.endsWith("/")) {   //$NON-NLS-1$
    376             baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE;
    377         }
    378 
    379         sources.add(SdkSourceCategory.ANDROID_REPO,
    380                 new SdkRepoSource(baseUrl,
    381                                   SdkSourceCategory.ANDROID_REPO.getUiName()));
    382 
    383         // Load user sources (this will also notify change listeners but this operation is
    384         // done early enough that there shouldn't be any anyway.)
    385         sources.loadUserAddons(getSdkLog());
    386     }
    387 
    388     /**
    389      * Returns the list of installed packages, parsing them if this has not yet been done.
    390      * <p/>
    391      * The package list is cached in the {@link LocalSdkParser} and will be reset when
    392      * {@link #reloadSdk()} is invoked.
    393      */
    394     public Package[] getInstalledPackages(ITaskMonitor monitor) {
    395         LocalSdkParser parser = getLocalSdkParser();
    396 
    397         Package[] packages = parser.getPackages();
    398 
    399         if (packages == null) {
    400             // load on demand the first time
    401             packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), monitor);
    402         }
    403 
    404         return packages;
    405     }
    406     /**
    407      * Install the list of given {@link Archive}s. This is invoked by the user selecting some
    408      * packages in the remote page and then clicking "install selected".
    409      *
    410      * @param archives The archives to install. Incompatible ones will be skipped.
    411      * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
    412      * @return A list of archives that have been installed. Can be empty but not null.
    413      */
    414     @VisibleForTesting(visibility=Visibility.PRIVATE)
    415     protected List<Archive> installArchives(final List<ArchiveInfo> archives, final int flags) {
    416         if (mTaskFactory == null) {
    417             throw new IllegalArgumentException("Task Factory is null");
    418         }
    419 
    420         // this will accumulate all the packages installed.
    421         final List<Archive> newlyInstalledArchives = new ArrayList<Archive>();
    422 
    423         final boolean forceHttp = getSettingsController().getSettings().getForceHttp();
    424 
    425         // sort all archives based on their dependency level.
    426         Collections.sort(archives, new InstallOrderComparator());
    427 
    428         mTaskFactory.start("Installing Archives", new ITask() {
    429             @Override
    430             public void run(ITaskMonitor monitor) {
    431 
    432                 final int progressPerArchive = 2 * ArchiveInstaller.NUM_MONITOR_INC;
    433                 monitor.setProgressMax(1 + archives.size() * progressPerArchive);
    434                 monitor.setDescription("Preparing to install archives");
    435 
    436                 boolean installedAddon = false;
    437                 boolean installedTools = false;
    438                 boolean installedPlatformTools = false;
    439                 boolean preInstallHookInvoked = false;
    440 
    441                 // Mark all current local archives as already installed.
    442                 HashSet<Archive> installedArchives = new HashSet<Archive>();
    443                 for (Package p : getInstalledPackages(monitor.createSubMonitor(1))) {
    444                     for (Archive a : p.getArchives()) {
    445                         installedArchives.add(a);
    446                     }
    447                 }
    448 
    449                 int numInstalled = 0;
    450                 nextArchive: for (ArchiveInfo ai : archives) {
    451                     Archive archive = ai.getNewArchive();
    452                     if (archive == null) {
    453                         // This is not supposed to happen.
    454                         continue nextArchive;
    455                     }
    456 
    457                     int nextProgress = monitor.getProgress() + progressPerArchive;
    458                     try {
    459                         if (monitor.isCancelRequested()) {
    460                             break;
    461                         }
    462 
    463                         ArchiveInfo[] adeps = ai.getDependsOn();
    464                         if (adeps != null) {
    465                             for (ArchiveInfo adep : adeps) {
    466                                 Archive na = adep.getNewArchive();
    467                                 if (na == null) {
    468                                     // This archive depends on a missing archive.
    469                                     // We shouldn't get here.
    470                                     // Skip it.
    471                                     monitor.log("Skipping '%1$s'; it depends on a missing package.",
    472                                             archive.getParentPackage().getShortDescription());
    473                                     continue nextArchive;
    474                                 } else if (!installedArchives.contains(na)) {
    475                                     // This archive depends on another one that was not installed.
    476                                     // We shouldn't get here.
    477                                     // Skip it.
    478                                     monitor.logError("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
    479                                             archive.getParentPackage().getShortDescription(),
    480                                             adep.getShortDescription());
    481                                     continue nextArchive;
    482                                 }
    483                             }
    484                         }
    485 
    486                         if (!preInstallHookInvoked) {
    487                             preInstallHookInvoked = true;
    488                             broadcastPreInstallHook();
    489                         }
    490 
    491                         ArchiveInstaller installer = createArchiveInstaler();
    492                         if (installer.install(ai,
    493                                               mOsSdkRoot,
    494                                               forceHttp,
    495                                               mSdkManager,
    496                                               getDownloadCache(),
    497                                               monitor)) {
    498                             // We installed this archive.
    499                             newlyInstalledArchives.add(archive);
    500                             installedArchives.add(archive);
    501                             numInstalled++;
    502 
    503                             // If this package was replacing an existing one, the old one
    504                             // is no longer installed.
    505                             installedArchives.remove(ai.getReplaced());
    506 
    507                             // Check if we successfully installed a platform-tool or add-on package.
    508                             if (archive.getParentPackage() instanceof AddonPackage) {
    509                                 installedAddon = true;
    510                             } else if (archive.getParentPackage() instanceof ToolPackage) {
    511                                 installedTools = true;
    512                             } else if (archive.getParentPackage() instanceof PlatformToolPackage) {
    513                                 installedPlatformTools = true;
    514                             }
    515                         }
    516 
    517                     } catch (Throwable t) {
    518                         // Display anything unexpected in the monitor.
    519                         String msg = t.getMessage();
    520                         if (msg != null) {
    521                             msg = String.format("Unexpected Error installing '%1$s': %2$s: %3$s",
    522                                     archive.getParentPackage().getShortDescription(),
    523                                     t.getClass().getCanonicalName(), msg);
    524                         } else {
    525                             // no error info? get the stack call to display it
    526                             // At least that'll give us a better bug report.
    527                             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    528                             t.printStackTrace(new PrintStream(baos));
    529 
    530                             msg = String.format("Unexpected Error installing '%1$s'\n%2$s",
    531                                     archive.getParentPackage().getShortDescription(),
    532                                     baos.toString());
    533                         }
    534 
    535                         monitor.log(     "%1$s", msg);      //$NON-NLS-1$
    536                         mSdkLog.error(t, "%1$s", msg);      //$NON-NLS-1$
    537                     } finally {
    538 
    539                         // Always move the progress bar to the desired position.
    540                         // This allows internal methods to not have to care in case
    541                         // they abort early
    542                         monitor.incProgress(nextProgress - monitor.getProgress());
    543                     }
    544                 }
    545 
    546                 if (installedAddon) {
    547                     // Update the USB vendor ids for adb
    548                     try {
    549                         mSdkManager.updateAdb();
    550                         monitor.log("Updated ADB to support the USB devices declared in the SDK add-ons.");
    551                     } catch (Exception e) {
    552                         mSdkLog.error(e, "Update ADB failed");
    553                         monitor.logError("failed to update adb to support the USB devices declared in the SDK add-ons.");
    554                     }
    555                 }
    556 
    557                 if (preInstallHookInvoked) {
    558                     broadcastPostInstallHook();
    559                 }
    560 
    561                 if (installedAddon || installedPlatformTools) {
    562                     // We need to restart ADB. Actually since we don't know if it's even
    563                     // running, maybe we should just kill it and not start it.
    564                     // Note: it turns out even under Windows we don't need to kill adb
    565                     // before updating the tools folder, as adb.exe is (surprisingly) not
    566                     // locked.
    567 
    568                     askForAdbRestart(monitor);
    569                 }
    570 
    571                 if (installedTools) {
    572                     notifyToolsNeedsToBeRestarted(flags);
    573                 }
    574 
    575                 if (numInstalled == 0) {
    576                     monitor.setDescription("Done. Nothing was installed.");
    577                 } else {
    578                     monitor.setDescription("Done. %1$d %2$s installed.",
    579                             numInstalled,
    580                             numInstalled == 1 ? "package" : "packages");
    581 
    582                     //notify listeners something was installed, so that they can refresh
    583                     reloadSdk();
    584                 }
    585             }
    586         });
    587 
    588         return newlyInstalledArchives;
    589     }
    590 
    591     /**
    592      * A comparator to sort all the {@link ArchiveInfo} based on their
    593      * dependency level. This forces the installer to install first all packages
    594      * with no dependency, then those with one level of dependency, etc.
    595      */
    596     private static class InstallOrderComparator implements Comparator<ArchiveInfo> {
    597 
    598         private final Map<ArchiveInfo, Integer> mOrders = new HashMap<ArchiveInfo, Integer>();
    599 
    600         @Override
    601         public int compare(ArchiveInfo o1, ArchiveInfo o2) {
    602             int n1 = getDependencyOrder(o1);
    603             int n2 = getDependencyOrder(o2);
    604 
    605             return n1 - n2;
    606         }
    607 
    608         private int getDependencyOrder(ArchiveInfo ai) {
    609             if (ai == null) {
    610                 return 0;
    611             }
    612 
    613             // reuse cached value, if any
    614             Integer cached = mOrders.get(ai);
    615             if (cached != null) {
    616                 return cached.intValue();
    617             }
    618 
    619             ArchiveInfo[] deps = ai.getDependsOn();
    620             if (deps == null) {
    621                 return 0;
    622             }
    623 
    624             // compute dependencies, recursively
    625             int n = deps.length;
    626 
    627             for (ArchiveInfo dep : deps) {
    628                 n += getDependencyOrder(dep);
    629             }
    630 
    631             // cache it
    632             mOrders.put(ai, Integer.valueOf(n));
    633 
    634             return n;
    635         }
    636 
    637     }
    638 
    639     /**
    640      * Attempts to restart ADB.
    641      * <p/>
    642      * If the "ask before restart" setting is set (the default), prompt the user whether
    643      * now is a good time to restart ADB.
    644      *
    645      * @param monitor
    646      */
    647     private void askForAdbRestart(ITaskMonitor monitor) {
    648         final boolean[] canRestart = new boolean[] { true };
    649 
    650         if (getWindowShell() != null &&
    651                 getSettingsController().getSettings().getAskBeforeAdbRestart()) {
    652             // need to ask for permission first
    653             final Shell shell = getWindowShell();
    654             if (shell != null && !shell.isDisposed()) {
    655                 shell.getDisplay().syncExec(new Runnable() {
    656                     @Override
    657                     public void run() {
    658                         if (!shell.isDisposed()) {
    659                             canRestart[0] = MessageDialog.openQuestion(shell,
    660                                     "ADB Restart",
    661                                     "A package that depends on ADB has been updated. \n" +
    662                                     "Do you want to restart ADB now?");
    663                         }
    664                     }
    665                 });
    666             }
    667         }
    668 
    669         if (canRestart[0]) {
    670             AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
    671             adb.stopAdb();
    672             adb.startAdb();
    673         }
    674     }
    675 
    676     private void notifyToolsNeedsToBeRestarted(int flags) {
    677 
    678         String msg = null;
    679         if ((flags & TOOLS_MSG_UPDATED_FROM_ADT) != 0) {
    680             msg =
    681             "The Android SDK and AVD Manager that you are currently using has been updated. " +
    682             "Please also run Eclipse > Help > Check for Updates to see if the Android " +
    683             "plug-in needs to be updated.";
    684 
    685         } else if ((flags & TOOLS_MSG_UPDATED_FROM_SDKMAN) != 0) {
    686             msg =
    687             "The Android SDK and AVD Manager that you are currently using has been updated. " +
    688             "It is recommended that you now close the manager window and re-open it. " +
    689             "If you use Eclipse, please run Help > Check for Updates to see if the Android " +
    690             "plug-in needs to be updated.";
    691         }
    692 
    693         final String msg2 = msg;
    694 
    695         final Shell shell = getWindowShell();
    696         if (msg2 != null && shell != null && !shell.isDisposed()) {
    697             shell.getDisplay().syncExec(new Runnable() {
    698                 @Override
    699                 public void run() {
    700                     if (!shell.isDisposed()) {
    701                         MessageDialog.openInformation(shell,
    702                                 "Android Tools Updated",
    703                                 msg2);
    704                     }
    705                 }
    706             });
    707         }
    708     }
    709 
    710 
    711     /**
    712      * Tries to update all the *existing* local packages.
    713      * This version *requires* to be run with a GUI.
    714      * <p/>
    715      * There are two modes of operation:
    716      * <ul>
    717      * <li>If selectedArchives is null, refreshes all sources, compares the available remote
    718      * packages with the current local ones and suggest updates to be done to the user (including
    719      * new platforms that the users doesn't have yet).
    720      * <li>If selectedArchives is not null, this represents a list of archives/packages that
    721      * the user wants to install or update, so just process these.
    722      * </ul>
    723      *
    724      * @param selectedArchives The list of remote archives to consider for the update.
    725      *  This can be null, in which case a list of remote archive is fetched from all
    726      *  available sources.
    727      * @param includeObsoletes True if obsolete packages should be used when resolving what
    728      *  to update.
    729      * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
    730      * @return A list of archives that have been installed. Can be null if nothing was done.
    731      */
    732     public List<Archive> updateOrInstallAll_WithGUI(
    733             Collection<Archive> selectedArchives,
    734             boolean includeObsoletes,
    735             int flags) {
    736 
    737         // Note: we no longer call refreshSources(true) here. This will be done
    738         // automatically by computeUpdates() iif it needs to access sources to
    739         // resolve missing dependencies.
    740 
    741         SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
    742         List<ArchiveInfo> archives = ul.computeUpdates(
    743                 selectedArchives,
    744                 getSources(),
    745                 getLocalSdkParser().getPackages(),
    746                 includeObsoletes);
    747 
    748         if (selectedArchives == null) {
    749             getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
    750             ul.addNewPlatforms(
    751                     archives,
    752                     getSources(),
    753                     getLocalSdkParser().getPackages(),
    754                     includeObsoletes);
    755         }
    756 
    757         // TODO if selectedArchives is null and archives.len==0, find if there are
    758         // any new platform we can suggest to install instead.
    759 
    760         Collections.sort(archives);
    761 
    762         SdkUpdaterChooserDialog dialog =
    763             new SdkUpdaterChooserDialog(getWindowShell(), this, archives);
    764         dialog.open();
    765 
    766         ArrayList<ArchiveInfo> result = dialog.getResult();
    767         if (result != null && result.size() > 0) {
    768             return installArchives(result, flags);
    769         }
    770         return null;
    771     }
    772 
    773     /**
    774      * Fetches all archives available on the known remote sources.
    775      *
    776      * Used by {@link UpdaterData#listRemotePackages_NoGUI} and
    777      * {@link UpdaterData#updateOrInstallAll_NoGUI}.
    778      *
    779      * @param includeAll True to list and install all packages, including obsolete ones.
    780      * @return A list of potential {@link ArchiveInfo} to install.
    781      */
    782     private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeAll) {
    783         refreshSources(true);
    784         getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
    785 
    786         List<ArchiveInfo> archives;
    787         SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
    788 
    789         if (includeAll) {
    790             archives = ul.getAllRemoteArchives(
    791                     getSources(),
    792                     getLocalSdkParser().getPackages(),
    793                     includeAll);
    794 
    795         } else {
    796             archives = ul.computeUpdates(
    797                     null /*selectedArchives*/,
    798                     getSources(),
    799                     getLocalSdkParser().getPackages(),
    800                     includeAll);
    801 
    802             ul.addNewPlatforms(
    803                     archives,
    804                     getSources(),
    805                     getLocalSdkParser().getPackages(),
    806                     includeAll);
    807         }
    808 
    809         Collections.sort(archives);
    810         return archives;
    811     }
    812 
    813     /**
    814      * Lists remote packages available for install using
    815      * {@link UpdaterData#updateOrInstallAll_NoGUI}.
    816      *
    817      * @param includeAll True to list and install all packages, including obsolete ones.
    818      * @param extendedOutput True to display more details on each package.
    819      */
    820     public void listRemotePackages_NoGUI(boolean includeAll, boolean extendedOutput) {
    821 
    822         List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);
    823 
    824         mSdkLog.info("Packages available for installation or update: %1$d\n", archives.size());
    825 
    826         int index = 1;
    827         for (ArchiveInfo ai : archives) {
    828             Archive a = ai.getNewArchive();
    829             if (a != null) {
    830                 Package p = a.getParentPackage();
    831                 if (p != null) {
    832                     if (extendedOutput) {
    833                         mSdkLog.info("----------\n");
    834                         mSdkLog.info("id: %1$d or \"%2$s\"\n", index, p.installId());
    835                         mSdkLog.info("     Type: %1$s\n",
    836                                 p.getClass().getSimpleName().replaceAll("Package", "")); //$NON-NLS-1$ //$NON-NLS-2$
    837                         String desc = LineUtil.reformatLine("     Desc: %s\n",
    838                                 p.getLongDescription());
    839                         mSdkLog.info("%s", desc); //$NON-NLS-1$
    840                     } else {
    841                         mSdkLog.info("%1$ 4d- %2$s\n",
    842                                 index,
    843                                 p.getShortDescription());
    844                     }
    845                     index++;
    846                 }
    847             }
    848         }
    849     }
    850 
    851     /**
    852      * Tries to update all the *existing* local packages.
    853      * This version is intended to run without a GUI and
    854      * only outputs to the current {@link ILogger}.
    855      *
    856      * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()}
    857      *   or package indexes to limit the packages we can update or install.
    858      *   A null or empty list means to update everything possible.
    859      * @param includeAll True to list and install all packages, including obsolete ones.
    860      * @param dryMode True to check what would be updated/installed but do not actually
    861      *   download or install anything.
    862      * @return A list of archives that have been installed. Can be null if nothing was done.
    863      */
    864     public List<Archive> updateOrInstallAll_NoGUI(
    865             Collection<String> pkgFilter,
    866             boolean includeAll,
    867             boolean dryMode) {
    868 
    869         List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);
    870 
    871         // Filter the selected archives to only keep the ones matching the filter
    872         if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
    873             // Map filter types to an SdkRepository Package type,
    874             // e.g. create a map "platform" => PlatformPackage.class
    875             HashMap<String, Class<? extends Package>> pkgMap =
    876                 new HashMap<String, Class<? extends Package>>();
    877 
    878             mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
    879             mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);
    880 
    881             // Prepare a map install-id => package instance
    882             HashMap<String, Package> installIdMap = new HashMap<String, Package>();
    883             for (ArchiveInfo ai : archives) {
    884                 Archive a = ai.getNewArchive();
    885                 if (a != null) {
    886                     Package p = a.getParentPackage();
    887                     if (p != null) {
    888                         String id = p.installId();
    889                         if (id != null && id.length() > 0 && !installIdMap.containsKey(id)) {
    890                             installIdMap.put(id, p);
    891                         }
    892                     }
    893                 }
    894             }
    895 
    896             // Now intersect this with the pkgFilter requested by the user, in order to
    897             // only keep the classes that the user wants to install.
    898             // We also create a set with the package indices requested by the user
    899             // and a set of install-ids requested by the user.
    900 
    901             HashSet<Class<? extends Package>> userFilteredClasses =
    902                 new HashSet<Class<? extends Package>>();
    903             SparseIntArray userFilteredIndices = new SparseIntArray();
    904             Set<String> userFilteredInstallIds = new HashSet<String>();
    905 
    906             for (String type : pkgFilter) {
    907                 if (installIdMap.containsKey(type)) {
    908                     userFilteredInstallIds.add(type);
    909 
    910                 } else if (type.replaceAll("[0-9]+", "").length() == 0) {//$NON-NLS-1$ //$NON-NLS-2$
    911                     // An all-digit number is a package index requested by the user.
    912                     int index = Integer.parseInt(type);
    913                     userFilteredIndices.put(index, index);
    914 
    915                 } else if (pkgMap.containsKey(type)) {
    916                     userFilteredClasses.add(pkgMap.get(type));
    917 
    918                 } else {
    919                     // This should not happen unless there's a mismatch in the package map.
    920                     mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
    921                 }
    922             }
    923 
    924             // we don't need the maps anymore
    925             pkgMap = null;
    926             installIdMap = null;
    927 
    928             // Now filter the remote archives list to keep:
    929             // - any package which class matches userFilteredClasses
    930             // - any package index which matches userFilteredIndices
    931             // - any package install id which matches userFilteredInstallIds
    932 
    933             int index = 1;
    934             for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
    935                 boolean keep = false;
    936                 ArchiveInfo ai = it.next();
    937                 Archive a = ai.getNewArchive();
    938                 if (a != null) {
    939                     Package p = a.getParentPackage();
    940                     if (p != null) {
    941                         if (userFilteredInstallIds.contains(p.installId()) ||
    942                                 userFilteredClasses.contains(p.getClass()) ||
    943                                 userFilteredIndices.get(index) > 0) {
    944                             keep = true;
    945                         }
    946 
    947                         index++;
    948                     }
    949                 }
    950 
    951                 if (!keep) {
    952                     it.remove();
    953                 }
    954             }
    955 
    956             if (archives.size() == 0) {
    957                 mSdkLog.info(LineUtil.reflowLine(
    958                         "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n"));
    959                 return null;
    960             }
    961         }
    962 
    963         if (archives != null && archives.size() > 0) {
    964             if (dryMode) {
    965                 mSdkLog.info("Packages selected for install:\n");
    966                 for (ArchiveInfo ai : archives) {
    967                     Archive a = ai.getNewArchive();
    968                     if (a != null) {
    969                         Package p = a.getParentPackage();
    970                         if (p != null) {
    971                             mSdkLog.info("- %1$s\n", p.getShortDescription());
    972                         }
    973                     }
    974                 }
    975                 mSdkLog.info("\nDry mode is on so nothing is actually being installed.\n");
    976             } else {
    977                 return installArchives(archives, NO_TOOLS_MSG);
    978             }
    979         } else {
    980             mSdkLog.info("There is nothing to install or update.\n");
    981         }
    982 
    983         return null;
    984     }
    985 
    986     @SuppressWarnings("unchecked")
    987     private void mapFilterToPackageClass(
    988             HashMap<String, Class<? extends Package>> inOutPkgMap,
    989             String[] nodes) {
    990 
    991         // Automatically find the classes matching the node names
    992         ClassLoader classLoader = getClass().getClassLoader();
    993         String basePackage = Package.class.getPackage().getName();
    994 
    995         for (String node : nodes) {
    996             // Capitalize the name
    997             String name = node.substring(0, 1).toUpperCase() + node.substring(1);
    998 
    999             // We can have one dash at most in a name. If it's present, we'll try
   1000             // with the dash or with the next letter capitalized.
   1001             int dash = name.indexOf('-');
   1002             if (dash > 0) {
   1003                 name = name.replaceFirst("-", "");
   1004             }
   1005 
   1006             for (int alternatives = 0; alternatives < 2; alternatives++) {
   1007 
   1008                 String fqcn = basePackage + '.' + name + "Package";  //$NON-NLS-1$
   1009                 try {
   1010                     Class<? extends Package> clazz =
   1011                         (Class<? extends Package>) classLoader.loadClass(fqcn);
   1012                     if (clazz != null) {
   1013                         inOutPkgMap.put(node, clazz);
   1014                         continue;
   1015                     }
   1016                 } catch (Throwable ignore) {
   1017                 }
   1018 
   1019                 if (alternatives == 0 && dash > 0) {
   1020                     // Try an alternative where the next letter after the dash
   1021                     // is converted to an upper case.
   1022                     name = name.substring(0, dash) +
   1023                            name.substring(dash, dash + 1).toUpperCase() +
   1024                            name.substring(dash + 1);
   1025                 } else {
   1026                     break;
   1027                 }
   1028             }
   1029         }
   1030     }
   1031 
   1032     /**
   1033      * Refresh all sources. This is invoked either internally (reusing an existing monitor)
   1034      * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
   1035      * null and a new task should be created.)
   1036      *
   1037      * @param forceFetching When true, load sources that haven't been loaded yet.
   1038      *                      When false, only refresh sources that have been loaded yet.
   1039      */
   1040     public void refreshSources(final boolean forceFetching) {
   1041         assert mTaskFactory != null;
   1042 
   1043         final boolean forceHttp = getSettingsController().getSettings().getForceHttp();
   1044 
   1045         mTaskFactory.start("Refresh Sources", new ITask() {
   1046             @Override
   1047             public void run(ITaskMonitor monitor) {
   1048 
   1049                 getPackageLoader().loadRemoteAddonsList(monitor);
   1050 
   1051                 SdkSource[] sources = getSources().getAllSources();
   1052                 monitor.setDescription("Refresh Sources");
   1053                 monitor.setProgressMax(monitor.getProgress() + sources.length);
   1054                 for (SdkSource source : sources) {
   1055                     if (forceFetching ||
   1056                             source.getPackages() != null ||
   1057                             source.getFetchError() != null) {
   1058                         source.load(getDownloadCache(), monitor.createSubMonitor(1), forceHttp);
   1059                     }
   1060                     monitor.incProgress(1);
   1061                 }
   1062             }
   1063         });
   1064     }
   1065 
   1066     /**
   1067      * Safely invoke all the registered {@link ISdkChangeListener#onSdkLoaded()}.
   1068      * This can be called from any thread.
   1069      */
   1070     public void broadcastOnSdkLoaded() {
   1071         if (mWindowShell != null && !mWindowShell.isDisposed() && mListeners.size() > 0) {
   1072             mWindowShell.getDisplay().syncExec(new Runnable() {
   1073                 @Override
   1074                 public void run() {
   1075                     for (ISdkChangeListener listener : mListeners) {
   1076                         try {
   1077                             listener.onSdkLoaded();
   1078                         } catch (Throwable t) {
   1079                             mSdkLog.error(t, null);
   1080                         }
   1081                     }
   1082                 }
   1083             });
   1084         }
   1085     }
   1086 
   1087     /**
   1088      * Safely invoke all the registered {@link ISdkChangeListener#onSdkReload()}.
   1089      * This can be called from any thread.
   1090      */
   1091     private void broadcastOnSdkReload() {
   1092         if (mWindowShell != null && !mWindowShell.isDisposed() && mListeners.size() > 0) {
   1093             mWindowShell.getDisplay().syncExec(new Runnable() {
   1094                 @Override
   1095                 public void run() {
   1096                     for (ISdkChangeListener listener : mListeners) {
   1097                         try {
   1098                             listener.onSdkReload();
   1099                         } catch (Throwable t) {
   1100                             mSdkLog.error(t, null);
   1101                         }
   1102                     }
   1103                 }
   1104             });
   1105         }
   1106     }
   1107 
   1108     /**
   1109      * Safely invoke all the registered {@link ISdkChangeListener#preInstallHook()}.
   1110      * This can be called from any thread.
   1111      */
   1112     private void broadcastPreInstallHook() {
   1113         if (mWindowShell != null && !mWindowShell.isDisposed() && mListeners.size() > 0) {
   1114             mWindowShell.getDisplay().syncExec(new Runnable() {
   1115                 @Override
   1116                 public void run() {
   1117                     for (ISdkChangeListener listener : mListeners) {
   1118                         try {
   1119                             listener.preInstallHook();
   1120                         } catch (Throwable t) {
   1121                             mSdkLog.error(t, null);
   1122                         }
   1123                     }
   1124                 }
   1125             });
   1126         }
   1127     }
   1128 
   1129     /**
   1130      * Safely invoke all the registered {@link ISdkChangeListener#postInstallHook()}.
   1131      * This can be called from any thread.
   1132      */
   1133     private void broadcastPostInstallHook() {
   1134         if (mWindowShell != null && !mWindowShell.isDisposed() && mListeners.size() > 0) {
   1135             mWindowShell.getDisplay().syncExec(new Runnable() {
   1136                 @Override
   1137                 public void run() {
   1138                     for (ISdkChangeListener listener : mListeners) {
   1139                         try {
   1140                             listener.postInstallHook();
   1141                         } catch (Throwable t) {
   1142                             mSdkLog.error(t, null);
   1143                         }
   1144                     }
   1145                 }
   1146             });
   1147         }
   1148     }
   1149 
   1150     /**
   1151      * Internal helper to return a new {@link ArchiveInstaller}.
   1152      * This allows us to override the installer for unit-testing.
   1153      */
   1154     @VisibleForTesting(visibility=Visibility.PRIVATE)
   1155     protected ArchiveInstaller createArchiveInstaler() {
   1156         return new ArchiveInstaller();
   1157     }
   1158 
   1159 }
   1160