• 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.widgets;
18 
19 import com.android.prefs.AndroidLocation.AndroidLocationException;
20 import com.android.sdklib.IAndroidTarget;
21 import com.android.sdklib.ISdkLog;
22 import com.android.sdklib.NullSdkLog;
23 import com.android.sdklib.SdkConstants;
24 import com.android.sdklib.internal.avd.AvdInfo;
25 import com.android.sdklib.internal.avd.AvdManager;
26 import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
27 import com.android.sdklib.internal.repository.ITask;
28 import com.android.sdklib.internal.repository.ITaskMonitor;
29 import com.android.sdkuilib.internal.repository.SettingsController;
30 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
31 import com.android.sdkuilib.internal.tasks.ProgressTask;
32 import com.android.sdkuilib.repository.SdkUpdaterWindow;
33 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
34 
35 import org.eclipse.jface.dialogs.MessageDialog;
36 import org.eclipse.jface.window.Window;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.events.ControlAdapter;
39 import org.eclipse.swt.events.ControlEvent;
40 import org.eclipse.swt.events.DisposeEvent;
41 import org.eclipse.swt.events.DisposeListener;
42 import org.eclipse.swt.events.SelectionAdapter;
43 import org.eclipse.swt.events.SelectionEvent;
44 import org.eclipse.swt.events.SelectionListener;
45 import org.eclipse.swt.graphics.Image;
46 import org.eclipse.swt.graphics.Rectangle;
47 import org.eclipse.swt.layout.GridData;
48 import org.eclipse.swt.layout.GridLayout;
49 import org.eclipse.swt.widgets.Button;
50 import org.eclipse.swt.widgets.Composite;
51 import org.eclipse.swt.widgets.Display;
52 import org.eclipse.swt.widgets.Label;
53 import org.eclipse.swt.widgets.Shell;
54 import org.eclipse.swt.widgets.Table;
55 import org.eclipse.swt.widgets.TableColumn;
56 import org.eclipse.swt.widgets.TableItem;
57 
58 import java.io.BufferedReader;
59 import java.io.File;
60 import java.io.IOException;
61 import java.io.InputStreamReader;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Comparator;
65 import java.util.Formatter;
66 import java.util.Locale;
67 
68 
69 /**
70  * The AVD selector is a table that is added to the given parent composite.
71  * <p/>
72  * After using one of the constructors, call {@link #setSelection(AvdInfo)},
73  * {@link #setSelectionListener(SelectionListener)} and finally use
74  * {@link #getSelected()} to retrieve the selection.
75  */
76 public final class AvdSelector {
77     private static int NUM_COL = 2;
78 
79     private final DisplayMode mDisplayMode;
80 
81     private AvdManager mAvdManager;
82     private final String mOsSdkPath;
83 
84     private Table mTable;
85     private Button mDeleteButton;
86     private Button mDetailsButton;
87     private Button mNewButton;
88     private Button mEditButton;
89     private Button mRefreshButton;
90     private Button mManagerButton;
91     private Button mRepairButton;
92     private Button mStartButton;
93 
94     private SelectionListener mSelectionListener;
95     private IAvdFilter mTargetFilter;
96 
97     /** Defaults to true. Changed by the {@link #setEnabled(boolean)} method to represent the
98      * "global" enabled state on this composite. */
99     private boolean mIsEnabled = true;
100 
101     private ImageFactory mImageFactory;
102     private Image mOkImage;
103     private Image mBrokenImage;
104     private Image mInvalidImage;
105 
106     private SettingsController mController;
107 
108     private final ISdkLog mSdkLog;
109 
110 
111     /**
112      * The display mode of the AVD Selector.
113      */
114     public static enum DisplayMode {
115         /**
116          * Manager mode. Invalid AVDs are displayed. Buttons to create/delete AVDs
117          */
118         MANAGER,
119 
120         /**
121          * Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
122          * there is a button to open the AVD Manager.
123          * In the "check" selection mode, checkboxes are displayed on each line
124          * and {@link AvdSelector#getSelected()} returns the line that is checked
125          * even if it is not the currently selected line. Only one line can
126          * be checked at once.
127          */
128         SIMPLE_CHECK,
129 
130         /**
131          * Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
132          * there is a button to open the AVD Manager.
133          * In the "select" selection mode, there are no checkboxes and
134          * {@link AvdSelector#getSelected()} returns the line currently selected.
135          * Only one line can be selected at once.
136          */
137         SIMPLE_SELECTION,
138     }
139 
140     /**
141      * A filter to control the whether or not an AVD should be displayed by the AVD Selector.
142      */
143     public interface IAvdFilter {
144         /**
145          * Called before {@link #accept(AvdInfo)} is called for any AVD.
146          */
prepare()147         void prepare();
148 
149         /**
150          * Called to decided whether an AVD should be displayed.
151          * @param avd the AVD to test.
152          * @return true if the AVD should be displayed.
153          */
accept(AvdInfo avd)154         boolean accept(AvdInfo avd);
155 
156         /**
157          * Called after {@link #accept(AvdInfo)} has been called on all the AVDs.
158          */
cleanup()159         void cleanup();
160     }
161 
162     /**
163      * Internal implementation of {@link IAvdFilter} to filter out the AVDs that are not
164      * running an image compatible with a specific target.
165      */
166     private final static class TargetBasedFilter implements IAvdFilter {
167         private final IAndroidTarget mTarget;
168 
TargetBasedFilter(IAndroidTarget target)169         TargetBasedFilter(IAndroidTarget target) {
170             mTarget = target;
171         }
172 
prepare()173         public void prepare() {
174             // nothing to prepare
175         }
176 
accept(AvdInfo avd)177         public boolean accept(AvdInfo avd) {
178             if (avd != null) {
179                 return mTarget.canRunOn(avd.getTarget());
180             }
181 
182             return false;
183         }
184 
cleanup()185         public void cleanup() {
186             // nothing to clean up
187         }
188     }
189 
190     /**
191      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
192      * by a {@link IAndroidTarget}.
193      * <p/>Only the {@link AvdInfo} able to run application developed for the given
194      * {@link IAndroidTarget} will be displayed.
195      *
196      * @param parent The parent composite where the selector will be added.
197      * @param osSdkPath The SDK root path. When not null, enables the start button to start
198      *                  an emulator on a given AVD.
199      * @param manager the AVD manager.
200      * @param filter When non-null, will allow filtering the AVDs to display.
201      * @param displayMode The display mode ({@link DisplayMode}).
202      * @param sdkLog The logger. Cannot be null.
203      */
AvdSelector(Composite parent, String osSdkPath, AvdManager manager, IAvdFilter filter, DisplayMode displayMode, ISdkLog sdkLog)204     public AvdSelector(Composite parent,
205             String osSdkPath,
206             AvdManager manager,
207             IAvdFilter filter,
208             DisplayMode displayMode,
209             ISdkLog sdkLog) {
210         mOsSdkPath = osSdkPath;
211         mAvdManager = manager;
212         mTargetFilter = filter;
213         mDisplayMode = displayMode;
214         mSdkLog = sdkLog;
215 
216         // get some bitmaps.
217         mImageFactory = new ImageFactory(parent.getDisplay());
218         mOkImage = mImageFactory.getImageByName("accept_icon16.png");
219         mBrokenImage = mImageFactory.getImageByName("broken_16.png");
220         mInvalidImage = mImageFactory.getImageByName("reject_icon16.png");
221 
222         // Layout has 2 columns
223         Composite group = new Composite(parent, SWT.NONE);
224         GridLayout gl;
225         group.setLayout(gl = new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
226         gl.marginHeight = gl.marginWidth = 0;
227         group.setLayoutData(new GridData(GridData.FILL_BOTH));
228         group.setFont(parent.getFont());
229         group.addDisposeListener(new DisposeListener() {
230             public void widgetDisposed(DisposeEvent arg0) {
231                 mImageFactory.dispose();
232             }
233         });
234 
235         int style = SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER;
236         if (displayMode == DisplayMode.SIMPLE_CHECK) {
237             style |= SWT.CHECK;
238         }
239         mTable = new Table(group, style);
240         mTable.setHeaderVisible(true);
241         mTable.setLinesVisible(false);
242         setTableHeightHint(0);
243 
244         Composite buttons = new Composite(group, SWT.NONE);
245         buttons.setLayout(gl = new GridLayout(1, false /*makeColumnsEqualWidth*/));
246         gl.marginHeight = gl.marginWidth = 0;
247         buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
248         buttons.setFont(group.getFont());
249 
250         if (displayMode == DisplayMode.MANAGER) {
251             mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
252             mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
253             mNewButton.setText("New...");
254             mNewButton.setToolTipText("Creates a new AVD.");
255             mNewButton.addSelectionListener(new SelectionAdapter() {
256                 @Override
257                 public void widgetSelected(SelectionEvent arg0) {
258                     onNew();
259                 }
260             });
261 
262             mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
263             mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
264             mEditButton.setText("Edit...");
265             mEditButton.setToolTipText("Edit an existing AVD.");
266             mEditButton.addSelectionListener(new SelectionAdapter() {
267                 @Override
268                 public void widgetSelected(SelectionEvent arg0) {
269                     onEdit();
270                 }
271             });
272 
273             mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
274             mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
275             mDeleteButton.setText("Delete...");
276             mDeleteButton.setToolTipText("Deletes the selected AVD.");
277             mDeleteButton.addSelectionListener(new SelectionAdapter() {
278                 @Override
279                 public void widgetSelected(SelectionEvent arg0) {
280                     onDelete();
281                 }
282             });
283 
284             mRepairButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
285             mRepairButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
286             mRepairButton.setText("Repair...");
287             mRepairButton.setToolTipText("Repairs the selected AVD.");
288             mRepairButton.addSelectionListener(new SelectionAdapter() {
289                 @Override
290                 public void widgetSelected(SelectionEvent arg0) {
291                     onRepair();
292                 }
293             });
294 
295             Label l = new Label(buttons, SWT.SEPARATOR | SWT.HORIZONTAL);
296             l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
297         }
298 
299         mDetailsButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
300         mDetailsButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
301         mDetailsButton.setText("Details...");
302         mDetailsButton.setToolTipText("Displays details of the selected AVD.");
303         mDetailsButton.addSelectionListener(new SelectionAdapter() {
304             @Override
305             public void widgetSelected(SelectionEvent arg0) {
306                 onDetails();
307             }
308         });
309 
310         mStartButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
311         mStartButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
312         mStartButton.setText("Start...");
313         mStartButton.setToolTipText("Starts the selected AVD.");
314         mStartButton.addSelectionListener(new SelectionAdapter() {
315             @Override
316             public void widgetSelected(SelectionEvent arg0) {
317                 onStart();
318             }
319         });
320 
321         Composite padding = new Composite(buttons, SWT.NONE);
322         padding.setLayoutData(new GridData(GridData.FILL_VERTICAL));
323 
324         mRefreshButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
325         mRefreshButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
326         mRefreshButton.setText("Refresh");
327         mRefreshButton.setToolTipText("Reloads the list of AVD.\nUse this if you create AVDs from the command line.");
328         mRefreshButton.addSelectionListener(new SelectionAdapter() {
329             @Override
330             public void widgetSelected(SelectionEvent arg0) {
331                 refresh(true);
332             }
333         });
334 
335         if (displayMode != DisplayMode.MANAGER) {
336             mManagerButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
337             mManagerButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
338             mManagerButton.setText("Manager...");
339             mManagerButton.setToolTipText("Launches the AVD manager.");
340             mManagerButton.addSelectionListener(new SelectionAdapter() {
341                 @Override
342                 public void widgetSelected(SelectionEvent e) {
343                     onManager();
344                 }
345             });
346         } else {
347             Composite legend = new Composite(group, SWT.NONE);
348             legend.setLayout(gl = new GridLayout(4, false /*makeColumnsEqualWidth*/));
349             gl.marginHeight = gl.marginWidth = 0;
350             legend.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false,
351                     NUM_COL, 1));
352             legend.setFont(group.getFont());
353 
354             new Label(legend, SWT.NONE).setImage(mOkImage);
355             new Label(legend, SWT.NONE).setText("A valid Android Virtual Device.");
356             new Label(legend, SWT.NONE).setImage(mBrokenImage);
357             new Label(legend, SWT.NONE).setText(
358                     "A repairable Android Virtual Device.");
359             new Label(legend, SWT.NONE).setImage(mInvalidImage);
360             Label l = new Label(legend, SWT.NONE);
361             l.setText("An Android Virtual Device that failed to load. Click 'Details' to see the error.");
362             GridData gd;
363             l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
364             gd.horizontalSpan = 3;
365         }
366 
367         // create the table columns
368         final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
369         column0.setText("AVD Name");
370         final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
371         column1.setText("Target Name");
372         final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
373         column2.setText("Platform");
374         final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
375         column3.setText("API Level");
376         final TableColumn column4 = new TableColumn(mTable, SWT.NONE);
377         column4.setText("CPU/ABI");
378 
379         adjustColumnsWidth(mTable, column0, column1, column2, column3, column4);
380         setupSelectionListener(mTable);
381         fillTable(mTable);
382         setEnabled(true);
383     }
384 
385     /**
386      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
387      *
388      * @param parent The parent composite where the selector will be added.
389      * @param manager the AVD manager.
390      * @param displayMode The display mode ({@link DisplayMode}).
391      * @param sdkLog The logger. Cannot be null.
392      */
AvdSelector(Composite parent, String osSdkPath, AvdManager manager, DisplayMode displayMode, ISdkLog sdkLog)393     public AvdSelector(Composite parent,
394             String osSdkPath,
395             AvdManager manager,
396             DisplayMode displayMode,
397             ISdkLog sdkLog) {
398         this(parent, osSdkPath, manager, (IAvdFilter)null /* filter */, displayMode, sdkLog);
399     }
400 
401     /**
402      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
403      * by an {@link IAndroidTarget}.
404      * <p/>Only the {@link AvdInfo} able to run applications developed for the given
405      * {@link IAndroidTarget} will be displayed.
406      *
407      * @param parent The parent composite where the selector will be added.
408      * @param manager the AVD manager.
409      * @param filter Only shows the AVDs matching this target (must not be null).
410      * @param displayMode The display mode ({@link DisplayMode}).
411      * @param sdkLog The logger. Cannot be null.
412      */
AvdSelector(Composite parent, String osSdkPath, AvdManager manager, IAndroidTarget filter, DisplayMode displayMode, ISdkLog sdkLog)413     public AvdSelector(Composite parent,
414             String osSdkPath,
415             AvdManager manager,
416             IAndroidTarget filter,
417             DisplayMode displayMode,
418             ISdkLog sdkLog) {
419         this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode, sdkLog);
420     }
421 
422     /**
423      * Sets an optional SettingsController.
424      * @param controller the controller.
425      */
setSettingsController(SettingsController controller)426     public void setSettingsController(SettingsController controller) {
427         mController = controller;
428     }
429     /**
430      * Sets the table grid layout data.
431      *
432      * @param heightHint If > 0, the height hint is set to the requested value.
433      */
setTableHeightHint(int heightHint)434     public void setTableHeightHint(int heightHint) {
435         GridData data = new GridData();
436         if (heightHint > 0) {
437             data.heightHint = heightHint;
438         }
439         data.grabExcessVerticalSpace = true;
440         data.grabExcessHorizontalSpace = true;
441         data.horizontalAlignment = GridData.FILL;
442         data.verticalAlignment = GridData.FILL;
443         mTable.setLayoutData(data);
444     }
445 
446     /**
447      * Refresh the display of Android Virtual Devices.
448      * Tries to keep the selection.
449      * <p/>
450      * This must be called from the UI thread.
451      *
452      * @param reload if true, the AVD manager will reload the AVD from the disk.
453      * @return false if the reloading failed. This is always true if <var>reload</var> is
454      * <code>false</code>.
455      */
refresh(boolean reload)456     public boolean refresh(boolean reload) {
457         if (reload) {
458             try {
459                 mAvdManager.reloadAvds(NullSdkLog.getLogger());
460             } catch (AndroidLocationException e) {
461                 return false;
462             }
463         }
464 
465         AvdInfo selected = getSelected();
466 
467         fillTable(mTable);
468 
469         setSelection(selected);
470 
471         return true;
472     }
473 
474     /**
475      * Sets a new AVD manager
476      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
477      * @param manager the AVD manager.
478      */
setManager(AvdManager manager)479     public void setManager(AvdManager manager) {
480         mAvdManager = manager;
481     }
482 
483     /**
484      * Sets a new AVD filter.
485      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
486      * @param filter An IAvdFilter. If non-null, this will filter out the AVD to not display.
487      */
setFilter(IAvdFilter filter)488     public void setFilter(IAvdFilter filter) {
489         mTargetFilter = filter;
490     }
491 
492     /**
493      * Sets a new Android Target-based AVD filter.
494      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
495      * @param target An IAndroidTarget. If non-null, only AVD whose target are compatible with the
496      * filter target will displayed an available for selection.
497      */
setFilter(IAndroidTarget target)498     public void setFilter(IAndroidTarget target) {
499         if (target != null) {
500             mTargetFilter = new TargetBasedFilter(target);
501         } else {
502             mTargetFilter = null;
503         }
504     }
505 
506     /**
507      * Sets a selection listener. Set it to null to remove it.
508      * The listener will be called <em>after</em> this table processed its selection
509      * events so that the caller can see the updated state.
510      * <p/>
511      * The event's item contains a {@link TableItem}.
512      * The {@link TableItem#getData()} contains an {@link IAndroidTarget}.
513      * <p/>
514      * It is recommended that the caller uses the {@link #getSelected()} method instead.
515      * <p/>
516      * The default behavior for double click (when not in {@link DisplayMode#SIMPLE_CHECK}) is to
517      * display the details of the selected AVD.<br>
518      * To disable it (when you provide your own double click action), set
519      * {@link SelectionEvent#doit} to false in
520      * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)}
521      *
522      * @param selectionListener The new listener or null to remove it.
523      */
setSelectionListener(SelectionListener selectionListener)524     public void setSelectionListener(SelectionListener selectionListener) {
525         mSelectionListener = selectionListener;
526     }
527 
528     /**
529      * Sets the current target selection.
530      * <p/>
531      * If the selection is actually changed, this will invoke the selection listener
532      * (if any) with a null event.
533      *
534      * @param target the target to be selected. Use null to deselect everything.
535      * @return true if the target could be selected, false otherwise.
536      */
setSelection(AvdInfo target)537     public boolean setSelection(AvdInfo target) {
538         boolean found = false;
539         boolean modified = false;
540 
541         int selIndex = mTable.getSelectionIndex();
542         int index = 0;
543         for (TableItem i : mTable.getItems()) {
544             if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
545                 if ((AvdInfo) i.getData() == target) {
546                     found = true;
547                     if (!i.getChecked()) {
548                         modified = true;
549                         i.setChecked(true);
550                     }
551                 } else if (i.getChecked()) {
552                     modified = true;
553                     i.setChecked(false);
554                 }
555             } else {
556                 if ((AvdInfo) i.getData() == target) {
557                     found = true;
558                     if (index != selIndex) {
559                         mTable.setSelection(index);
560                         modified = true;
561                     }
562                     break;
563                 }
564 
565                 index++;
566             }
567         }
568 
569         if (modified && mSelectionListener != null) {
570             mSelectionListener.widgetSelected(null);
571         }
572 
573         enableActionButtons();
574 
575         return found;
576     }
577 
578     /**
579      * Returns the currently selected item. In {@link DisplayMode#SIMPLE_CHECK} mode this will
580      * return the {@link AvdInfo} that is checked instead of the list selection.
581      *
582      * @return The currently selected item or null.
583      */
getSelected()584     public AvdInfo getSelected() {
585         if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
586             for (TableItem i : mTable.getItems()) {
587                 if (i.getChecked()) {
588                     return (AvdInfo) i.getData();
589                 }
590             }
591         } else {
592             int selIndex = mTable.getSelectionIndex();
593             if (selIndex >= 0) {
594                 return (AvdInfo) mTable.getItem(selIndex).getData();
595             }
596         }
597 
598         return null;
599     }
600 
601     /**
602      * Enables the receiver if the argument is true, and disables it otherwise.
603      * A disabled control is typically not selectable from the user interface
604      * and draws with an inactive or "grayed" look.
605      *
606      * @param enabled the new enabled state.
607      */
setEnabled(boolean enabled)608     public void setEnabled(boolean enabled) {
609         // We can only enable widgets if the AVD Manager is defined.
610         mIsEnabled = enabled && mAvdManager != null;
611 
612         mTable.setEnabled(mIsEnabled);
613         mRefreshButton.setEnabled(mIsEnabled);
614 
615         if (mNewButton != null) {
616             mNewButton.setEnabled(mIsEnabled);
617         }
618         if (mManagerButton != null) {
619             mManagerButton.setEnabled(mIsEnabled);
620         }
621 
622         enableActionButtons();
623     }
624 
isEnabled()625     public boolean isEnabled() {
626         return mIsEnabled;
627     }
628 
629     /**
630      * Adds a listener to adjust the columns width when the parent is resized.
631      * <p/>
632      * If we need something more fancy, we might want to use this:
633      * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
634      */
adjustColumnsWidth(final Table table, final TableColumn column0, final TableColumn column1, final TableColumn column2, final TableColumn column3, final TableColumn column4)635     private void adjustColumnsWidth(final Table table,
636             final TableColumn column0,
637             final TableColumn column1,
638             final TableColumn column2,
639             final TableColumn column3,
640             final TableColumn column4) {
641         // Add a listener to resize the column to the full width of the table
642         table.addControlListener(new ControlAdapter() {
643             @Override
644             public void controlResized(ControlEvent e) {
645                 Rectangle r = table.getClientArea();
646                 column0.setWidth(r.width * 20 / 100); // 20%
647                 column1.setWidth(r.width * 30 / 100); // 30%
648                 column2.setWidth(r.width * 15 / 100); // 15%
649                 column3.setWidth(r.width * 15 / 100); // 15%
650                 column4.setWidth(r.width * 20 / 100); // 22%
651             }
652         });
653     }
654 
655     /**
656      * Creates a selection listener that will check or uncheck the whole line when
657      * double-clicked (aka "the default selection").
658      */
setupSelectionListener(final Table table)659     private void setupSelectionListener(final Table table) {
660         // Add a selection listener that will check/uncheck items when they are double-clicked
661         table.addSelectionListener(new SelectionListener() {
662 
663             /**
664              * Handles single-click selection on the table.
665              * {@inheritDoc}
666              */
667             public void widgetSelected(SelectionEvent e) {
668                 if (e.item instanceof TableItem) {
669                     TableItem i = (TableItem) e.item;
670                     enforceSingleSelection(i);
671                 }
672 
673                 if (mSelectionListener != null) {
674                     mSelectionListener.widgetSelected(e);
675                 }
676 
677                 enableActionButtons();
678             }
679 
680             /**
681              * Handles double-click selection on the table.
682              * Note that the single-click handler will probably already have been called.
683              *
684              * On double-click, <em>always</em> check the table item.
685              *
686              * {@inheritDoc}
687              */
688             public void widgetDefaultSelected(SelectionEvent e) {
689                 if (e.item instanceof TableItem) {
690                     TableItem i = (TableItem) e.item;
691                     if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
692                         i.setChecked(true);
693                     }
694                     enforceSingleSelection(i);
695 
696                 }
697 
698                 // whether or not we display details. default: true when not in SIMPLE_CHECK mode.
699                 boolean showDetails = mDisplayMode != DisplayMode.SIMPLE_CHECK;
700 
701                 if (mSelectionListener != null) {
702                     mSelectionListener.widgetDefaultSelected(e);
703                     showDetails &= e.doit; // enforce false in SIMPLE_CHECK
704                 }
705 
706                 if (showDetails) {
707                     onDetails();
708                 }
709 
710                 enableActionButtons();
711             }
712 
713             /**
714              * To ensure single selection, uncheck all other items when this one is selected.
715              * This makes the chekboxes act as radio buttons.
716              */
717             private void enforceSingleSelection(TableItem item) {
718                 if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
719                     if (item.getChecked()) {
720                         Table parentTable = item.getParent();
721                         for (TableItem i2 : parentTable.getItems()) {
722                             if (i2 != item && i2.getChecked()) {
723                                 i2.setChecked(false);
724                             }
725                         }
726                     }
727                 } else {
728                     // pass
729                 }
730             }
731         });
732     }
733 
734     /**
735      * Fills the table with all AVD.
736      * The table columns are:
737      * <ul>
738      * <li>column 0: sdk name
739      * <li>column 1: sdk vendor
740      * <li>column 2: sdk api name
741      * <li>column 3: sdk version
742      * </ul>
743      */
fillTable(final Table table)744     private void fillTable(final Table table) {
745         table.removeAll();
746 
747         // get the AVDs
748         AvdInfo avds[] = null;
749         if (mAvdManager != null) {
750             if (mDisplayMode == DisplayMode.MANAGER) {
751                 avds = mAvdManager.getAllAvds();
752             } else {
753                 avds = mAvdManager.getValidAvds();
754             }
755         }
756 
757         if (avds != null && avds.length > 0) {
758             Arrays.sort(avds, new Comparator<AvdInfo>() {
759                 public int compare(AvdInfo o1, AvdInfo o2) {
760                     return o1.compareTo(o2);
761                 }
762             });
763 
764             table.setEnabled(true);
765 
766             if (mTargetFilter != null) {
767                 mTargetFilter.prepare();
768             }
769 
770             for (AvdInfo avd : avds) {
771                 if (mTargetFilter == null || mTargetFilter.accept(avd)) {
772                     TableItem item = new TableItem(table, SWT.NONE);
773                     item.setData(avd);
774                     item.setText(0, avd.getName());
775                     if (mDisplayMode == DisplayMode.MANAGER) {
776                         AvdStatus status = avd.getStatus();
777                         item.setImage(0, status == AvdStatus.OK ? mOkImage :
778                             isAvdRepairable(status) ? mBrokenImage : mInvalidImage);
779                     }
780                     IAndroidTarget target = avd.getTarget();
781                     if (target != null) {
782                         item.setText(1, target.getFullName());
783                         item.setText(2, target.getVersionName());
784                         item.setText(3, target.getVersion().getApiString());
785                         item.setText(4, AvdInfo.getPrettyAbiType(avd.getAbiType()));
786                     } else {
787                         item.setText(1, "?");
788                         item.setText(2, "?");
789                         item.setText(3, "?");
790                         item.setText(4, "?");
791                     }
792                 }
793             }
794 
795             if (mTargetFilter != null) {
796                 mTargetFilter.cleanup();
797             }
798         }
799 
800         if (table.getItemCount() == 0) {
801             table.setEnabled(false);
802             TableItem item = new TableItem(table, SWT.NONE);
803             item.setData(null);
804             item.setText(0, "--");
805             item.setText(1, "No AVD available");
806             item.setText(2, "--");
807             item.setText(3, "--");
808         }
809     }
810 
811     /**
812      * Returns the currently selected AVD in the table.
813      * <p/>
814      * Unlike {@link #getSelected()} this will always return the item being selected
815      * in the list, ignoring the check boxes state in {@link DisplayMode#SIMPLE_CHECK} mode.
816      */
getTableSelection()817     private AvdInfo getTableSelection() {
818         int selIndex = mTable.getSelectionIndex();
819         if (selIndex >= 0) {
820             return (AvdInfo) mTable.getItem(selIndex).getData();
821         }
822 
823         return null;
824     }
825 
826     /**
827      * Updates the enable state of the Details, Start, Delete and Update buttons.
828      */
829     @SuppressWarnings("null")
enableActionButtons()830     private void enableActionButtons() {
831         if (mIsEnabled == false) {
832             mDetailsButton.setEnabled(false);
833             mStartButton.setEnabled(false);
834 
835             if (mEditButton != null) {
836                 mEditButton.setEnabled(false);
837             }
838             if (mDeleteButton != null) {
839                 mDeleteButton.setEnabled(false);
840             }
841             if (mRepairButton != null) {
842                 mRepairButton.setEnabled(false);
843             }
844         } else {
845             AvdInfo selection = getTableSelection();
846             boolean hasSelection = selection != null;
847 
848             mDetailsButton.setEnabled(hasSelection);
849             mStartButton.setEnabled(mOsSdkPath != null &&
850                     hasSelection &&
851                     selection.getStatus() == AvdStatus.OK);
852 
853             if (mEditButton != null) {
854                 mEditButton.setEnabled(hasSelection);
855             }
856             if (mDeleteButton != null) {
857                 mDeleteButton.setEnabled(hasSelection);
858             }
859             if (mRepairButton != null) {
860                 mRepairButton.setEnabled(hasSelection && isAvdRepairable(selection.getStatus()));
861             }
862         }
863     }
864 
onNew()865     private void onNew() {
866         AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
867                 mAvdManager,
868                 mImageFactory,
869                 mSdkLog,
870                 null);
871 
872         if (dlg.open() == Window.OK) {
873             refresh(false /*reload*/);
874         }
875     }
876 
onEdit()877     private void onEdit() {
878         AvdInfo avdInfo = getTableSelection();
879 
880         AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
881                 mAvdManager,
882                 mImageFactory,
883                 mSdkLog,
884                 avdInfo);
885 
886         if (dlg.open() == Window.OK) {
887             refresh(false /*reload*/);
888         }
889     }
890 
onDetails()891     private void onDetails() {
892         AvdInfo avdInfo = getTableSelection();
893 
894         AvdDetailsDialog dlg = new AvdDetailsDialog(mTable.getShell(), avdInfo);
895         dlg.open();
896     }
897 
onDelete()898     private void onDelete() {
899         final AvdInfo avdInfo = getTableSelection();
900 
901         // get the current Display
902         final Display display = mTable.getDisplay();
903 
904         // check if the AVD is running
905         if (avdInfo.isRunning()) {
906             display.asyncExec(new Runnable() {
907                 public void run() {
908                     Shell shell = display.getActiveShell();
909                     MessageDialog.openError(shell,
910                             "Delete Android Virtual Device",
911                             String.format(
912                                     "The Android Virtual Device '%1$s' is currently running in an emulator and cannot be deleted.",
913                                     avdInfo.getName()));
914                 }
915             });
916             return;
917         }
918 
919         // Confirm you want to delete this AVD
920         final boolean[] result = new boolean[1];
921         display.syncExec(new Runnable() {
922             public void run() {
923                 Shell shell = display.getActiveShell();
924                 result[0] = MessageDialog.openQuestion(shell,
925                         "Delete Android Virtual Device",
926                         String.format(
927                                 "Please confirm that you want to delete the Android Virtual Device named '%s'. This operation cannot be reverted.",
928                                 avdInfo.getName()));
929             }
930         });
931 
932         if (result[0] == false) {
933             return;
934         }
935 
936         // log for this action.
937         ISdkLog log = mSdkLog;
938         if (log == null || log instanceof MessageBoxLog) {
939             // If the current logger is a message box, we use our own (to make sure
940             // to display errors right away and customize the title).
941             log = new MessageBoxLog(
942                 String.format("Result of deleting AVD '%s':", avdInfo.getName()),
943                 display,
944                 false /*logErrorsOnly*/);
945         }
946 
947         // delete the AVD
948         boolean success = mAvdManager.deleteAvd(avdInfo, log);
949 
950         // display the result
951         if (log instanceof MessageBoxLog) {
952             ((MessageBoxLog) log).displayResult(success);
953         }
954 
955         if (success) {
956             refresh(false /*reload*/);
957         }
958     }
959 
960     /**
961      * Repairs the selected AVD.
962      * <p/>
963      * For now this only supports fixing the wrong value in image.sysdir.*
964      */
onRepair()965     private void onRepair() {
966         final AvdInfo avdInfo = getTableSelection();
967 
968         // get the current Display
969         final Display display = mTable.getDisplay();
970 
971         // log for this action.
972         ISdkLog log = mSdkLog;
973         if (log == null || log instanceof MessageBoxLog) {
974             // If the current logger is a message box, we use our own (to make sure
975             // to display errors right away and customize the title).
976             log = new MessageBoxLog(
977                 String.format("Result of updating AVD '%s':", avdInfo.getName()),
978                 display,
979                 false /*logErrorsOnly*/);
980         }
981 
982         // delete the AVD
983         try {
984             mAvdManager.updateAvd(avdInfo, log);
985 
986             // display the result
987             if (log instanceof MessageBoxLog) {
988                 ((MessageBoxLog) log).displayResult(true /* success */);
989             }
990             refresh(false /*reload*/);
991 
992         } catch (IOException e) {
993             log.error(e, null);
994             if (log instanceof MessageBoxLog) {
995                 ((MessageBoxLog) log).displayResult(false /* success */);
996             }
997         }
998     }
999 
onManager()1000     private void onManager() {
1001 
1002         // get the current Display
1003         Display display = mTable.getDisplay();
1004 
1005         // log for this action.
1006         ISdkLog log = mSdkLog;
1007         if (log == null || log instanceof MessageBoxLog) {
1008             // If the current logger is a message box, we use our own (to make sure
1009             // to display errors right away and customize the title).
1010             log = new MessageBoxLog("Result of SDK Manager", display, true /*logErrorsOnly*/);
1011         }
1012 
1013         SdkUpdaterWindow window = new SdkUpdaterWindow(
1014                 mTable.getShell(),
1015                 log,
1016                 mAvdManager.getSdkManager().getLocation(),
1017                 SdkInvocationContext.AVD_SELECTOR);
1018         window.open();
1019         refresh(true /*reload*/); // UpdaterWindow uses its own AVD manager so this one must reload.
1020 
1021         if (log instanceof MessageBoxLog) {
1022             ((MessageBoxLog) log).displayResult(true);
1023         }
1024     }
1025 
onStart()1026     private void onStart() {
1027         AvdInfo avdInfo = getTableSelection();
1028 
1029         if (avdInfo == null || mOsSdkPath == null) {
1030             return;
1031         }
1032 
1033         AvdStartDialog dialog = new AvdStartDialog(mTable.getShell(), avdInfo, mOsSdkPath,
1034                 mController);
1035         if (dialog.open() == Window.OK) {
1036             String path = mOsSdkPath + File.separator
1037                     + SdkConstants.OS_SDK_TOOLS_FOLDER
1038                     + SdkConstants.FN_EMULATOR;
1039 
1040             final String avdName = avdInfo.getName();
1041 
1042             // build the command line based on the available parameters.
1043             ArrayList<String> list = new ArrayList<String>();
1044             list.add(path);
1045             list.add("-avd");                             //$NON-NLS-1$
1046             list.add(avdName);
1047             if (dialog.hasWipeData()) {
1048                 list.add("-wipe-data");                   //$NON-NLS-1$
1049             }
1050             if (dialog.hasSnapshot()) {
1051                 if (!dialog.hasSnapshotLaunch()) {
1052                     list.add("-no-snapshot-load");
1053                 }
1054                 if (!dialog.hasSnapshotSave()) {
1055                     list.add("-no-snapshot-save");
1056                 }
1057             }
1058             float scale = dialog.getScale();
1059             if (scale != 0.f) {
1060                 // do the rounding ourselves. This is because %.1f will write .4899 as .4
1061                 scale = Math.round(scale * 100);
1062                 scale /=  100.f;
1063                 list.add("-scale");                       //$NON-NLS-1$
1064                 // because the emulator expects English decimal values, don't use String.format
1065                 // but a Formatter.
1066                 Formatter formatter = new Formatter(Locale.US);
1067                 formatter.format("%.2f", scale);   //$NON-NLS-1$
1068                 list.add(formatter.toString());
1069             }
1070 
1071             // convert the list into an array for the call to exec.
1072             final String[] command = list.toArray(new String[list.size()]);
1073 
1074             // launch the emulator
1075             new ProgressTask(mTable.getShell(),
1076                     "Starting Android Emulator",
1077                     new ITask() {
1078                         public void run(ITaskMonitor monitor) {
1079                             try {
1080                                 monitor.setDescription(
1081                                         "Starting emulator for AVD '%1$s'",
1082                                         avdName);
1083                                 int n = 10;
1084                                 monitor.setProgressMax(n);
1085                                 Process process = Runtime.getRuntime().exec(command);
1086                                 grabEmulatorOutput(process, monitor);
1087 
1088                                 // This small wait prevents the dialog from closing too fast:
1089                                 // When it works, the emulator returns immediately, even if
1090                                 // no UI is shown yet. And when it fails (because the AVD is
1091                                 // locked/running)
1092                                 // if we don't have a wait we don't capture the error for
1093                                 // some reason.
1094                                 for (int i = 0; i < n; i++) {
1095                                     try {
1096                                         Thread.sleep(100);
1097                                         monitor.incProgress(1);
1098                                     } catch (InterruptedException e) {
1099                                         // ignore
1100                                     }
1101                                 }
1102                             } catch (IOException e) {
1103                                 monitor.logError("Failed to start emulator: %1$s",
1104                                         e.getMessage());
1105                             }
1106                         }
1107             });
1108         }
1109     }
1110 
1111     /**
1112      * Get the stderr/stdout outputs of a process and return when the process is done.
1113      * Both <b>must</b> be read or the process will block on windows.
1114      * @param process The process to get the output from.
1115      * @param monitor An {@link ITaskMonitor} to capture errors. Cannot be null.
1116      */
grabEmulatorOutput(final Process process, final ITaskMonitor monitor)1117     private void grabEmulatorOutput(final Process process, final ITaskMonitor monitor) {
1118         // read the lines as they come. if null is returned, it's because the process finished
1119         new Thread("emu-stderr") { //$NON-NLS-1$
1120             @Override
1121             public void run() {
1122                 // create a buffer to read the stderr output
1123                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
1124                 BufferedReader errReader = new BufferedReader(is);
1125 
1126                 try {
1127                     while (true) {
1128                         String line = errReader.readLine();
1129                         if (line != null) {
1130                             monitor.logError("%1$s", line);    //$NON-NLS-1$
1131                         } else {
1132                             break;
1133                         }
1134                     }
1135                 } catch (IOException e) {
1136                     // do nothing.
1137                 }
1138             }
1139         }.start();
1140 
1141         new Thread("emu-stdout") { //$NON-NLS-1$
1142             @Override
1143             public void run() {
1144                 InputStreamReader is = new InputStreamReader(process.getInputStream());
1145                 BufferedReader outReader = new BufferedReader(is);
1146 
1147                 try {
1148                     while (true) {
1149                         String line = outReader.readLine();
1150                         if (line != null) {
1151                             monitor.log("%1$s", line);    //$NON-NLS-1$
1152                         } else {
1153                             break;
1154                         }
1155                     }
1156                 } catch (IOException e) {
1157                     // do nothing.
1158                 }
1159             }
1160         }.start();
1161     }
1162 
isAvdRepairable(AvdStatus avdStatus)1163     private boolean isAvdRepairable(AvdStatus avdStatus) {
1164         return avdStatus == AvdStatus.ERROR_IMAGE_DIR;
1165     }
1166 }
1167