2 * Copyright (C) 2011 The Android Open Source Project
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
17 package com.android.sdkuilib.internal.repository;
\r
20 import com.android.sdklib.ISdkLog;
\r
21 import com.android.sdklib.SdkConstants;
\r
22 import com.android.sdklib.internal.repository.ITaskFactory;
\r
23 import com.android.sdkuilib.internal.repository.PackagesPage.MenuAction;
\r
24 import com.android.sdkuilib.internal.repository.UpdaterPage.Purpose;
\r
25 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
\r
26 import com.android.sdkuilib.internal.tasks.ProgressView;
\r
27 import com.android.sdkuilib.internal.tasks.ProgressViewFactory;
\r
28 import com.android.sdkuilib.internal.widgets.ImgDisabledButton;
\r
29 import com.android.sdkuilib.internal.widgets.ToggleButton;
\r
30 import com.android.sdkuilib.repository.ISdkChangeListener;
\r
31 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
\r
32 import com.android.sdkuilib.ui.GridDataBuilder;
\r
33 import com.android.sdkuilib.ui.GridLayoutBuilder;
\r
34 import com.android.sdkuilib.ui.SwtBaseDialog;
\r
35 import com.android.util.Pair;
\r
37 import org.eclipse.swt.SWT;
\r
38 import org.eclipse.swt.events.DisposeEvent;
\r
39 import org.eclipse.swt.events.DisposeListener;
\r
40 import org.eclipse.swt.events.SelectionAdapter;
\r
41 import org.eclipse.swt.events.SelectionEvent;
\r
42 import org.eclipse.swt.graphics.Image;
\r
43 import org.eclipse.swt.graphics.Point;
\r
44 import org.eclipse.swt.layout.GridData;
\r
45 import org.eclipse.swt.layout.GridLayout;
\r
46 import org.eclipse.swt.widgets.Button;
\r
47 import org.eclipse.swt.widgets.Composite;
\r
48 import org.eclipse.swt.widgets.Display;
\r
49 import org.eclipse.swt.widgets.Event;
\r
50 import org.eclipse.swt.widgets.Label;
\r
51 import org.eclipse.swt.widgets.Listener;
\r
52 import org.eclipse.swt.widgets.Menu;
\r
53 import org.eclipse.swt.widgets.MenuItem;
\r
54 import org.eclipse.swt.widgets.ProgressBar;
\r
55 import org.eclipse.swt.widgets.Shell;
\r
57 import java.util.ArrayList;
\r
60 * This is the private implementation of the UpdateWindow
\r
61 * for the second version of the SDK Manager.
\r
63 * This window features only one embedded page, the combined installed+available package list.
\r
65 public class SdkUpdaterWindowImpl2 implements ISdkUpdaterWindow {
\r
67 private static final String APP_NAME = "Android SDK Manager";
\r
68 private final Shell mParentShell;
\r
69 private final SdkInvocationContext mContext;
\r
70 /** Internal data shared between the window and its pages. */
\r
71 private final UpdaterData mUpdaterData;
\r
72 /** A list of extra pages to instantiate. Each entry is an object array with 2 elements:
\r
73 * the string title and the Composite class to instantiate to create the page. */
\r
74 private ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>> mExtraPages;
\r
75 /** Sets whether the auto-update wizard will be shown when opening the window. */
\r
76 private boolean mRequestAutoUpdate;
\r
78 // --- UI members ---
\r
80 protected Shell mShell;
\r
81 private PackagesPage mPkgPage;
\r
82 private ProgressBar mProgressBar;
\r
83 private Label mStatusText;
\r
84 private ImgDisabledButton mButtonStop;
\r
85 private ToggleButton mButtonDetails;
\r
86 private SettingsController mSettingsController;
\r
89 * Creates a new window. Caller must call open(), which will block.
\r
91 * @param parentShell Parent shell.
\r
92 * @param sdkLog Logger. Cannot be null.
\r
93 * @param osSdkRoot The OS path to the SDK root.
\r
94 * @param context The {@link SdkInvocationContext} to change the behavior depending on who's
\r
95 * opening the SDK Manager.
\r
97 public SdkUpdaterWindowImpl2(
\r
101 SdkInvocationContext context) {
\r
102 mParentShell = parentShell;
\r
103 mContext = context;
\r
104 mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
\r
108 * Creates a new window. Caller must call open(), which will block.
\r
110 * This is to be used when the window is opened from {@link AvdManagerWindowImpl1}
\r
111 * to share the same {@link UpdaterData} structure.
\r
113 * @param parentShell Parent shell.
\r
114 * @param updaterData The parent's updater data.
\r
115 * @param context The {@link SdkInvocationContext} to change the behavior depending on who's
\r
116 * opening the SDK Manager.
\r
118 public SdkUpdaterWindowImpl2(
\r
120 UpdaterData updaterData,
\r
121 SdkInvocationContext context) {
\r
122 mParentShell = parentShell;
\r
123 mContext = context;
\r
124 mUpdaterData = updaterData;
\r
128 * Opens the window.
\r
129 * @wbp.parser.entryPoint
\r
131 public void open() {
\r
132 if (mParentShell == null) {
\r
133 Display.setAppName(APP_NAME); //$hide$ (hide from SWT designer)
\r
137 preCreateContent();
\r
143 if (postCreateContent()) { //$hide$ (hide from SWT designer)
\r
144 Display display = Display.getDefault();
\r
145 while (!mShell.isDisposed()) {
\r
146 if (!display.readAndDispatch()) {
\r
152 dispose(); //$hide$
\r
155 private void createShell() {
\r
156 mShell = new Shell(mParentShell, SWT.SHELL_TRIM);
\r
157 mShell.addDisposeListener(new DisposeListener() {
\r
158 public void widgetDisposed(DisposeEvent e) {
\r
159 onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer)
\r
163 GridLayout glShell = new GridLayout(2, false);
\r
164 glShell.verticalSpacing = 0;
\r
165 glShell.horizontalSpacing = 0;
\r
166 glShell.marginWidth = 0;
\r
167 glShell.marginHeight = 0;
\r
168 mShell.setLayout(glShell);
\r
170 mShell.setMinimumSize(new Point(500, 300));
\r
171 mShell.setSize(700, 500);
\r
172 mShell.setText(APP_NAME);
\r
175 private void createContents() {
\r
177 mPkgPage = new PackagesPage(mShell, SWT.NONE, mUpdaterData);
\r
178 mPkgPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
\r
180 Composite composite1 = new Composite(mShell, SWT.NONE);
\r
181 composite1.setLayout(new GridLayout(1, false));
\r
182 composite1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
\r
184 mProgressBar = new ProgressBar(composite1, SWT.NONE);
\r
185 mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
\r
187 mStatusText = new Label(composite1, SWT.NONE);
\r
188 mStatusText.setText("Status Placeholder"); //$NON-NLS-1$ placeholder
\r
189 mStatusText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
\r
191 Composite composite2 = new Composite(mShell, SWT.NONE);
\r
192 composite2.setLayout(new GridLayout(2, false));
\r
194 mButtonStop = new ImgDisabledButton(composite2, SWT.NONE,
\r
195 getImage("stop_enabled_16.png"), //$NON-NLS-1$
\r
196 getImage("stop_disabled_16.png")); //$NON-NLS-1$
\r
197 mButtonStop.addListener(SWT.Selection, new Listener() {
\r
198 public void handleEvent(Event event) {
\r
203 mButtonDetails = new ToggleButton(composite2, SWT.NONE,
\r
204 getImage("collapsed_16.png"), //$NON-NLS-1$
\r
205 getImage("expanded_16.png")); //$NON-NLS-1$
\r
206 mButtonDetails.addListener(SWT.Selection, new Listener() {
\r
207 public void handleEvent(Event event) {
\r
213 private void createMenuBar() {
\r
215 Menu menuBar = new Menu(mShell, SWT.BAR);
\r
216 mShell.setMenuBar(menuBar);
\r
218 MenuItem menuBarPackages = new MenuItem(menuBar, SWT.CASCADE);
\r
219 menuBarPackages.setText("Packages");
\r
221 Menu menuPkgs = new Menu(menuBarPackages);
\r
222 menuBarPackages.setMenu(menuPkgs);
\r
224 MenuItem showUpdatesNew = new MenuItem(menuPkgs,
\r
225 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuStyle());
\r
226 showUpdatesNew.setText(
\r
227 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuTitle());
\r
228 mPkgPage.registerMenuAction(
\r
229 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG, showUpdatesNew);
\r
231 MenuItem showInstalled = new MenuItem(menuPkgs,
\r
232 MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuStyle());
\r
233 showInstalled.setText(
\r
234 MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuTitle());
\r
235 mPkgPage.registerMenuAction(
\r
236 MenuAction.TOGGLE_SHOW_INSTALLED_PKG, showInstalled);
\r
238 MenuItem showObsoletePackages = new MenuItem(menuPkgs,
\r
239 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuStyle());
\r
240 showObsoletePackages.setText(
\r
241 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuTitle());
\r
242 mPkgPage.registerMenuAction(
\r
243 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG, showObsoletePackages);
\r
245 MenuItem showArchives = new MenuItem(menuPkgs,
\r
246 MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuStyle());
\r
247 showArchives.setText(
\r
248 MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuTitle());
\r
249 mPkgPage.registerMenuAction(
\r
250 MenuAction.TOGGLE_SHOW_ARCHIVES, showArchives);
\r
252 new MenuItem(menuPkgs, SWT.SEPARATOR);
\r
254 MenuItem sortByApi = new MenuItem(menuPkgs,
\r
255 MenuAction.SORT_API_LEVEL.getMenuStyle());
\r
257 MenuAction.SORT_API_LEVEL.getMenuTitle());
\r
258 mPkgPage.registerMenuAction(
\r
259 MenuAction.SORT_API_LEVEL, sortByApi);
\r
261 MenuItem sortBySource = new MenuItem(menuPkgs,
\r
262 MenuAction.SORT_SOURCE.getMenuStyle());
\r
263 sortBySource.setText(
\r
264 MenuAction.SORT_SOURCE.getMenuTitle());
\r
265 mPkgPage.registerMenuAction(
\r
266 MenuAction.SORT_SOURCE, sortBySource);
\r
268 new MenuItem(menuPkgs, SWT.SEPARATOR);
\r
270 MenuItem reload = new MenuItem(menuPkgs,
\r
271 MenuAction.RELOAD.getMenuStyle());
\r
273 MenuAction.RELOAD.getMenuTitle());
\r
274 mPkgPage.registerMenuAction(
\r
275 MenuAction.RELOAD, reload);
\r
277 MenuItem menuBarTools = new MenuItem(menuBar, SWT.CASCADE);
\r
278 menuBarTools.setText("Tools");
\r
280 Menu menuTools = new Menu(menuBarTools);
\r
281 menuBarTools.setMenu(menuTools);
\r
283 if (mContext == SdkInvocationContext.STANDALONE) {
\r
284 MenuItem manageAvds = new MenuItem(menuTools, SWT.NONE);
\r
285 manageAvds.setText("Manage AVDs...");
\r
286 manageAvds.addSelectionListener(new SelectionAdapter() {
\r
288 public void widgetSelected(SelectionEvent event) {
\r
294 MenuItem manageSources = new MenuItem(menuTools,
\r
295 MenuAction.SHOW_ADDON_SITES.getMenuStyle());
\r
296 manageSources.setText(
\r
297 MenuAction.SHOW_ADDON_SITES.getMenuTitle());
\r
298 mPkgPage.registerMenuAction(
\r
299 MenuAction.SHOW_ADDON_SITES, manageSources);
\r
301 if (mContext == SdkInvocationContext.STANDALONE) {
\r
302 // Note: when invoked from an IDE, the SwtMenuBar library isn't
\r
303 // available. This means this source should not directly import
\r
304 // any of SwtMenuBar classes, otherwise the whole window class
\r
305 // would fail to load. The MenuBarWrapper below helps to make
\r
306 // that indirection.
\r
309 new MenuBarWrapper(APP_NAME, menuTools) {
\r
311 public void onPreferencesMenuSelected() {
\r
312 showRegisteredPage(Purpose.SETTINGS);
\r
316 public void onAboutMenuSelected() {
\r
317 showRegisteredPage(Purpose.ABOUT_BOX);
\r
321 public void printError(String format, Object... args) {
\r
322 if (mUpdaterData != null) {
\r
323 mUpdaterData.getSdkLog().error(null, format, args);
\r
327 } catch (Exception e) {
\r
328 mUpdaterData.getSdkLog().error(e, "Failed to setup menu bar");
\r
329 e.printStackTrace();
\r
334 private Image getImage(String filename) {
\r
335 if (mUpdaterData != null) {
\r
336 ImageFactory imgFactory = mUpdaterData.getImageFactory();
\r
337 if (imgFactory != null) {
\r
338 return imgFactory.getImageByName(filename);
\r
345 // -- Start of internal part ----------
\r
346 // Hide everything down-below from SWT designer
\r
349 // --- Public API -----------
\r
353 * Registers an extra page for the updater window.
\r
355 * Pages must derive from {@link Composite} and implement a constructor that takes
\r
356 * a single parent {@link Composite} argument.
\r
358 * All pages must be registered before the call to {@link #open()}.
\r
360 * @param pageClass The {@link Composite}-derived class that will implement the page.
\r
361 * @param purpose The purpose of this page, e.g. an about box, settings page or generic.
\r
363 @SuppressWarnings("unchecked")
\r
364 public void registerPage(Class<? extends UpdaterPage> pageClass,
\r
366 if (mExtraPages == null) {
\r
367 mExtraPages = new ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>>();
\r
369 Pair<?, Purpose> value = Pair.of(pageClass, purpose);
\r
370 mExtraPages.add((Pair<Class<? extends UpdaterPage>, Purpose>) value);
\r
374 * Indicate the initial page that should be selected when the window opens.
\r
375 * This must be called before the call to {@link #open()}.
\r
376 * If null or if the page class is not found, the first page will be selected.
\r
378 public void setInitialPage(Class<? extends Composite> pageClass) {
\r
379 // Unused in this case. This window display only one page.
\r
383 * Sets whether the auto-update wizard will be shown when opening the window.
\r
385 * This must be called before the call to {@link #open()}.
\r
387 public void setRequestAutoUpdate(boolean requestAutoUpdate) {
\r
388 mRequestAutoUpdate = requestAutoUpdate;
\r
392 * Adds a new listener to be notified when a change is made to the content of the SDK.
\r
394 public void addListener(ISdkChangeListener listener) {
\r
395 mUpdaterData.addListeners(listener);
\r
399 * Removes a new listener to be notified anymore when a change is made to the content of
\r
402 public void removeListener(ISdkChangeListener listener) {
\r
403 mUpdaterData.removeListener(listener);
\r
406 // --- Internals & UI Callbacks -----------
\r
409 * Called before the UI is created.
\r
411 private void preCreateContent() {
\r
412 mUpdaterData.setWindowShell(mShell);
\r
413 // We need the UI factory to create the UI
\r
414 mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay()));
\r
415 // Note: we can't create the TaskFactory yet because we need the UI
\r
416 // to be created first, so this is done in postCreateContent().
\r
420 * Once the UI has been created, initializes the content.
\r
421 * This creates the pages, selects the first one, setup sources and scan for local folders.
\r
423 * Returns true if we should show the window.
\r
425 private boolean postCreateContent() {
\r
426 ProgressViewFactory factory = new ProgressViewFactory();
\r
427 factory.setProgressView(new ProgressView(
\r
428 mStatusText, mProgressBar, mButtonStop,
\r
429 mContext == SdkInvocationContext.IDE ? mUpdaterData.getSdkLog() : null));
\r
430 mUpdaterData.setTaskFactory(factory);
\r
432 setWindowImage(mShell);
\r
435 initializeSettings();
\r
437 if (mUpdaterData.checkIfInitFailed()) {
\r
441 mUpdaterData.broadcastOnSdkLoaded();
\r
443 if (mRequestAutoUpdate) {
\r
444 mUpdaterData.updateOrInstallAll_WithGUI(
\r
445 null /*selectedArchives*/,
\r
446 false /* includeObsoletes */);
\r
449 // Tell the one page its the selected one
\r
450 mPkgPage.onPageSelected();
\r
456 * Creates the icon of the window shell.
\r
458 * @param shell The shell on which to put the icon
\r
460 private void setWindowImage(Shell shell) {
\r
461 String imageName = "android_icon_16.png"; //$NON-NLS-1$
\r
462 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
\r
463 imageName = "android_icon_128.png"; //$NON-NLS-1$
\r
466 if (mUpdaterData != null) {
\r
467 ImageFactory imgFactory = mUpdaterData.getImageFactory();
\r
468 if (imgFactory != null) {
\r
469 shell.setImage(imgFactory.getImageByName(imageName));
\r
475 * Called by the main loop when the window has been disposed.
\r
477 private void dispose() {
\r
478 mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog());
\r
482 * Callback called when the window shell is disposed.
\r
484 private void onAndroidSdkUpdaterDispose() {
\r
485 if (mUpdaterData != null) {
\r
486 ImageFactory imgFactory = mUpdaterData.getImageFactory();
\r
487 if (imgFactory != null) {
\r
488 imgFactory.dispose();
\r
494 * Used to initialize the sources.
\r
496 private void setupSources() {
\r
497 mUpdaterData.setupDefaultSources();
\r
501 * Initializes settings.
\r
502 * This must be called after addExtraPages(), which created a settings page.
\r
503 * Iterate through all the pages to find the first (and supposedly unique) setting page,
\r
504 * and use it to load and apply these settings.
\r
506 private void initializeSettings() {
\r
507 mSettingsController = mUpdaterData.getSettingsController();
\r
508 mSettingsController.loadSettings();
\r
509 mSettingsController.applySettings();
\r
512 private void onToggleDetails() {
\r
513 mButtonDetails.setState(1 - mButtonDetails.getState());
\r
516 private void onStopSelected() {
\r
520 private void showRegisteredPage(Purpose purpose) {
\r
521 if (mExtraPages == null) {
\r
525 Class<? extends UpdaterPage> clazz = null;
\r
527 for (Pair<Class<? extends UpdaterPage>, Purpose> extraPage : mExtraPages) {
\r
528 if (extraPage.getSecond() == purpose) {
\r
529 clazz = extraPage.getFirst();
\r
534 if (clazz != null) {
\r
535 PageDialog d = new PageDialog(mShell, clazz, purpose == Purpose.SETTINGS);
\r
540 private void onAvdManager() {
\r
541 ITaskFactory oldFactory = mUpdaterData.getTaskFactory();
\r
544 AvdManagerWindowImpl1 win = new AvdManagerWindowImpl1(
\r
547 AvdManagerWindowImpl1.AvdInvocationContext.SDK_MANAGER);
\r
549 for (Pair<Class<? extends UpdaterPage>, Purpose> page : mExtraPages) {
\r
550 win.registerPage(page.getFirst(), page.getSecond());
\r
554 } catch (Exception e) {
\r
555 mUpdaterData.getSdkLog().error(e, "AVD Manager window error");
\r
557 mUpdaterData.setTaskFactory(oldFactory);
\r
561 // End of hiding from SWT Designer
\r
567 * Dialog used to display either the About page or the Settings (aka Options) page
\r
568 * with a "close" button.
\r
570 private class PageDialog extends SwtBaseDialog {
\r
572 private final Class<? extends UpdaterPage> mPageClass;
\r
573 private final boolean mIsSettingsPage;
\r
575 protected PageDialog(
\r
577 Class<? extends UpdaterPage> pageClass,
\r
578 boolean isSettingsPage) {
\r
579 super(parentShell, SWT.APPLICATION_MODAL, null /*title*/);
\r
580 mPageClass = pageClass;
\r
581 mIsSettingsPage = isSettingsPage;
\r
585 protected void createContents() {
\r
586 Shell shell = getShell();
\r
587 setWindowImage(shell);
\r
589 GridLayoutBuilder.create(shell).columns(2);
\r
591 UpdaterPage content = UpdaterPage.newInstance(
\r
595 mUpdaterData.getSdkLog());
\r
596 GridDataBuilder.create(content).fill().grab().hSpan(2);
\r
597 if (content.getLayout() instanceof GridLayout) {
\r
598 GridLayout gl = (GridLayout) content.getLayout();
\r
599 gl.marginHeight = gl.marginWidth = 0;
\r
602 if (mIsSettingsPage && content instanceof ISettingsPage) {
\r
603 mSettingsController.setSettingsPage((ISettingsPage) content);
\r
606 getShell().setText(
\r
607 String.format("%1$s - %2$s", APP_NAME, content.getPageTitle())); //$NON-NLS-1$
\r
609 Label filler = new Label(shell, SWT.NONE);
\r
610 GridDataBuilder.create(filler).hFill().hGrab();
\r
612 Button close = new Button(shell, SWT.PUSH);
\r
613 close.setText("Close");
\r
614 GridDataBuilder.create(close);
\r
615 close.addSelectionListener(new SelectionAdapter() {
\r
617 public void widgetSelected(SelectionEvent e) {
\r
624 protected void postCreate() {
\r
629 protected void close() {
\r
630 if (mIsSettingsPage) {
\r
631 mSettingsController.setSettingsPage(null);
\r