OSDN Git Service

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