Home | History | Annotate | Download | only in widgets
      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          */
    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          */
    155         boolean accept(AvdInfo avd);
    156 
    157         /**
    158          * Called after {@link #accept(AvdInfo)} has been called on all the AVDs.
    159          */
    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 
    170         TargetBasedFilter(IAndroidTarget target) {
    171             mTarget = target;
    172         }
    173 
    174         @Override
    175         public void prepare() {
    176             // nothing to prepare
    177         }
    178 
    179         @Override
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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")
    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 
    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 
    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 
    899     private void onDetails() {
    900         AvdInfo avdInfo = getTableSelection();
    901 
    902         AvdDetailsDialog dlg = new AvdDetailsDialog(mTable.getShell(), avdInfo);
    903         dlg.open();
    904     }
    905 
    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      */
    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 
   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 
   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 
   1184     private boolean isAvdRepairable(AvdStatus avdStatus) {
   1185         return avdStatus == AvdStatus.ERROR_IMAGE_DIR;
   1186     }
   1187 }
   1188