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