OSDN Git Service

486095ca7191b95b98a814e8a2a8676d88d15afa
[android-x86/sdk.git] / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / repository / UpdaterData.java
1 /*\r
2  * Copyright (C) 2009 The Android Open Source Project\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 package com.android.sdkuilib.internal.repository;\r
18 \r
19 import com.android.prefs.AndroidLocation.AndroidLocationException;\r
20 import com.android.sdklib.ISdkLog;\r
21 import com.android.sdklib.SdkManager;\r
22 import com.android.sdklib.internal.avd.AvdManager;\r
23 import com.android.sdklib.internal.repository.AddonPackage;\r
24 import com.android.sdklib.internal.repository.Archive;\r
25 import com.android.sdklib.internal.repository.ITask;\r
26 import com.android.sdklib.internal.repository.ITaskFactory;\r
27 import com.android.sdklib.internal.repository.ITaskMonitor;\r
28 import com.android.sdklib.internal.repository.LocalSdkParser;\r
29 import com.android.sdklib.internal.repository.Package;\r
30 import com.android.sdklib.internal.repository.RepoSource;\r
31 import com.android.sdklib.internal.repository.RepoSources;\r
32 import com.android.sdklib.internal.repository.ToolPackage;\r
33 import com.android.sdklib.internal.repository.Package.UpdateInfo;\r
34 import com.android.sdkuilib.internal.repository.icons.ImageFactory;\r
35 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;\r
36 \r
37 import org.eclipse.swt.widgets.Shell;\r
38 \r
39 import java.io.ByteArrayOutputStream;\r
40 import java.io.PrintStream;\r
41 import java.util.ArrayList;\r
42 import java.util.Collection;\r
43 import java.util.HashMap;\r
44 import java.util.Map;\r
45 \r
46 /**\r
47  * Data shared between {@link UpdaterWindowImpl} and its pages.\r
48  */\r
49 class UpdaterData {\r
50     private String mOsSdkRoot;\r
51 \r
52     private final ISdkLog mSdkLog;\r
53     private ITaskFactory mTaskFactory;\r
54     private boolean mUserCanChangeSdkRoot;\r
55 \r
56     private SdkManager mSdkManager;\r
57     private AvdManager mAvdManager;\r
58 \r
59     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();\r
60     private final RepoSources mSources = new RepoSources();\r
61 \r
62     private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);\r
63     private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);\r
64 \r
65     private ImageFactory mImageFactory;\r
66 \r
67     private final SettingsController mSettingsController = new SettingsController();\r
68 \r
69     private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();\r
70 \r
71     private Shell mWindowShell;\r
72 \r
73     public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {\r
74         mOsSdkRoot = osSdkRoot;\r
75         mSdkLog = sdkLog;\r
76 \r
77         initSdk();\r
78     }\r
79 \r
80     // ----- getters, setters ----\r
81 \r
82     public void setOsSdkRoot(String osSdkRoot) {\r
83         if (mOsSdkRoot == null || mOsSdkRoot.equals(osSdkRoot) == false) {\r
84             mOsSdkRoot = osSdkRoot;\r
85             initSdk();\r
86         }\r
87     }\r
88 \r
89     public String getOsSdkRoot() {\r
90         return mOsSdkRoot;\r
91     }\r
92 \r
93     public void setTaskFactory(ITaskFactory taskFactory) {\r
94         mTaskFactory = taskFactory;\r
95     }\r
96 \r
97     public ITaskFactory getTaskFactory() {\r
98         return mTaskFactory;\r
99     }\r
100 \r
101     public void setUserCanChangeSdkRoot(boolean userCanChangeSdkRoot) {\r
102         mUserCanChangeSdkRoot = userCanChangeSdkRoot;\r
103     }\r
104 \r
105     public boolean canUserChangeSdkRoot() {\r
106         return mUserCanChangeSdkRoot;\r
107     }\r
108 \r
109     public RepoSources getSources() {\r
110         return mSources;\r
111     }\r
112 \r
113     public RepoSourcesAdapter getSourcesAdapter() {\r
114         return mSourcesAdapter;\r
115     }\r
116 \r
117     public LocalSdkParser getLocalSdkParser() {\r
118         return mLocalSdkParser;\r
119     }\r
120 \r
121     public LocalSdkAdapter getLocalSdkAdapter() {\r
122         return mLocalSdkAdapter;\r
123     }\r
124 \r
125     public ISdkLog getSdkLog() {\r
126         return mSdkLog;\r
127     }\r
128 \r
129     public void setImageFactory(ImageFactory imageFactory) {\r
130         mImageFactory = imageFactory;\r
131     }\r
132 \r
133     public ImageFactory getImageFactory() {\r
134         return mImageFactory;\r
135     }\r
136 \r
137     public SdkManager getSdkManager() {\r
138         return mSdkManager;\r
139     }\r
140 \r
141     public AvdManager getAvdManager() {\r
142         return mAvdManager;\r
143     }\r
144 \r
145     public SettingsController getSettingsController() {\r
146         return mSettingsController;\r
147     }\r
148 \r
149     public void addListeners(ISdkListener listener) {\r
150         if (mListeners.contains(listener) == false) {\r
151             mListeners.add(listener);\r
152         }\r
153     }\r
154 \r
155     public void removeListener(ISdkListener listener) {\r
156         mListeners.remove(listener);\r
157     }\r
158 \r
159     public void setWindowShell(Shell windowShell) {\r
160         mWindowShell = windowShell;\r
161     }\r
162 \r
163     public Shell getWindowShell() {\r
164         return mWindowShell;\r
165     }\r
166 \r
167     // -----\r
168 \r
169     /**\r
170      * Initializes the {@link SdkManager} and the {@link AvdManager}.\r
171      */\r
172     private void initSdk() {\r
173         mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);\r
174         try {\r
175             mAvdManager = null; // remove the old one if needed.\r
176             mAvdManager = new AvdManager(mSdkManager, mSdkLog);\r
177         } catch (AndroidLocationException e) {\r
178             mSdkLog.error(e, "Unable to read AVDs");\r
179         }\r
180 \r
181         // notify adapters/parsers\r
182         // TODO\r
183 \r
184         // notify listeners.\r
185         notifyListeners();\r
186     }\r
187 \r
188     /**\r
189      * Reloads the SDK content (targets).\r
190      * <p/> This also reloads the AVDs in case their status changed.\r
191      * <p/>This does not notify the listeners ({@link ISdkListener}).\r
192      */\r
193     public void reloadSdk() {\r
194         // reload SDK\r
195         mSdkManager.reloadSdk(mSdkLog);\r
196 \r
197         // reload AVDs\r
198         if (mAvdManager != null) {\r
199             try {\r
200                 mAvdManager.reloadAvds(mSdkLog);\r
201             } catch (AndroidLocationException e) {\r
202                 // FIXME\r
203             }\r
204         }\r
205 \r
206         // notify adapters?\r
207         mLocalSdkParser.clearPackages();\r
208         // TODO\r
209 \r
210         // notify listeners\r
211         notifyListeners();\r
212     }\r
213 \r
214     /**\r
215      * Reloads the AVDs.\r
216      * <p/>This does not notify the listeners.\r
217      */\r
218     public void reloadAvds() {\r
219         // reload AVDs\r
220         if (mAvdManager != null) {\r
221             try {\r
222                 mAvdManager.reloadAvds(mSdkLog);\r
223             } catch (AndroidLocationException e) {\r
224                 mSdkLog.error(e, null);\r
225             }\r
226         }\r
227     }\r
228 \r
229     /**\r
230      * Returns the list of installed packages, parsing them if this has not yet been done.\r
231      */\r
232     public Package[] getInstalledPackage() {\r
233         LocalSdkParser parser = getLocalSdkParser();\r
234 \r
235         Package[] packages = parser.getPackages();\r
236 \r
237         if (packages == null) {\r
238             // load on demand the first time\r
239             packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), getSdkLog());\r
240         }\r
241 \r
242         return packages;\r
243     }\r
244 \r
245     /**\r
246      * Notify the listeners ({@link ISdkListener}) that the SDK was reloaded.\r
247      * <p/>This can be called from any thread.\r
248      */\r
249     public void notifyListeners() {\r
250         if (mWindowShell != null && mListeners.size() > 0) {\r
251             mWindowShell.getDisplay().syncExec(new Runnable() {\r
252                 public void run() {\r
253                     for (ISdkListener listener : mListeners) {\r
254                         try {\r
255                             listener.onSdkChange();\r
256                         } catch (Throwable t) {\r
257                             mSdkLog.error(t, null);\r
258                         }\r
259                     }\r
260                 }\r
261             });\r
262         }\r
263     }\r
264 \r
265     /**\r
266      * Install the list of given {@link Archive}s. This is invoked by the user selecting some\r
267      * packages in the remote page and then clicking "install selected".\r
268      *\r
269      * @param archives The archives to install. Incompatible ones will be skipped.\r
270      */\r
271     public void installArchives(final Collection<Archive> archives) {\r
272         if (mTaskFactory == null) {\r
273             throw new IllegalArgumentException("Task Factory is null");\r
274         }\r
275 \r
276         final boolean forceHttp = getSettingsController().getForceHttp();\r
277 \r
278         mTaskFactory.start("Installing Archives", new ITask() {\r
279             public void run(ITaskMonitor monitor) {\r
280 \r
281                 final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;\r
282                 monitor.setProgressMax(archives.size() * progressPerArchive);\r
283                 monitor.setDescription("Preparing to install archives");\r
284 \r
285                 boolean installedAddon = false;\r
286                 boolean installedTools = false;\r
287 \r
288                 int numInstalled = 0;\r
289                 for (Archive archive : archives) {\r
290 \r
291                     int nextProgress = monitor.getProgress() + progressPerArchive;\r
292                     try {\r
293                         if (monitor.isCancelRequested()) {\r
294                             break;\r
295                         }\r
296 \r
297                         if (archive.getParentPackage() instanceof AddonPackage) {\r
298                             installedAddon = true;\r
299                         } else if (archive.getParentPackage() instanceof ToolPackage) {\r
300                             installedTools = true;\r
301                         }\r
302 \r
303                         if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {\r
304                             numInstalled++;\r
305                         }\r
306 \r
307                     } catch (Throwable t) {\r
308                         // Display anything unexpected in the monitor.\r
309                         String msg = t.getMessage();\r
310                         if (msg != null) {\r
311                             monitor.setResult("Unexpected Error installing '%1$s': %2$s",\r
312                                     archive.getParentPackage().getShortDescription(), msg);\r
313                         } else {\r
314                             // no error info? get the stack call to display it\r
315                             // At least that'll give us a better bug report.\r
316                             ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
317                             t.printStackTrace(new PrintStream(baos));\r
318 \r
319                             // and display it\r
320                             monitor.setResult("Unexpected Error installing '%1$s'\n%2$s",\r
321                                     archive.getParentPackage().getShortDescription(),\r
322                                     baos.toString());\r
323                         }\r
324                     } finally {\r
325 \r
326                         // Always move the progress bar to the desired position.\r
327                         // This allows internal methods to not have to care in case\r
328                         // they abort early\r
329                         monitor.incProgress(nextProgress - monitor.getProgress());\r
330                     }\r
331                 }\r
332 \r
333                 if (installedAddon) {\r
334                     // Update the USB vendor ids for adb\r
335                     try {\r
336                         mSdkManager.updateAdb();\r
337                     } catch (Exception e) {\r
338                         mSdkLog.error(e, "Update ADB failed");\r
339                     }\r
340                 }\r
341 \r
342                 if (installedAddon || installedTools) {\r
343                     // We need to restart ADB. Actually since we don't know if it's even\r
344                     // running, maybe we should just kill it and not start it.\r
345                     // Note: it turns out even under Windows we don't need to kill adb\r
346                     // before updating the tools folder, as adb.exe is (surprisingly) not\r
347                     // locked.\r
348 \r
349                     // TODO either bring in ddmlib and use its existing methods to stop adb\r
350                     // or use a shell exec to tools/adb.\r
351                 }\r
352 \r
353                 if (numInstalled == 0) {\r
354                     monitor.setDescription("Done. Nothing was installed.");\r
355                 } else {\r
356                     monitor.setDescription("Done. %1$d %2$s installed.",\r
357                             numInstalled,\r
358                             numInstalled == 1 ? "package" : "packages");\r
359 \r
360                     //notify listeners something was installed, so that they can refresh\r
361                     reloadSdk();\r
362                 }\r
363             }\r
364         });\r
365     }\r
366 \r
367     /**\r
368      * Tries to update all the *existing* local packages.\r
369      * This first refreshes all sources, then compares the available remote packages when\r
370      * the current local ones and suggest updates to be done to the user. Finally all\r
371      * selected updates are installed.\r
372      *\r
373      * @param selectedArchives The list of remote archive to consider for the update.\r
374      *  This can be null, in which case a list of remote archive is fetched from all\r
375      *  available sources.\r
376      */\r
377     public void updateOrInstallAll(Collection<Archive> selectedArchives) {\r
378         if (selectedArchives == null) {\r
379             refreshSources(true);\r
380         }\r
381 \r
382         final Map<Archive, Archive> updates = findUpdates(selectedArchives);\r
383 \r
384         if (selectedArchives != null) {\r
385             // Not only we want to perform updates but we also want to install the\r
386             // selected archives. If they do not match an update, list them anyway\r
387             // except they map themselves to null (no "old" archive)\r
388             for (Archive a : selectedArchives) {\r
389                 if (!updates.containsValue(a)) {\r
390                     updates.put(a, null);\r
391                 }\r
392             }\r
393         }\r
394 \r
395         UpdateChooserDialog dialog = new UpdateChooserDialog(this, updates);\r
396         dialog.open();\r
397 \r
398         Collection<Archive> result = dialog.getResult();\r
399         if (result != null && result.size() > 0) {\r
400             installArchives(result);\r
401         }\r
402     }\r
403 \r
404     /**\r
405      * Refresh all sources. This is invoked either internally (reusing an existing monitor)\r
406      * or as a UI callback on the remote page "Refresh" button (in which case the monitor is\r
407      * null and a new task should be created.)\r
408      *\r
409      * @param forceFetching When true, load sources that haven't been loaded yet.\r
410      *                      When false, only refresh sources that have been loaded yet.\r
411      */\r
412     public void refreshSources(final boolean forceFetching) {\r
413         assert mTaskFactory != null;\r
414 \r
415         final boolean forceHttp = getSettingsController().getForceHttp();\r
416 \r
417         mTaskFactory.start("Refresh Sources",new ITask() {\r
418             public void run(ITaskMonitor monitor) {\r
419                 RepoSource[] sources = mSources.getSources();\r
420                 monitor.setProgressMax(sources.length);\r
421                 for (RepoSource source : sources) {\r
422                     if (forceFetching ||\r
423                             source.getPackages() != null ||\r
424                             source.getFetchError() != null) {\r
425                         source.load(monitor.createSubMonitor(1), forceHttp);\r
426                     }\r
427                     monitor.incProgress(1);\r
428                 }\r
429             }\r
430         });\r
431     }\r
432 \r
433     /**\r
434      * Check the local archives vs the remote available packages to find potential updates.\r
435      * Return a map [remote archive => local archive] of suitable update candidates.\r
436      * Returns null if there's an unexpected error. Otherwise returns a map that can be\r
437      * empty but not null.\r
438      *\r
439      * @param selectedArchives The list of remote archive to consider for the update.\r
440      *  This can be null, in which case a list of remote archive is fetched from all\r
441      *  available sources.\r
442      */\r
443     private Map<Archive, Archive> findUpdates(Collection<Archive> selectedArchives) {\r
444         // Map [remote archive => local archive] of suitable update candidates\r
445         Map<Archive, Archive> result = new HashMap<Archive, Archive>();\r
446 \r
447         // First go thru all sources and make a local list of all available archives\r
448         // sorted by package class.\r
449         HashMap<Class<? extends Package>, ArrayList<Archive>> availPkgs =\r
450             new HashMap<Class<? extends Package>, ArrayList<Archive>>();\r
451 \r
452         if (selectedArchives != null) {\r
453             // Only consider the archives given\r
454 \r
455             for (Archive a : selectedArchives) {\r
456                 // Only add compatible archives\r
457                 if (a.isCompatible()) {\r
458                     Class<? extends Package> clazz = a.getParentPackage().getClass();\r
459 \r
460                     ArrayList<Archive> list = availPkgs.get(clazz);\r
461                     if (list == null) {\r
462                         availPkgs.put(clazz, list = new ArrayList<Archive>());\r
463                     }\r
464 \r
465                     list.add(a);\r
466                 }\r
467             }\r
468 \r
469         } else {\r
470             // Get all the available archives from all loaded sources\r
471             RepoSource[] remoteSources = getSources().getSources();\r
472 \r
473             for (RepoSource remoteSrc : remoteSources) {\r
474                 Package[] remotePkgs = remoteSrc.getPackages();\r
475                 if (remotePkgs != null) {\r
476                     for (Package remotePkg : remotePkgs) {\r
477                         Class<? extends Package> clazz = remotePkg.getClass();\r
478 \r
479                         ArrayList<Archive> list = availPkgs.get(clazz);\r
480                         if (list == null) {\r
481                             availPkgs.put(clazz, list = new ArrayList<Archive>());\r
482                         }\r
483 \r
484                         for (Archive a : remotePkg.getArchives()) {\r
485                             // Only add compatible archives\r
486                             if (a.isCompatible()) {\r
487                                 list.add(a);\r
488                             }\r
489                         }\r
490                     }\r
491                 }\r
492             }\r
493         }\r
494 \r
495         Package[] localPkgs = getLocalSdkParser().getPackages();\r
496         if (localPkgs == null) {\r
497             // This is unexpected. The local sdk parser should have been called first.\r
498             return null;\r
499         }\r
500 \r
501         for (Package localPkg : localPkgs) {\r
502             // get the available archive list for this package type\r
503             ArrayList<Archive> list = availPkgs.get(localPkg.getClass());\r
504 \r
505             // if this list is empty, we'll never find anything that matches\r
506             if (list == null || list.size() == 0) {\r
507                 continue;\r
508             }\r
509 \r
510             // local packages should have one archive at most\r
511             Archive[] localArchives = localPkg.getArchives();\r
512             if (localArchives != null && localArchives.length > 0) {\r
513                 Archive localArchive = localArchives[0];\r
514                 // only consider archive compatible with the current platform\r
515                 if (localArchive != null && localArchive.isCompatible()) {\r
516 \r
517                     // We checked all this archive stuff because that's what eventually gets\r
518                     // installed, but the "update" mechanism really works on packages. So now\r
519                     // the real question: is there a remote package that can update this\r
520                     // local package?\r
521 \r
522                     for (Archive availArchive : list) {\r
523                         UpdateInfo info = localPkg.canBeUpdatedBy(availArchive.getParentPackage());\r
524                         if (info == UpdateInfo.UPDATE) {\r
525                             // Found one!\r
526                             result.put(availArchive, localArchive);\r
527                             break;\r
528                         }\r
529                     }\r
530                 }\r
531             }\r
532         }\r
533 \r
534         return result;\r
535     }\r
536 }\r