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.ITask;
     27 import com.android.sdklib.internal.repository.ITaskFactory;
     28 import com.android.sdklib.internal.repository.ITaskMonitor;
     29 import com.android.sdklib.internal.repository.LocalSdkParser;
     30 import com.android.sdklib.internal.repository.Package;
     31 import com.android.sdklib.internal.repository.RepoSource;
     32 import com.android.sdklib.internal.repository.RepoSources;
     33 import com.android.sdklib.internal.repository.ToolPackage;
     34 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     35 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
     36 
     37 import org.eclipse.jface.dialogs.MessageDialog;
     38 import org.eclipse.swt.widgets.Display;
     39 import org.eclipse.swt.widgets.Shell;
     40 
     41 import java.io.ByteArrayOutputStream;
     42 import java.io.PrintStream;
     43 import java.util.ArrayList;
     44 import java.util.Collection;
     45 import java.util.HashSet;
     46 
     47 /**
     48  * Data shared between {@link UpdaterWindowImpl} and its pages.
     49  */
     50 class UpdaterData {
     51     private String mOsSdkRoot;
     52 
     53     private final ISdkLog mSdkLog;
     54     private ITaskFactory mTaskFactory;
     55     private boolean mUserCanChangeSdkRoot;
     56 
     57     private SdkManager mSdkManager;
     58     private AvdManager mAvdManager;
     59 
     60     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
     61     private final RepoSources mSources = new RepoSources();
     62 
     63     private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);
     64     private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);
     65 
     66     private ImageFactory mImageFactory;
     67 
     68     private final SettingsController mSettingsController;
     69 
     70     private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();
     71 
     72     private Shell mWindowShell;
     73 
     74     private AndroidLocationException mAvdManagerInitError;
     75 
     76     /**
     77      * Creates a new updater data.
     78      *
     79      * @param sdkLog Logger. Cannot be null.
     80      * @param osSdkRoot The OS path to the SDK root.
     81      */
     82     public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {
     83         mOsSdkRoot = osSdkRoot;
     84         mSdkLog = sdkLog;
     85 
     86         mSettingsController = new SettingsController(this);
     87 
     88         initSdk();
     89     }
     90 
     91     // ----- getters, setters ----
     92 
     93     public String getOsSdkRoot() {
     94         return mOsSdkRoot;
     95     }
     96 
     97     public void setTaskFactory(ITaskFactory taskFactory) {
     98         mTaskFactory = taskFactory;
     99     }
    100 
    101     public ITaskFactory getTaskFactory() {
    102         return mTaskFactory;
    103     }
    104 
    105     public void setUserCanChangeSdkRoot(boolean userCanChangeSdkRoot) {
    106         mUserCanChangeSdkRoot = userCanChangeSdkRoot;
    107     }
    108 
    109     public boolean canUserChangeSdkRoot() {
    110         return mUserCanChangeSdkRoot;
    111     }
    112 
    113     public RepoSources getSources() {
    114         return mSources;
    115     }
    116 
    117     public RepoSourcesAdapter getSourcesAdapter() {
    118         return mSourcesAdapter;
    119     }
    120 
    121     public LocalSdkParser getLocalSdkParser() {
    122         return mLocalSdkParser;
    123     }
    124 
    125     public LocalSdkAdapter getLocalSdkAdapter() {
    126         return mLocalSdkAdapter;
    127     }
    128 
    129     public ISdkLog getSdkLog() {
    130         return mSdkLog;
    131     }
    132 
    133     public void setImageFactory(ImageFactory imageFactory) {
    134         mImageFactory = imageFactory;
    135     }
    136 
    137     public ImageFactory getImageFactory() {
    138         return mImageFactory;
    139     }
    140 
    141     public SdkManager getSdkManager() {
    142         return mSdkManager;
    143     }
    144 
    145     public AvdManager getAvdManager() {
    146         return mAvdManager;
    147     }
    148 
    149     public SettingsController getSettingsController() {
    150         return mSettingsController;
    151     }
    152 
    153     /** Adds a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
    154     public void addListeners(ISdkListener listener) {
    155         if (mListeners.contains(listener) == false) {
    156             mListeners.add(listener);
    157         }
    158     }
    159 
    160     /** Removes a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
    161     public void removeListener(ISdkListener listener) {
    162         mListeners.remove(listener);
    163     }
    164 
    165     public void setWindowShell(Shell windowShell) {
    166         mWindowShell = windowShell;
    167     }
    168 
    169     public Shell getWindowShell() {
    170         return mWindowShell;
    171     }
    172 
    173     /**
    174      * Check if any error occurred during initialization.
    175      * If it did, display an error message.
    176      *
    177      * @return True if an error occurred, false if we should continue.
    178      */
    179     public boolean checkIfInitFailed() {
    180         if (mAvdManagerInitError != null) {
    181             String example;
    182             if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
    183                 example = "%USERPROFILE%";     //$NON-NLS-1$
    184             } else {
    185                 example = "~";                 //$NON-NLS-1$
    186             }
    187 
    188             MessageDialog.openError(mWindowShell,
    189                 "Android Virtual Devices Manager",
    190                 String.format(
    191                     "The AVD manager normally uses the user's profile directory to store " +
    192                     "AVD files. However it failed to find the default profile directory. " +
    193                     "\n" +
    194                     "To fix this, please set the environment variable ANDROID_SDK_HOME to " +
    195                     "a valid path such as \"%s\".",
    196                     example));
    197 
    198             return true;
    199         }
    200         return false;
    201     }
    202 
    203     // -----
    204 
    205     /**
    206      * Initializes the {@link SdkManager} and the {@link AvdManager}.
    207      */
    208     private void initSdk() {
    209         mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
    210         try {
    211             mAvdManager = null; // remove the old one if needed.
    212             mAvdManager = new AvdManager(mSdkManager, mSdkLog);
    213         } catch (AndroidLocationException e) {
    214             mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage());  //$NON-NLS-1$
    215 
    216             // Note: we used to continue here, but the thing is that
    217             // mAvdManager==null so nothing is really going to work as
    218             // expected. Let's just display an error later in checkIfInitFailed()
    219             // and abort right there. This step is just too early in the SWT
    220             // setup process to display a message box yet.
    221 
    222             mAvdManagerInitError = e;
    223         }
    224 
    225         // notify adapters/parsers
    226         // TODO
    227 
    228         // notify listeners.
    229         notifyListeners(false /*init*/);
    230     }
    231 
    232     /**
    233      * Reloads the SDK content (targets).
    234      * <p/> This also reloads the AVDs in case their status changed.
    235      * <p/>This does not notify the listeners ({@link ISdkListener}).
    236      */
    237     public void reloadSdk() {
    238         // reload SDK
    239         mSdkManager.reloadSdk(mSdkLog);
    240 
    241         // reload AVDs
    242         if (mAvdManager != null) {
    243             try {
    244                 mAvdManager.reloadAvds(mSdkLog);
    245             } catch (AndroidLocationException e) {
    246                 // FIXME
    247             }
    248         }
    249 
    250         // notify adapters?
    251         mLocalSdkParser.clearPackages();
    252         // TODO
    253 
    254         // notify listeners
    255         notifyListeners(false /*init*/);
    256     }
    257 
    258     /**
    259      * Reloads the AVDs.
    260      * <p/>This does not notify the listeners.
    261      */
    262     public void reloadAvds() {
    263         // reload AVDs
    264         if (mAvdManager != null) {
    265             try {
    266                 mAvdManager.reloadAvds(mSdkLog);
    267             } catch (AndroidLocationException e) {
    268                 mSdkLog.error(e, null);
    269             }
    270         }
    271     }
    272 
    273     /**
    274      * Returns the list of installed packages, parsing them if this has not yet been done.
    275      */
    276     public Package[] getInstalledPackage() {
    277         LocalSdkParser parser = getLocalSdkParser();
    278 
    279         Package[] packages = parser.getPackages();
    280 
    281         if (packages == null) {
    282             // load on demand the first time
    283             packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), getSdkLog());
    284         }
    285 
    286         return packages;
    287     }
    288 
    289     /**
    290      * Notify the listeners ({@link ISdkListener}) that the SDK was reloaded.
    291      * <p/>This can be called from any thread.
    292      * @param init whether the SDK loaded for the first time.
    293      */
    294     public void notifyListeners(final boolean init) {
    295         if (mWindowShell != null && mListeners.size() > 0) {
    296             mWindowShell.getDisplay().syncExec(new Runnable() {
    297                 public void run() {
    298                     for (ISdkListener listener : mListeners) {
    299                         try {
    300                             listener.onSdkChange(init);
    301                         } catch (Throwable t) {
    302                             mSdkLog.error(t, null);
    303                         }
    304                     }
    305                 }
    306             });
    307         }
    308     }
    309 
    310     /**
    311      * Install the list of given {@link Archive}s. This is invoked by the user selecting some
    312      * packages in the remote page and then clicking "install selected".
    313      *
    314      * @param result The archives to install. Incompatible ones will be skipped.
    315      */
    316     public void installArchives(final ArrayList<ArchiveInfo> result) {
    317         if (mTaskFactory == null) {
    318             throw new IllegalArgumentException("Task Factory is null");
    319         }
    320 
    321         final boolean forceHttp = getSettingsController().getForceHttp();
    322 
    323         mTaskFactory.start("Installing Archives", new ITask() {
    324             public void run(ITaskMonitor monitor) {
    325 
    326                 final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
    327                 monitor.setProgressMax(result.size() * progressPerArchive);
    328                 monitor.setDescription("Preparing to install archives");
    329 
    330                 boolean installedAddon = false;
    331                 boolean installedTools = false;
    332 
    333                 // Mark all current local archives as already installed.
    334                 HashSet<Archive> installedArchives = new HashSet<Archive>();
    335                 for (Package p : getInstalledPackage()) {
    336                     for (Archive a : p.getArchives()) {
    337                         installedArchives.add(a);
    338                     }
    339                 }
    340 
    341                 int numInstalled = 0;
    342                 nextArchive: for (ArchiveInfo ai : result) {
    343                     Archive archive = ai.getNewArchive();
    344                     if (archive == null) {
    345                         // This is not supposed to happen.
    346                         continue nextArchive;
    347                     }
    348 
    349                     int nextProgress = monitor.getProgress() + progressPerArchive;
    350                     try {
    351                         if (monitor.isCancelRequested()) {
    352                             break;
    353                         }
    354 
    355                         ArchiveInfo[] adeps = ai.getDependsOn();
    356                         if (adeps != null) {
    357                             for (ArchiveInfo adep : adeps) {
    358                                 Archive na = adep.getNewArchive();
    359                                 if (na == null) {
    360                                     // This archive depends on a missing archive.
    361                                     // We shouldn't get here.
    362                                     // Skip it.
    363                                     monitor.setResult("Skipping '%1$s'; it depends on a missing package.",
    364                                             archive.getParentPackage().getShortDescription());
    365                                     continue nextArchive;
    366                                 } else if (!installedArchives.contains(na)) {
    367                                     // This archive depends on another one that was not installed.
    368                                     // We shouldn't get here.
    369                                     // Skip it.
    370                                     monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
    371                                             archive.getParentPackage().getShortDescription(),
    372                                             adep.getShortDescription());
    373                                     continue nextArchive;
    374                                 }
    375                             }
    376                         }
    377 
    378                         if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
    379                             // We installed this archive.
    380                             installedArchives.add(archive);
    381                             numInstalled++;
    382 
    383                             // If this package was replacing an existing one, the old one
    384                             // is no longer installed.
    385                             installedArchives.remove(ai.getReplaced());
    386 
    387                             // Check if we successfully installed a tool or add-on package.
    388                             if (archive.getParentPackage() instanceof AddonPackage) {
    389                                 installedAddon = true;
    390                             } else if (archive.getParentPackage() instanceof ToolPackage) {
    391                                 installedTools = true;
    392                             }
    393                         }
    394 
    395                     } catch (Throwable t) {
    396                         // Display anything unexpected in the monitor.
    397                         String msg = t.getMessage();
    398                         if (msg != null) {
    399                             monitor.setResult("Unexpected Error installing '%1$s': %2$s",
    400                                     archive.getParentPackage().getShortDescription(), msg);
    401                         } else {
    402                             // no error info? get the stack call to display it
    403                             // At least that'll give us a better bug report.
    404                             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    405                             t.printStackTrace(new PrintStream(baos));
    406 
    407                             // and display it
    408                             monitor.setResult("Unexpected Error installing '%1$s'\n%2$s",
    409                                     archive.getParentPackage().getShortDescription(),
    410                                     baos.toString());
    411                         }
    412                     } finally {
    413 
    414                         // Always move the progress bar to the desired position.
    415                         // This allows internal methods to not have to care in case
    416                         // they abort early
    417                         monitor.incProgress(nextProgress - monitor.getProgress());
    418                     }
    419                 }
    420 
    421                 if (installedAddon) {
    422                     // Update the USB vendor ids for adb
    423                     try {
    424                         mSdkManager.updateAdb();
    425                         monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons.");
    426                     } catch (Exception e) {
    427                         mSdkLog.error(e, "Update ADB failed");
    428                         monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons.");
    429                     }
    430                 }
    431 
    432                 if (installedAddon || installedTools) {
    433                     // We need to restart ADB. Actually since we don't know if it's even
    434                     // running, maybe we should just kill it and not start it.
    435                     // Note: it turns out even under Windows we don't need to kill adb
    436                     // before updating the tools folder, as adb.exe is (surprisingly) not
    437                     // locked.
    438 
    439                     askForAdbRestart(monitor);
    440                 }
    441 
    442                 if (installedTools) {
    443                     notifyToolsNeedsToBeRestarted();
    444                 }
    445 
    446                 if (numInstalled == 0) {
    447                     monitor.setDescription("Done. Nothing was installed.");
    448                 } else {
    449                     monitor.setDescription("Done. %1$d %2$s installed.",
    450                             numInstalled,
    451                             numInstalled == 1 ? "package" : "packages");
    452 
    453                     //notify listeners something was installed, so that they can refresh
    454                     reloadSdk();
    455                 }
    456             }
    457         });
    458     }
    459 
    460     /**
    461      * Attemps to restart ADB.
    462      *
    463      * If the "ask before restart" setting is set (the default), prompt the user whether
    464      * now is a good time to restart ADB.
    465      * @param monitor
    466      */
    467     private void askForAdbRestart(ITaskMonitor monitor) {
    468         final boolean[] canRestart = new boolean[] { true };
    469 
    470         if (getSettingsController().getAskBeforeAdbRestart()) {
    471             // need to ask for permission first
    472             Display display = mWindowShell.getDisplay();
    473 
    474             display.syncExec(new Runnable() {
    475                 public void run() {
    476                     canRestart[0] = MessageDialog.openQuestion(mWindowShell,
    477                             "ADB Restart",
    478                             "A package that depends on ADB has been updated. It is recommended " +
    479                             "to restart ADB. Is it OK to do it now? If not, you can restart it " +
    480                             "manually later.");
    481                 }
    482             });
    483         }
    484 
    485         if (canRestart[0]) {
    486             AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
    487             adb.stopAdb();
    488             adb.startAdb();
    489         }
    490     }
    491 
    492     private void notifyToolsNeedsToBeRestarted() {
    493         Display display = mWindowShell.getDisplay();
    494 
    495         display.syncExec(new Runnable() {
    496             public void run() {
    497                 MessageDialog.openInformation(mWindowShell,
    498                         "Android Tools Updated",
    499                         "The Android SDK and AVD Manager that you are currently using has been updated. " +
    500                         "It is recommended that you now close the manager window and re-open it. " +
    501                         "If you started this window from Eclipse, please check if the Android " +
    502                         "plug-in needs to be updated.");
    503             }
    504         });
    505     }
    506 
    507 
    508     /**
    509      * Tries to update all the *existing* local packages.
    510      * <p/>
    511      * There are two modes of operation:
    512      * <ul>
    513      * <li>If selectedArchives is null, refreshes all sources, compares the available remote
    514      * packages with the current local ones and suggest updates to be done to the user (including
    515      * new platforms that the users doesn't have yet).
    516      * <li>If selectedArchives is not null, this represents a list of archives/packages that
    517      * the user wants to install or update, so just process these.
    518      * </ul>
    519      *
    520      * @param selectedArchives The list of remote archive to consider for the update.
    521      *  This can be null, in which case a list of remote archive is fetched from all
    522      *  available sources.
    523      */
    524     public void updateOrInstallAll(Collection<Archive> selectedArchives) {
    525         if (selectedArchives == null) {
    526             refreshSources(true);
    527         }
    528 
    529         UpdaterLogic ul = new UpdaterLogic();
    530         ArrayList<ArchiveInfo> archives = ul.computeUpdates(
    531                 selectedArchives,
    532                 getSources(),
    533                 getLocalSdkParser().getPackages());
    534 
    535         if (selectedArchives == null) {
    536             ul.addNewPlatforms(archives, getSources(), getLocalSdkParser().getPackages());
    537         }
    538 
    539         // TODO if selectedArchives is null and archives.len==0, find if there's
    540         // any new platform we can suggest to install instead.
    541 
    542         UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, archives);
    543         dialog.open();
    544 
    545         ArrayList<ArchiveInfo> result = dialog.getResult();
    546         if (result != null && result.size() > 0) {
    547             installArchives(result);
    548         }
    549     }
    550     /**
    551      * Refresh all sources. This is invoked either internally (reusing an existing monitor)
    552      * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
    553      * null and a new task should be created.)
    554      *
    555      * @param forceFetching When true, load sources that haven't been loaded yet.
    556      *                      When false, only refresh sources that have been loaded yet.
    557      */
    558     public void refreshSources(final boolean forceFetching) {
    559         assert mTaskFactory != null;
    560 
    561         final boolean forceHttp = getSettingsController().getForceHttp();
    562 
    563         mTaskFactory.start("Refresh Sources",new ITask() {
    564             public void run(ITaskMonitor monitor) {
    565                 RepoSource[] sources = mSources.getSources();
    566                 monitor.setProgressMax(sources.length);
    567                 for (RepoSource source : sources) {
    568                     if (forceFetching ||
    569                             source.getPackages() != null ||
    570                             source.getFetchError() != null) {
    571                         source.load(monitor.createSubMonitor(1), forceHttp);
    572                     }
    573                     monitor.incProgress(1);
    574                 }
    575             }
    576         });
    577     }
    578 }
    579