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