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