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