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