• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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