OSDN Git Service

SDK Manager: correctly handle lack of AvdManager
[android-x86/sdk.git] / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / repository / UpdaterWindowImpl.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 \r
20 import com.android.sdklib.ISdkLog;\r
21 import com.android.sdklib.SdkConstants;\r
22 import com.android.sdklib.internal.repository.RepoSource;\r
23 import com.android.sdklib.internal.repository.RepoSources;\r
24 import com.android.sdklib.repository.SdkRepository;\r
25 import com.android.sdkuilib.internal.repository.icons.ImageFactory;\r
26 import com.android.sdkuilib.internal.tasks.ProgressTaskFactory;\r
27 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;\r
28 \r
29 import org.eclipse.swt.SWT;\r
30 import org.eclipse.swt.custom.SashForm;\r
31 import org.eclipse.swt.custom.StackLayout;\r
32 import org.eclipse.swt.events.DisposeEvent;\r
33 import org.eclipse.swt.events.DisposeListener;\r
34 import org.eclipse.swt.events.SelectionAdapter;\r
35 import org.eclipse.swt.events.SelectionEvent;\r
36 import org.eclipse.swt.graphics.Point;\r
37 import org.eclipse.swt.layout.FillLayout;\r
38 import org.eclipse.swt.widgets.Composite;\r
39 import org.eclipse.swt.widgets.Display;\r
40 import org.eclipse.swt.widgets.List;\r
41 import org.eclipse.swt.widgets.Shell;\r
42 \r
43 import java.lang.reflect.Constructor;\r
44 import java.util.ArrayList;\r
45 \r
46 /**\r
47  * This is the private implementation of the UpdateWindow.\r
48  */\r
49 public class UpdaterWindowImpl {\r
50 \r
51     private final Shell mParentShell;\r
52     /** Internal data shared between the window and its pages. */\r
53     private final UpdaterData mUpdaterData;\r
54     /** The array of pages instances. Only one is visible at a time. */\r
55     private ArrayList<Composite> mPages = new ArrayList<Composite>();\r
56     /** Indicates a page change is due to an internal request. Prevents callbacks from looping. */\r
57     private boolean mInternalPageChange;\r
58     /** A list of extra pages to instantiate. Each entry is an object array with 2 elements:\r
59      *  the string title and the Composite class to instantiate to create the page. */\r
60     private ArrayList<Object[]> mExtraPages;\r
61     /** A factory to create progress task dialogs. */\r
62     private ProgressTaskFactory mTaskFactory;\r
63     /** The initial page to display. If null or not a know class, the first page will be displayed.\r
64      * Must be set before the first call to {@link #open()}. */\r
65     private Class<? extends Composite> mInitialPage;\r
66     /** Sets whether the auto-update wizard will be shown when opening the window. */\r
67     private boolean mRequestAutoUpdate;\r
68 \r
69     // --- UI members ---\r
70 \r
71     protected Shell mAndroidSdkUpdater;\r
72     private SashForm mSashForm;\r
73     private List mPageList;\r
74     private Composite mPagesRootComposite;\r
75     private LocalPackagesPage mLocalPackagePage;\r
76     private RemotePackagesPage mRemotePackagesPage;\r
77     private AvdManagerPage mAvdManagerPage;\r
78     private StackLayout mStackLayout;\r
79 \r
80     /**\r
81      * Creates a new window. Caller must call open(), which will block.\r
82      *\r
83      * @param parentShell Parent shell.\r
84      * @param sdkLog Logger. Cannot be null.\r
85      * @param osSdkRoot The OS path to the SDK root.\r
86      * @param userCanChangeSdkRoot If true, the window lets the user change the SDK path\r
87      *                             being browsed.\r
88      */\r
89     public UpdaterWindowImpl(Shell parentShell, ISdkLog sdkLog, String osSdkRoot,\r
90             boolean userCanChangeSdkRoot) {\r
91         mParentShell = parentShell;\r
92         mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);\r
93         mUpdaterData.setUserCanChangeSdkRoot(userCanChangeSdkRoot);\r
94     }\r
95 \r
96     /**\r
97      * Open the window.\r
98      * @wbp.parser.entryPoint\r
99      */\r
100     public void open() {\r
101         if (mParentShell == null) {\r
102             Display.setAppName("Android"); //$hide$ (hide from SWT designer)\r
103         }\r
104 \r
105         createContents();\r
106         mAndroidSdkUpdater.open();\r
107         mAndroidSdkUpdater.layout();\r
108 \r
109         if (postCreate()) {    //$hide$ (hide from SWT designer)\r
110             Display display = Display.getDefault();\r
111             while (!mAndroidSdkUpdater.isDisposed()) {\r
112                 if (!display.readAndDispatch()) {\r
113                     display.sleep();\r
114                 }\r
115             }\r
116         }\r
117 \r
118         dispose();  //$hide$\r
119     }\r
120 \r
121     /**\r
122      * Create contents of the window.\r
123      */\r
124     protected void createContents() {\r
125         mAndroidSdkUpdater = new Shell(mParentShell, SWT.SHELL_TRIM);\r
126         mAndroidSdkUpdater.addDisposeListener(new DisposeListener() {\r
127             public void widgetDisposed(DisposeEvent e) {\r
128                 onAndroidSdkUpdaterDispose();    //$hide$ (hide from SWT designer)\r
129             }\r
130         });\r
131 \r
132         FillLayout fl;\r
133         mAndroidSdkUpdater.setLayout(fl = new FillLayout(SWT.HORIZONTAL));\r
134         fl.marginHeight = fl.marginWidth = 5;\r
135         mAndroidSdkUpdater.setMinimumSize(new Point(200, 50));\r
136         mAndroidSdkUpdater.setSize(745, 433);\r
137         mAndroidSdkUpdater.setText("Android SDK and AVD Manager");\r
138 \r
139         mSashForm = new SashForm(mAndroidSdkUpdater, SWT.NONE);\r
140 \r
141         mPageList = new List(mSashForm, SWT.BORDER);\r
142         mPageList.addSelectionListener(new SelectionAdapter() {\r
143             @Override\r
144             public void widgetSelected(SelectionEvent e) {\r
145                 onPageListSelected();    //$hide$ (hide from SWT designer)\r
146             }\r
147         });\r
148 \r
149         mPagesRootComposite = new Composite(mSashForm, SWT.NONE);\r
150         mStackLayout = new StackLayout();\r
151         mPagesRootComposite.setLayout(mStackLayout);\r
152 \r
153         mAvdManagerPage = new AvdManagerPage(mPagesRootComposite, mUpdaterData);\r
154         mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);\r
155         mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, mUpdaterData);\r
156 \r
157         mSashForm.setWeights(new int[] {150, 576});\r
158     }\r
159 \r
160     // -- Start of internal part ----------\r
161     // Hide everything down-below from SWT designer\r
162     //$hide>>$\r
163 \r
164     // --- Public API -----------\r
165 \r
166 \r
167     /**\r
168      * Registers an extra page for the updater window.\r
169      * <p/>\r
170      * Pages must derive from {@link Composite} and implement a constructor that takes\r
171      * a single parent {@link Composite} argument.\r
172      * <p/>\r
173      * All pages must be registered before the call to {@link #open()}.\r
174      *\r
175      * @param title The title of the page.\r
176      * @param pageClass The {@link Composite}-derived class that will implement the page.\r
177      */\r
178     public void registerExtraPage(String title, Class<? extends Composite> pageClass) {\r
179         if (mExtraPages == null) {\r
180             mExtraPages = new ArrayList<Object[]>();\r
181         }\r
182         mExtraPages.add(new Object[]{ title, pageClass });\r
183     }\r
184 \r
185     /**\r
186      * Indicate the initial page that should be selected when the window opens.\r
187      * This must be called before the call to {@link #open()}.\r
188      * If null or if the page class is not found, the first page will be selected.\r
189      */\r
190     public void setInitialPage(Class<? extends Composite> pageClass) {\r
191         mInitialPage = pageClass;\r
192     }\r
193 \r
194     /**\r
195      * Sets whether the auto-update wizard will be shown when opening the window.\r
196      * <p/>\r
197      * This must be called before the call to {@link #open()}.\r
198      */\r
199     public void setRequestAutoUpdate(boolean requestAutoUpdate) {\r
200         mRequestAutoUpdate = requestAutoUpdate;\r
201     }\r
202 \r
203     /**\r
204      * Adds a new listener to be notified when a change is made to the content of the SDK.\r
205      */\r
206     public void addListeners(ISdkListener listener) {\r
207         mUpdaterData.addListeners(listener);\r
208     }\r
209 \r
210     /**\r
211      * Removes a new listener to be notified anymore when a change is made to the content of\r
212      * the SDK.\r
213      */\r
214     public void removeListener(ISdkListener listener) {\r
215         mUpdaterData.removeListener(listener);\r
216     }\r
217 \r
218     // --- Internals & UI Callbacks -----------\r
219 \r
220 \r
221     /**\r
222      * Helper to return the SWT shell.\r
223      */\r
224     private Shell getShell() {\r
225         return mAndroidSdkUpdater;\r
226     }\r
227 \r
228     /**\r
229      * Callback called when the window shell is disposed.\r
230      */\r
231     private void onAndroidSdkUpdaterDispose() {\r
232         if (mUpdaterData != null) {\r
233             ImageFactory imgFactory = mUpdaterData.getImageFactory();\r
234             if (imgFactory != null) {\r
235                 imgFactory.dispose();\r
236             }\r
237         }\r
238     }\r
239 \r
240     /**\r
241      * Creates the icon of the window shell.\r
242      */\r
243     private void setWindowImage(Shell androidSdkUpdater) {\r
244         String imageName = "android_icon_16.png"; //$NON-NLS-1$\r
245         if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {\r
246             imageName = "android_icon_128.png"; //$NON-NLS-1$\r
247         }\r
248 \r
249         if (mUpdaterData != null) {\r
250             ImageFactory imgFactory = mUpdaterData.getImageFactory();\r
251             if (imgFactory != null) {\r
252                 mAndroidSdkUpdater.setImage(imgFactory.getImageByName(imageName));\r
253             }\r
254         }\r
255     }\r
256 \r
257     /**\r
258      * Once the UI has been created, initializes the content.\r
259      * This creates the pages, selects the first one, setup sources and scan for local folders.\r
260      *\r
261      * Returns true if we should show the window.\r
262      */\r
263     private boolean postCreate() {\r
264         mUpdaterData.setWindowShell(getShell());\r
265         mTaskFactory = new ProgressTaskFactory(getShell());\r
266         mUpdaterData.setTaskFactory(mTaskFactory);\r
267         mUpdaterData.setImageFactory(new ImageFactory(getShell().getDisplay()));\r
268 \r
269         setWindowImage(mAndroidSdkUpdater);\r
270 \r
271         addPage(mAvdManagerPage,     "Virtual Devices");\r
272         addPage(mLocalPackagePage,   "Installed Packages");\r
273         addPage(mRemotePackagesPage, "Available Packages");\r
274         addExtraPages();\r
275 \r
276         int pageIndex = 0;\r
277         int i = 0;\r
278         for (Composite p : mPages) {\r
279             if (p.getClass().equals(mInitialPage)) {\r
280                 pageIndex = i;\r
281                 break;\r
282             }\r
283             i++;\r
284         }\r
285         displayPage(pageIndex);\r
286         mPageList.setSelection(pageIndex);\r
287 \r
288         setupSources();\r
289         initializeSettings();\r
290 \r
291         if (mUpdaterData.checkIfInitFailed()) {\r
292             return false;\r
293         }\r
294 \r
295         mUpdaterData.notifyListeners(true /*init*/);\r
296 \r
297         if (mRequestAutoUpdate) {\r
298             mUpdaterData.updateOrInstallAll(null /*selectedArchives*/);\r
299         }\r
300 \r
301         return true;\r
302     }\r
303 \r
304     /**\r
305      * Called by the main loop when the window has been disposed.\r
306      */\r
307     private void dispose() {\r
308         mUpdaterData.getSources().saveUserSources(mUpdaterData.getSdkLog());\r
309     }\r
310 \r
311     // --- page switching ---\r
312 \r
313     /**\r
314      * Adds an instance of a page to the page list.\r
315      * <p/>\r
316      * Each page is a {@link Composite}. The title of the page is stored in the\r
317      * {@link Composite#getData()} field.\r
318      */\r
319     private void addPage(Composite page, String title) {\r
320         page.setData(title);\r
321         mPages.add(page);\r
322         mPageList.add(title);\r
323     }\r
324 \r
325     /**\r
326      * Adds all extra pages. For each page, instantiates an instance of the {@link Composite}\r
327      * using the constructor that takes a single {@link Composite} argument and then adds it\r
328      * to the page list.\r
329      */\r
330     @SuppressWarnings("unchecked")\r
331     private void addExtraPages() {\r
332         if (mExtraPages == null) {\r
333             return;\r
334         }\r
335 \r
336         for (Object[] extraPage : mExtraPages) {\r
337             String title = (String) extraPage[0];\r
338             Class<? extends Composite> clazz = (Class<? extends Composite>) extraPage[1];\r
339 \r
340             // We want the constructor that takes a single Composite as parameter\r
341             Constructor<? extends Composite> cons;\r
342             try {\r
343                 cons = clazz.getConstructor(new Class<?>[] { Composite.class });\r
344                 Composite instance = cons.newInstance(new Object[] { mPagesRootComposite });\r
345                 addPage(instance, title);\r
346 \r
347             } catch (NoSuchMethodException e) {\r
348                 // There is no such constructor.\r
349                 mUpdaterData.getSdkLog().error(e,\r
350                         "Failed to add extra page %1$s. Constructor args must be (Composite parent).",  //$NON-NLS-1$\r
351                         clazz.getSimpleName());\r
352 \r
353             } catch (Exception e) {\r
354                 // Log this instead of crashing the whole app.\r
355                 mUpdaterData.getSdkLog().error(e,\r
356                         "Failed to add extra page %1$s.",  //$NON-NLS-1$\r
357                         clazz.getSimpleName());\r
358             }\r
359         }\r
360     }\r
361 \r
362     /**\r
363      * Callback invoked when an item is selected in the page list.\r
364      * If this is not an internal page change, displays the given page.\r
365      */\r
366     private void onPageListSelected() {\r
367         if (mInternalPageChange == false) {\r
368             int index = mPageList.getSelectionIndex();\r
369             if (index >= 0) {\r
370                 displayPage(index);\r
371             }\r
372         }\r
373     }\r
374 \r
375     /**\r
376      * Displays the page at the given index.\r
377      *\r
378      * @param index An index between 0 and {@link #mPages}'s length - 1.\r
379      */\r
380     private void displayPage(int index) {\r
381         Composite page = mPages.get(index);\r
382         if (page != null) {\r
383             mStackLayout.topControl = page;\r
384             mPagesRootComposite.layout(true);\r
385 \r
386             if (!mInternalPageChange) {\r
387                 mInternalPageChange = true;\r
388                 mPageList.setSelection(index);\r
389                 mInternalPageChange = false;\r
390             }\r
391         }\r
392     }\r
393 \r
394     /**\r
395      * Used to initialize the sources.\r
396      */\r
397     private void setupSources() {\r
398         RepoSources sources = mUpdaterData.getSources();\r
399         sources.add(new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /*userSource*/));\r
400 \r
401         // SDK_UPDATER_URLS is a semicolon-separated list of URLs that can be used to\r
402         // seed the SDK Updater list for full repositories.\r
403         String str = System.getenv("SDK_UPDATER_URLS");\r
404         if (str != null) {\r
405             String[] urls = str.split(";");\r
406             for (String url : urls) {\r
407                 if (url != null && url.length() > 0) {\r
408                     RepoSource s = new RepoSource(url, false /*userSource*/);\r
409                     if (!sources.hasSource(s)) {\r
410                         sources.add(s);\r
411                     }\r
412                 }\r
413             }\r
414         }\r
415 \r
416         // Load user sources\r
417         sources.loadUserSources(mUpdaterData.getSdkLog());\r
418 \r
419         // SDK_UPDATER_USER_URLS is a semicolon-separated list of URLs that can be used to\r
420         // seed the SDK Updater list for user-only repositories. User sources can only provide\r
421         // add-ons and extra packages.\r
422         str = System.getenv("SDK_UPDATER_USER_URLS");\r
423         if (str != null) {\r
424             String[] urls = str.split(";");\r
425             for (String url : urls) {\r
426                 if (url != null && url.length() > 0) {\r
427                     RepoSource s = new RepoSource(url, true /*userSource*/);\r
428                     if (!sources.hasSource(s)) {\r
429                         sources.add(s);\r
430                     }\r
431                 }\r
432             }\r
433         }\r
434 \r
435         mRemotePackagesPage.onSdkChange(false /*init*/);\r
436     }\r
437 \r
438     /**\r
439      * Initializes settings.\r
440      * This must be called after addExtraPages(), which created a settings page.\r
441      * Iterate through all the pages to find the first (and supposedly unique) setting page,\r
442      * and use it to load and apply these settings.\r
443      */\r
444     private void initializeSettings() {\r
445         SettingsController c = mUpdaterData.getSettingsController();\r
446         c.loadSettings();\r
447         c.applySettings();\r
448 \r
449         for (Object page : mPages) {\r
450             if (page instanceof ISettingsPage) {\r
451                 ISettingsPage settingsPage = (ISettingsPage) page;\r
452 \r
453                 c.setSettingsPage(settingsPage);\r
454                 break;\r
455             }\r
456         }\r
457     }\r
458 \r
459     // End of hiding from SWT Designer\r
460     //$hide<<$\r
461 }\r